yzx 3 日 前
コミット
19c32443aa
100 ファイル変更15480 行追加2582 行削除
  1. 15 9
      ruoyi-admin/pom.xml
  2. 163 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/AIChatController.java
  3. 40 533
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/ApiController.java
  4. 128 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcAsrLanguagesController.java
  5. 40 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcCallTaskController.java
  6. 42 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcInboundLlmAccountController.java
  7. 20 1
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcLlmAgentAccountController.java
  8. 142 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcTtsAliyunController.java
  9. 39 39
      ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/DoubaoVclController.java
  10. 41 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcAsrLanguages.java
  11. 2 55
      ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcCallPhone.java
  12. 12 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcCallTask.java
  13. 12 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcInboundLlmAccount.java
  14. 9 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcTtsAliyun.java
  15. 6 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/ILlmCapability.java
  16. 207 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/Coze.java
  17. 131 1
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/DeepSeekChat.java
  18. 126 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/Dify.java
  19. 9 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/MaxKB.java
  20. 2 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/model/AccountBaseEntity.java
  21. 62 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/mapper/CcAsrLanguagesMapper.java
  22. 1 11
      ruoyi-admin/src/main/java/com/ruoyi/aicall/mapper/CcCallPhoneMapper.java
  23. 7 16
      ruoyi-admin/src/main/java/com/ruoyi/aicall/model/ApiCallRecordQueryParams.java
  24. 0 1
      ruoyi-admin/src/main/java/com/ruoyi/aicall/model/CallTaskStatModel.java
  25. 28 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/model/ChatRequest.java
  26. 62 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/service/ICcAsrLanguagesService.java
  27. 1 10
      ruoyi-admin/src/main/java/com/ruoyi/aicall/service/ICcCallPhoneService.java
  28. 95 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/service/impl/CcAsrLanguagesServiceImpl.java
  29. 0 72
      ruoyi-admin/src/main/java/com/ruoyi/aicall/service/impl/CcCallPhoneServiceImpl.java
  30. 175 175
      ruoyi-admin/src/main/java/com/ruoyi/aicall/tencent/TencentCloudManage.java
  31. 3 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/utils/ClientIpCheck.java
  32. 1102 0
      ruoyi-admin/src/main/java/com/ruoyi/aicall/utils/LlmTest.java
  33. 140 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcFirewalldController.java
  34. 8 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcGatewaysController.java
  35. 24 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcIvrController.java
  36. 14 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcParamsController.java
  37. 210 11
      ruoyi-admin/src/main/java/com/ruoyi/cc/controller/FsConfController.java
  38. 4 4
      ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcCustInfo.java
  39. 0 6
      ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcGateways.java
  40. 6 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcIvr.java
  41. 0 5
      ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcOutboundCdr.java
  42. 22 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/domain/FirewallRule.java
  43. 0 9
      ruoyi-admin/src/main/java/com/ruoyi/cc/mapper/CcExtNumMapper.java
  44. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/cc/mapper/CcParamsMapper.java
  45. 0 9
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcExtNumService.java
  46. 0 2
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcOutboundCdrService.java
  47. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcParamsService.java
  48. 17 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/IFirewallService.java
  49. 2 10
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcExtNumServiceImpl.java
  50. 0 56
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcOutboundCdrServiceImpl.java
  51. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcParamsServiceImpl.java
  52. 53 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/FirewallServiceImpl.java
  53. 0 1
      ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/FsConfServiceImpl.java
  54. 304 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/utils/FirewalldXmlUtil.java
  55. 123 0
      ruoyi-admin/src/main/java/com/ruoyi/cc/utils/MyFileUtil.java
  56. 16 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
  57. 16 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
  58. 16 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
  59. 67 3
      ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SysInitConfig.java
  60. 5 4
      ruoyi-admin/src/main/resources/application-dev.yml
  61. 4 7
      ruoyi-admin/src/main/resources/application-local.yml
  62. 211 0
      ruoyi-admin/src/main/resources/application-nongdan.yml
  63. 3 1
      ruoyi-admin/src/main/resources/application-pro.yml
  64. 3 1
      ruoyi-admin/src/main/resources/application-test.yml
  65. 1 1
      ruoyi-admin/src/main/resources/application.yml
  66. 72 0
      ruoyi-admin/src/main/resources/mapper/aicall/CcAsrLanguagesMapper.xml
  67. 0 74
      ruoyi-admin/src/main/resources/mapper/aicall/CcCallPhoneMapper.xml
  68. 36 7
      ruoyi-admin/src/main/resources/mapper/aicall/CcCallTaskMapper.xml
  69. 33 8
      ruoyi-admin/src/main/resources/mapper/aicall/CcInboundLlmAccountMapper.xml
  70. 18 2
      ruoyi-admin/src/main/resources/mapper/aicall/CcTtsAliyunMapper.xml
  71. 0 6
      ruoyi-admin/src/main/resources/mapper/cc/CcExtNumMapper.xml
  72. 0 7
      ruoyi-admin/src/main/resources/mapper/cc/CcGatewaysMapper.xml
  73. 17 2
      ruoyi-admin/src/main/resources/mapper/cc/CcIvrMapper.xml
  74. 4 4
      ruoyi-admin/src/main/resources/mapper/cc/CcOutboundCdrMapper.xml
  75. 1120 0
      ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.i18n.js
  76. 1032 1
      ruoyi-admin/src/main/resources/static/i18n/messages.properties
  77. 1032 2
      ruoyi-admin/src/main/resources/static/i18n/messages_en.properties
  78. 1032 2
      ruoyi-admin/src/main/resources/static/i18n/messages_en_US.properties
  79. 1033 2
      ruoyi-admin/src/main/resources/static/i18n/messages_ja_JP.properties
  80. 1032 1
      ruoyi-admin/src/main/resources/static/i18n/messages_zh.properties
  81. 1032 1
      ruoyi-admin/src/main/resources/static/i18n/messages_zh_CN.properties
  82. BIN
      ruoyi-admin/src/main/resources/static/img/login-background.jpg
  83. 280 281
      ruoyi-admin/src/main/resources/static/phone-bar/ccPhoneBarSocket.js
  84. 10 10
      ruoyi-admin/src/main/resources/static/ruoyi/index.js
  85. 1 1
      ruoyi-admin/src/main/resources/static/ruoyi/js/common.js
  86. 17 16
      ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js
  87. 381 2
      ruoyi-admin/src/main/resources/templates/aicall/account/add.html
  88. 742 363
      ruoyi-admin/src/main/resources/templates/aicall/account/edit.html
  89. 26 23
      ruoyi-admin/src/main/resources/templates/aicall/callPhone/callPhone.html
  90. 767 316
      ruoyi-admin/src/main/resources/templates/aicall/callTask/add.html
  91. 9 9
      ruoyi-admin/src/main/resources/templates/aicall/callTask/callTask.html
  92. 753 351
      ruoyi-admin/src/main/resources/templates/aicall/callTask/edit.html
  93. 414 15
      ruoyi-admin/src/main/resources/templates/aicall/inboundllm/add.html
  94. 414 15
      ruoyi-admin/src/main/resources/templates/aicall/inboundllm/edit.html
  95. 1 1
      ruoyi-admin/src/main/resources/templates/cc/aliasrconf/aliasrconf.html
  96. 1 1
      ruoyi-admin/src/main/resources/templates/cc/alittsconf/alittsconf.html
  97. 1 1
      ruoyi-admin/src/main/resources/templates/cc/asrengine/asrengine.html
  98. 87 0
      ruoyi-admin/src/main/resources/templates/cc/awsasrconf/awsasrconf.html
  99. 87 0
      ruoyi-admin/src/main/resources/templates/cc/awsttsconf/awsttsconf.html
  100. 1 1
      ruoyi-admin/src/main/resources/templates/cc/catlogs/catlogs.html

+ 15 - 9
ruoyi-admin/pom.xml

@@ -135,22 +135,28 @@
 <!--            <version>2.6.0</version>-->
 <!--        </dependency>-->
 
-        <dependency>
-            <groupId>com.aliyun</groupId>
-            <artifactId>aliyun-java-sdk-core</artifactId>
-            <version>4.6.4</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>com.aliyun</groupId>-->
+<!--            <artifactId>aliyun-java-sdk-core</artifactId>-->
+<!--            <version>4.6.4</version>-->
+<!--        </dependency>-->
 <!--        <dependency>-->
 <!--            <groupId>com.aliyun</groupId>-->
 <!--            <artifactId>cloudfw20171207</artifactId>-->
 <!--            <version>8.0.2</version>-->
 <!--        </dependency>-->
 
-        <!-- tencent cloud sdk -->
+<!--        &lt;!&ndash; tencent cloud sdk &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>com.tencentcloudapi</groupId>-->
+<!--            <artifactId>tencentcloud-sdk-java-lighthouse</artifactId>-->
+<!--            <version>3.1.1114</version>  &lt;!&ndash; 或尝试 3.1.1000 &ndash;&gt;-->
+<!--        </dependency>-->
+
         <dependency>
-            <groupId>com.tencentcloudapi</groupId>
-            <artifactId>tencentcloud-sdk-java-lighthouse</artifactId>
-            <version>3.1.1114</version>  <!-- 或尝试 3.1.1000 -->
+            <groupId>com.coze</groupId>
+            <artifactId>coze-api</artifactId>
+            <version>0.3.1</version>
         </dependency>
 
     </dependencies>

+ 163 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/AIChatController.java

@@ -0,0 +1,163 @@
+package com.ruoyi.aicall.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.aicall.domain.CcInboundLlmAccount;
+import com.ruoyi.aicall.domain.CcLlmAgentAccount;
+import com.ruoyi.aicall.llm.ILlmCapability;
+import com.ruoyi.aicall.llm.model.AccountBaseEntity;
+import com.ruoyi.aicall.model.ChatRequest;
+import com.ruoyi.aicall.service.ICcInboundLlmAccountService;
+import com.ruoyi.aicall.service.ICcLlmAgentAccountService;
+import com.ruoyi.cc.utils.LlmAccountParser;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/ai")
+public class AIChatController {
+
+    @Autowired
+    private ICcLlmAgentAccountService llmAgentAccountService;
+    @Autowired
+    private ICcInboundLlmAccountService inboundLlmAccountService;
+
+    /**
+     * 模拟 ChatGPT 流式响应
+     */
+    @PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    @ResponseBody
+    public SseEmitter chatStream(@RequestBody ChatRequest chatRequest, HttpServletRequest httpRequest) {
+        SseEmitter emitter = new SseEmitter(0L); // 0表示永不超时
+        String requestId = chatRequest.getRequestId();
+
+//        // 异步执行,避免阻塞
+//        CompletableFuture.runAsync(() -> {
+//            try {
+//                for (int i = 0; i < 5; i++) {
+//                    // 发送数据
+//                    emitter.send(SseEmitter.event()
+//                            .id(String.valueOf(i))
+//                            .name("message")
+//                            .data("数据块 #" + i + " - " + LocalTime.now()));
+//
+//                    Thread.sleep(1000); // 模拟耗时操作
+//                }
+//                emitter.complete(); // 完成
+//            } catch (Exception e) {
+//                emitter.completeWithError(e);
+//            }
+//        });
+
+        // 异步执行,避免阻塞
+        CompletableFuture.runAsync(() -> {
+            try {
+                List<CcInboundLlmAccount> ccInboundLlmAccountList = inboundLlmAccountService.selectCcInboundLlmAccountByCallee(chatRequest.getInboundCallee());
+                if (CollectionUtils.isEmpty(ccInboundLlmAccountList)) {
+                    // 发送数据
+                    JSONObject rsponseData = new JSONObject();
+                    rsponseData.put("requestId", requestId);
+                    rsponseData.put("code", "500");
+                    rsponseData.put("content", "客服号码不正确");
+                    rsponseData.put("msgType", "message");
+                    log.info(JSONObject.toJSONString(rsponseData));
+                    emitter.send(SseEmitter.event()
+                            .name("message")
+                            .data(JSONObject.toJSONString(rsponseData)));
+                    emitter.complete(); // 完成
+                    return ;
+                } else {
+                    CcLlmAgentAccount ccLlmAgentAccount = llmAgentAccountService.selectCcLlmAgentAccountById(ccInboundLlmAccountList.get(0).getLlmAccountId());
+                    if (null == ccLlmAgentAccount) {
+                        // 发送数据
+                        JSONObject rsponseData = new JSONObject();
+                        rsponseData.put("requestId", requestId);
+                        rsponseData.put("code", "500");
+                        rsponseData.put("content", "客服号码不正确");
+                        rsponseData.put("msgType", "message");
+                        log.info(JSONObject.toJSONString(rsponseData));
+                        emitter.send(SseEmitter.event()
+                                .name("message")
+                                .data(JSONObject.toJSONString(rsponseData)));
+                        emitter.complete(); // 完成
+                        return ;
+                    } else {
+                        ILlmCapability llmAccount;
+                        String provider = ccLlmAgentAccount.getProviderClassName();
+                        AccountBaseEntity account =  LlmAccountParser.parse(ccLlmAgentAccount);
+                        llmAccount = (ILlmCapability) (Class.forName("com.ruoyi.aicall.llm.impl." + provider).newInstance());
+                        llmAccount.setAccount(account);
+                        String question = chatRequest.getQuestion();
+                        // 首句
+                        if (CollectionUtils.isEmpty(chatRequest.getMessages())) {
+                            chatRequest.setMessages(new ArrayList<>());
+                            // 没有历史记录,直接返回首句
+                            String openingRemarks = account.openingRemarks;
+                            JSONObject rsponseData = new JSONObject();
+                            rsponseData.put("requestId", requestId);
+                            rsponseData.put("code", "200");
+                            rsponseData.put("content", openingRemarks);
+                            JSONObject userMessage = new JSONObject();
+                            userMessage.put("role",  "assistant");
+                            userMessage.put("content",  openingRemarks);
+                            userMessage.put("content_type", "text");
+                            chatRequest.getMessages().add(userMessage);
+                            rsponseData.put("messages", chatRequest.getMessages());
+                            rsponseData.put("msgType", "message");
+                            log.info(JSONObject.toJSONString(rsponseData));
+                            emitter.send(SseEmitter.event()
+                                    .name("message")
+                                    .data(JSONObject.toJSONString(rsponseData)));
+                            emitter.complete(); // 完成
+                            return ;
+                        } else {
+                            // 客户未说话
+                            if (StringUtils.isEmpty(question)) {
+                                String noVoiceTips = account.customerNoVoiceTips;
+                                // 没有历史记录,直接返回首句
+                                JSONObject rsponseData = new JSONObject();
+                                rsponseData.put("requestId", requestId);
+                                rsponseData.put("code", "200");
+                                rsponseData.put("content", noVoiceTips);
+                                JSONObject userMessage = new JSONObject();
+                                userMessage.put("role",  "assistant");
+                                userMessage.put("content",  noVoiceTips);
+                                userMessage.put("content_type", "text");
+                                chatRequest.getMessages().add(userMessage);
+                                rsponseData.put("messages", chatRequest.getMessages());
+                                rsponseData.put("msgType", "message");
+                                log.info(JSONObject.toJSONString(rsponseData));
+                                emitter.send(SseEmitter.event()
+                                        .name("message")
+                                        .data(JSONObject.toJSONString(rsponseData)));
+                                emitter.complete(); // 完成
+                                return ;
+                            } else {
+                                // 调用大模型获取应答话术
+                                llmAccount.getStreamingChatContent(emitter, chatRequest);
+                                emitter.complete(); // 完成
+                                return;
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                emitter.completeWithError(e);
+            }
+        });
+
+        return emitter;
+    }
+}

+ 40 - 533
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/ApiController.java

@@ -13,36 +13,27 @@ import com.ruoyi.aicall.service.ICcCallTaskService;
 import com.ruoyi.aicall.service.ICcLlmAgentAccountService;
 import com.ruoyi.aicall.service.ICcTtsAliyunService;
 import com.ruoyi.aicall.utils.ClientIpCheck;
-import com.ruoyi.aicall.utils.DESUtil;
 import com.ruoyi.cc.domain.*;
 import com.ruoyi.cc.service.*;
 import com.ruoyi.cc.utils.DateValidatorUtils;
-import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.ShiroUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.uuid.UuidGenerator;
-import com.ruoyi.framework.shiro.service.SysPasswordService;
-import com.ruoyi.framework.shiro.util.AuthorizationUtils;
-import com.ruoyi.system.service.ISysUserService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.RandomUtils;
-import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.*;
-import java.util.stream.Collectors;
 
 @Controller
 @Slf4j
@@ -73,18 +64,6 @@ public class ApiController extends BaseController {
     private ICcExtNumService ccExtNumService;
     @Autowired
     private ICcParamsService ccParamsService;
-    @Autowired
-    private ISysUserService userService;
-    @Autowired
-    private SysPasswordService passwordService;
-    @Autowired
-    private ICcCustCallRecordService ccCustCallRecordService;
-    @Autowired
-    private ICcCustInfoService ccCustInfoService;
-    @Autowired
-    private ISysDivisionDataService sysDivisionDataService;
-    @Autowired
-    private ICcOutboundCdrService ccOutboundCdrService;
 
     /**
      * 获取外呼网关列表接口
@@ -93,14 +72,18 @@ public class ApiController extends BaseController {
      */
     @GetMapping("/gateway/list")
     @ResponseBody
-    public AjaxResult getGatewayList(HttpServletRequest req){
+    public AjaxResult getGatewayList(HttpServletRequest req, @RequestParam(value = "purposes", required = false) String purposes){
         // 校验客户端ip是否在白名单内
         if (!ClientIpCheck.checkIp(req)) {
             return AjaxResult.error(AjaxResult.Type.NO_AUTH, "未授权,请联系系统管理员添加ip白名单!", "");
         }
         // 获取外呼网关列表
         Map<String, Object> params = new HashMap<>();
-        params.put("purposes", Arrays.asList(2,3));
+        if (StringUtils.isBlank(purposes)) {
+            params.put("purposes", Arrays.asList(1,2,3));
+        } else {
+            params.put("purposes", Arrays.asList(purposes.split(",")));
+        }
         List<CcGateways> list = ccGatewaysService.selectCcGatewaysList(new CcGateways().setParams(params));
         List<ApiGatewaysModel> result = new ArrayList<>();
         for (CcGateways data: list) {
@@ -422,34 +405,6 @@ public class ApiController extends BaseController {
         return AjaxResult.success("success", apiCallTaskModel);
     }
 
-    /**
-     * 删除外呼任务
-     */
-    @PostMapping( "/removeTask")
-    @ResponseBody
-    @Transactional
-    public AjaxResult removeTask(@RequestBody Map<String, Long[]> paramMap)
-    {
-        Long[] batchIds= paramMap.get("batchIds");
-        if(batchIds==null){
-            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "batchIds不能为空!", "");
-        }
-        for (Long batchId : batchIds) {
-            // 备份拨打记录数据
-            ccCallPhoneService.bakCallPhoneByBatchId(batchId);
-            // 删除拨打记录数据
-            ccCallPhoneService.delCallPhoneByBatchId(batchId);
-            // 备份任务数据
-            ccCallTaskService.bakCallTaskByBatchId(batchId);
-        }
-
-        // 删除任务数据
-        String batchIdsStr = Arrays.stream(batchIds)
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
-        return toAjax(ccCallTaskService.deleteCcCallTaskByBatchIds(batchIdsStr));
-    }
-
     /**
      * 启动任务接口
      * @param req
@@ -587,7 +542,7 @@ public class ApiController extends BaseController {
         }
 
         // 追加名单
-        int successCount = 0;
+        Integer successCount = 0;
         List<CcCallPhone> callPhoneList = new ArrayList<>();
         for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
             String phoneNum = commonPhoneModel.getPhoneNum();
@@ -603,11 +558,11 @@ public class ApiController extends BaseController {
                 callPhoneList = new ArrayList<>();
             }
         }
-        if (!callPhoneList.isEmpty()) {
+        if (callPhoneList.size() > 0) {
             ccCallPhoneService.batchInsertCcCallPhone(callPhoneList);
         }
         log.info("成功追加" + successCount + "个名单");
-        return AjaxResult.success("成功追加" + successCount + "个名单",callPhoneList);
+        return AjaxResult.success("成功追加" + successCount + "个名单");
     }
 
 
@@ -672,6 +627,7 @@ public class ApiController extends BaseController {
         } else {
             bizJson.put("tailNum", phoneNum);
         }
+        bizJson.put("phoneNum", phoneNum);
         callPhone.setCustName(bizJson.getString("custName"));
         if (null == callPhone.getCustName()) {
             callPhone.setCustName("");
@@ -1072,20 +1028,13 @@ public class ApiController extends BaseController {
 
         String extnum = ccExtNum.getExtNum().toString();
         String opnum = ccExtNum.getUserCode();
-        String password = ccExtNum.getExtPass();
         String groupId = "1";
         String skillLevel = "9";
         String projectId = "1";
-        //1.创建token
         String loginToken = ccExtNumService.createToken(extnum, opnum, groupId, skillLevel, projectId);
-        //2.获取加密密码
-        String encryptStr = DESUtil.encrypt(password + "," + DateUtils.format(DateUtils.addDays(new Date(), 1), "yyyyMMddHHmm"));
-        encryptStr = String.format("var _phoneEncryptPassword='%s';", encryptStr);
-
         // 网关用途 0 dropped; 1 phonebar; 2 outbound tasks; 3. Unlimited
         Map<String, Object> params = new HashMap<>();
         params.put("purposes", Arrays.asList(1,3));
-        //3.获取工具条能用的所有网关列表
         List<CcGateways> gatewaysList = ccGatewaysService.selectCcGatewaysList(new CcGateways().setParams(params));
         List<JSONObject> gatewayList = new ArrayList<>();
         for (CcGateways ccGateways: gatewaysList) {
@@ -1111,487 +1060,45 @@ public class ApiController extends BaseController {
         callConfig.put("scriptServer", scriptServer);
         callConfig.put("scriptPort", scriptPort);
         callConfig.put("loginToken", loginToken);
-        callConfig.put("encryptPsw", encryptStr);
         callConfig.put("gatewayList", gatewayList);
-        //登录账号
-        callConfig.put("opNum", opnum);
-        //登录用户名称
-        SysUser sysUser = userService.selectUserByLoginName(opnum);
-        if(sysUser != null){
-            callConfig.put("userName", sysUser.getUserName());
-        }
 
         return AjaxResult.success(callConfig);
 
     }
 
-    /**
-     * 获取电话工具条的网关列表
-     */
-    @PostMapping("/myPhoneBar/params")
-    @ResponseBody
-    public AjaxResult getMyPhoneBaseParams(@RequestBody Map<String,String> param) {
-
-        String extNum = param.get("extNum");
-        String myGateway = param.get("myGateway");
-        if(extNum == null){
-            return AjaxResult.error("分机号参数缺失");
-        }
-        // 获取分机号
-        CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByExtNum(Long.valueOf(extNum));
-
-        String extnum = ccExtNum.getExtNum().toString();
-        String opnum = ccExtNum.getUserCode();
-        String password = ccExtNum.getExtPass();
-        String groupId = "1";
-        String skillLevel = "9";
-        String projectId = "1";
-        //1.创建token
-        String loginToken = ccExtNumService.createToken(extnum, opnum, groupId, skillLevel, projectId);
-        //2.获取加密密码
-        String encryptStr = DESUtil.encrypt(password + "," + DateUtils.format(DateUtils.addDays(new Date(), 1), "yyyyMMddHHmm"));
-        encryptStr = String.format("var _phoneEncryptPassword='%s';", encryptStr);
-
-        CcGateways ccGatewaysVo = new CcGateways();
-        //判断指定网关还是全部网关
-        if(StringUtils.isNotBlank(myGateway)){
-            List<Long> gatewayIds = Arrays.stream(myGateway.split(","))
-                    .map(Long::parseLong)
-                    .collect(Collectors.toList());
-            ccGatewaysVo.setGatewayIds(gatewayIds);
-        }else{
-            // 网关用途 0 已废弃; 1 电话条; 2 外呼任务; 3 无限制
-            Map<String, Object> params = new HashMap<>();
-            params.put("purposes", Arrays.asList(1,3));
-            ccGatewaysVo.setParams(params);
-        }
-        //3.获取工具条网关列表
-        List<CcGateways> gatewaysList = ccGatewaysService.selectCcGatewaysList(ccGatewaysVo);
-        List<JSONObject> gatewayList = new ArrayList<>();
-        for (CcGateways ccGateways: gatewaysList) {
-            JSONObject configGateway = new JSONObject();
-            configGateway.put("uuid", ccGateways.getId().toString());
-            configGateway.put("updateTime", ccGateways.getUpdateTime());
-            configGateway.put("gatewayAddr", ccGateways.getGwAddr());
-            configGateway.put("callerNumber", ccGateways.getCaller());
-            configGateway.put("calleePrefix", ccGateways.getCalleePrefix());
-            configGateway.put("callProfile", ccGateways.getProfileName());
-            configGateway.put("priority", ccGateways.getPriority());
-            configGateway.put("concurrency", ccGateways.getMaxConcurrency());
-            configGateway.put("register", ccGateways.getRegister());
-            configGateway.put("authUsername", ccGateways.getAuthUsername());
-            configGateway.put("audioCodec", ccGateways.getCodec());
-            gatewayList.add(configGateway);
-        }
-        JSONObject callConfig = new JSONObject();
-
-        String scriptServer = ccParamsService.getParamValueByCode("call-center-server-ip-addr", "");
-        String scriptPort = ccParamsService.getParamValueByCode("call-center-websocket-port", "");
-
-        callConfig.put("scriptServer", scriptServer);
-        callConfig.put("scriptPort", scriptPort);
-        callConfig.put("loginToken", loginToken);
-        callConfig.put("encryptPsw", encryptStr);
-        callConfig.put("gatewayList", gatewayList);
-        //登录账号
-        callConfig.put("opNum", opnum);
-        //登录用户名称
-        SysUser sysUser = userService.selectUserByLoginName(opnum);
-        if(sysUser != null){
-            callConfig.put("userName", sysUser.getUserName());
-        }
-
-        return AjaxResult.success(callConfig);
-
-    }
 
-    /**
-     * 查询未绑定的分机管理列表
-     */
-    @GetMapping("/extnum/selectUnBindCcExtNumList")
-    @ResponseBody
-    public AjaxResult selectUnBindCcExtNumList()
-    {
-        return AjaxResult.success(ccExtNumService.selectUnBindCcExtNumList());
-    }
-    /**
-     * 新增用户且绑定未使用的分机返回用户
-     */
-    @PostMapping("/user/addUserOrBindExtNumReturnUser")
-    @ResponseBody
-    @Transactional
-    public AjaxResult addUserOrBindExtNumReturnUser(@RequestBody SysUser user)
-    {
-        if (!userService.checkLoginNameUnique(user))
-        {
-            throw new RuntimeException("新增用户'" + user.getLoginName() + "'失败,登录账号已存在");
-        }
-        else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
-        {
-            throw new RuntimeException("新增用户'" + user.getLoginName() + "'失败,手机号码已存在");
-        }
-        user.setSalt(ShiroUtils.randomSalt());
-        user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt()));
-        user.setPwdUpdateDate(DateUtils.getNowDate());
-        user.setCreateBy("ylrz");
-        int i = userService.insertUser(user);
-        if(i>0){
-            //绑定分机
-            if (StringUtils.isNotEmpty(user.getLoginName())) {
-                CcExtNum extNum = ccExtNumService.selectCcExtNumByExtNum(user.getExtNum());
-                if (null != extNum) {
-                    extNum.setUserCode(user.getLoginName());
-                    int num = ccExtNumService.updateCcExtNum(extNum);
-                    if(num>0){
-                        return AjaxResult.success(user);
-                    }
-                }
-            }
-        }
-        throw new RuntimeException("新增用户失败");
-    }
-    /**
-     * 修改用户且绑分机
-     */
-    @PostMapping("/user/editUserOrUnBindExtNum")
+    @PostMapping("/test/localChat")
     @ResponseBody
-    @Transactional
-    public AjaxResult editUserOrUnBindExtNum(@RequestBody SysUser user)
-    {
-        if (!userService.checkLoginNameUnique(user))
-        {
-            throw new RuntimeException("修改用户'" + user.getLoginName() + "'失败,登录账号已存在");
-        }
-        else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
-        {
-            throw new RuntimeException("修改用户'" + user.getLoginName() + "'失败,手机号码已存在");
-        }
-        user.setUpdateBy("ylrz");
-        AuthorizationUtils.clearAllCachedAuthorizationInfo();
-        int i = userService.updateUser(user);
-        if(i>0){
-            //修改绑定分机
-            CcExtNum extNum = new CcExtNum();
-            extNum.setExtNum(user.getExtNum());
-            extNum.setUserCode(user.getLoginName());
-            //先清除原分机绑定
-            int cleanNum = ccExtNumService.cleanCcExtNumByUserCode(user.getLoginName());
-            if(cleanNum>0){
-                int updateNum = ccExtNumService.updateCcExtNumByUserCode(extNum);
-                if(updateNum>0){
-                    return AjaxResult.success(user);
-                }
+    public JSONObject localChat(HttpServletRequest request, @RequestBody JSONObject reqParams) {
+//        {"code":200, "data":{"choices":[{"content":"xxxxxxx", "wavFilePath":"/home/Records/251224101457010001/20260113161253001914.wav"}]}}
+        log.info("请求参数:{}", reqParams);
+        Integer rounds = 0;
+        for (int i = 0; i < reqParams.getJSONArray("messages").size(); i++) {
+            if ("assistant".equals(reqParams.getJSONArray("messages").getJSONObject(i).getString("role"))) {
+                rounds ++;
             }
         }
-        throw new RuntimeException("修改用户失败");
-    }
-
-
-    /**
-     * 获取手动外呼客户沟通信息
-     * @param phoneNum 手机号
-     * @param callType 类型  1呼入 2外呼
-     * @param uuid  通话uuid
-     */
-    @GetMapping("/getCustCommunicationInfo")
-    @ResponseBody
-    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum, @RequestParam("callType") Integer callType, @RequestParam("uuid") String uuid)
-    {
-        Map<String,Object> mmap = new HashMap<>();
-        CcCustInfo ccCustInfo = ccCustInfoService.selectCcCustInfoByPhoneNum(phoneNum);
-        if (null == ccCustInfo) {
-            ccCustInfo = new CcCustInfo();
-            ccCustInfo.setCallRecordList(new ArrayList<>());
-        } else {
-            ccCustInfo.setCallRecordList(ccCustCallRecordService.selectCcCustCallRecordList(new CcCustCallRecord().setCustId(ccCustInfo.getId())));
-        }
-        ccCustInfo.setPhoneNum(phoneNum);
-        mmap.put("ccCustInfo", ccCustInfo);
-        mmap.put("callType", callType);
-        mmap.put("uuid", uuid);
-        // 省下拉框
-        List<SysDivisionData> sysDivisionData = sysDivisionDataService.selectSysDivisionDataList(null);
-        List<SysDivisionData> provinces = sysDivisionData.stream()
-                .filter(d -> d.getDeep() == 0)
-                .collect(Collectors.toList());
-        mmap.put("provinces", provinces);
-        // 市下拉框
-        List<SysDivisionData> citys = sysDivisionData.stream()
-                .filter(d -> d.getDeep() == 1)
-                .collect(Collectors.toList());
-        mmap.put("citys", citys);
-        // 区县下拉框
-        List<SysDivisionData> countys = sysDivisionData.stream()
-                .filter(d -> d.getDeep() == 2)
-                .collect(Collectors.toList());
-        mmap.put("countys", countys);
-        return AjaxResult.success(mmap);
-    }
-
-    /**
-     * 新增保存手动外呼沟通记录
-     */
-    @PostMapping("/add/custcallrecord")
-    @ResponseBody
-    public AjaxResult addAustcallrecord(@RequestBody CcCustInfo ccCustInfo)
-    {
-        ccCustInfoService.updateCcCustInfo(ccCustInfo);
-        CcCustInfo custInfoBak = ccCustInfoService.selectCcCustInfoByPhoneNum(ccCustInfo.getPhoneNum());
-        CcCustCallRecord callRecord = JSONObject.parseObject(ccCustInfo.getCallRecord(), CcCustCallRecord.class);
-        callRecord.setCustId(custInfoBak.getId());
-        //这里改成查询
-
-        callRecord.setUserId(ccCustInfo.getOpNum());
-        callRecord.setUserRealName(ccCustInfo.getUserName());
-        callRecord.setCreateTime(new Date());
-        CcCustCallRecord hisCallRecord = ccCustCallRecordService.selectCcCustCallRecordByUuid(callRecord.getUuid());
-        if (null == hisCallRecord) {
-            return toAjax(ccCustCallRecordService.insertCcCustCallRecord(callRecord));
-        } else {
-            callRecord.setId(hisCallRecord.getId());
-            return toAjax(ccCustCallRecordService.updateCcCustCallRecord(callRecord));
-        }
-    }
-
-    /**
-     * 查询手动外呼记录列表
-     */
-    @PostMapping("/outboundcdrList")
-    @ResponseBody
-    public TableDataInfo outboundcdrList(@RequestBody CcOutboundCdr ccOutboundCdr)
-    {
-        startPage();
-        List<CcOutboundCdr> list = ccOutboundCdrService.selectCcOutboundCdrList(ccOutboundCdr);
-        for (CcOutboundCdr data: list) {
-            data.setWavFileUrl("/recordings/files?filename=" + data.getRecordFilename());
-        }
-        return getDataTable(list);
-    }
-
-    /**
-     * 通话记录查询接口(返回完整的数据表格式)
-     */
-    @PostMapping("/call/phone/records")
-    @ResponseBody
-    public TableDataInfo getcallPhoneRecords(HttpServletRequest req, @RequestBody ApiCallRecordQueryParams queryParams)
-    {
-        TableDataInfo tableDataInfo;
-        // 校验请求方ip是否合法
-        if (!ClientIpCheck.checkIp(req)) {
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.NO_AUTH.value());
-            tableDataInfo.setMsg("未授权,请联系系统管理员添加ip白名单!");
-            return tableDataInfo;
-        }
-        // 分页参数处理
-        if (null == queryParams.getPageNum()
-                && null == queryParams.getPageSize()) {
-            queryParams.setPageNum(1);
-            queryParams.setPageSize(200000);
-        }
-        if (null == queryParams.getPageNum()) {
-            queryParams.setPageNum(1);
-        }
-        if (null == queryParams.getPageSize()) {
-            queryParams.setPageSize(20);
-        }
-        // 类型(01:呼入, 02:AI外呼, 03:人工外呼)
-        String callType = queryParams.getCallType();
-        if (StringUtils.isBlank(callType)) {
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
-            tableDataInfo.setMsg("callType不能为空!");
-            return tableDataInfo;
-        }
-        // 校验参数
-        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeStart())
-                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeStart())) {
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
-            tableDataInfo.setMsg("calloutTimeStart格式不正确,请使用'yyyy-MM-dd HH:mm:ss'格式!");
-            return tableDataInfo;
-        }
-        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeEnd())
-                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeEnd())) {
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
-            tableDataInfo.setMsg("calloutTimeStart格式不正确,请使用'yyyy-MM-dd HH:mm:ss'格式!");
-            return tableDataInfo;
-        }
-
-        // 01:呼入, 02:AI外呼, 03:人工外呼
-        if ("01".equals(callType)) {
-            return getInboundRecords(queryParams);
-        } else if ("02".equals(callType)) {
-            return getAiCallRecordsTable(queryParams);
-        } else if ("03".equals(callType)) {
-            return getOutboundRecordsTable(queryParams);
-        } else {
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
-            tableDataInfo.setMsg("callType参数不合法,呼入请输入01,AI外呼请输入02,手工外呼请输入03!");
-            return tableDataInfo;
-        }
-    }
-    //ai外呼记录查询
-    private TableDataInfo getAiCallRecordsTable(ApiCallRecordQueryParams queryParams) {
-        Map<String, Object> params = new HashMap<>();
-        if (null != queryParams.getTimeLenStart()) {
-            params.put("timeLenStart", queryParams.getTimeLenStart());
-        }
-        if (null != queryParams.getTimeLenEnd()) {
-            params.put("timeLenEnd", queryParams.getTimeLenEnd());
-        }
-        if (null != queryParams.getCalloutTimeStart()) {
-            params.put("calloutTimeStart", queryParams.getCalloutTimeStart());
-        }
-        if (null != queryParams.getCalloutTimeEnd()) {
-            params.put("calloutTimeEnd", queryParams.getCalloutTimeEnd());
-        }
-        if (null != queryParams.getAnsweredTimeStart()) {
-            params.put("answeredTimeStart", queryParams.getAnsweredTimeStart());
-        }
-        if (null != queryParams.getAnsweredTimeEnd()) {
-            params.put("answeredTimeEnd", queryParams.getAnsweredTimeEnd());
-        }
-        if (null != queryParams.getCallEndTimeStart()) {
-            params.put("callEndTimeStart", queryParams.getCallEndTimeStart());
-        }
-        if (null != queryParams.getCallEndTimeEnd()) {
-            params.put("callEndTimeEnd", queryParams.getCallEndTimeEnd());
-        }
-        CcCallPhone ccCallPhone = new CcCallPhone();
-        if (null != queryParams.getBatchId() && queryParams.getBatchId() > 0) {
-            ccCallPhone.setBatchId(queryParams.getBatchId());
-        }
-        ccCallPhone.setUuid(queryParams.getUuid());
-        ccCallPhone.setTelephone(queryParams.getTelephone());
-        ccCallPhone.setAcdOpnum(queryParams.getExtnum());
-        ccCallPhone.setCallstatus(queryParams.getCallstatus());
-        ccCallPhone.setCallerNumber(queryParams.getCallerNumber());
-        ccCallPhone.setParams(params);
-        startPage(queryParams.getPageNum(), queryParams.getPageSize());
-        List<CcCallPhone> list = ccCallPhoneService.selectCcCallPhoneYlrzList(ccCallPhone);
-        list.forEach(callPhoneRecord -> {
-            if(StringUtils.isNotBlank(callPhoneRecord.getWavfile())){
-                if (callPhoneRecord.getWavfile().startsWith("/")) {
-                    callPhoneRecord.setWavfile("/recordings/files?filename=" + callPhoneRecord.getWavfile().substring(1));
-                }else{
-                    callPhoneRecord.setWavfile("/recordings/files?filename=" + callPhoneRecord.getWavfile());
-                }
-            }
-            callPhoneRecord.setCallstatusName( CcCallPhone.getCallStatusName(callPhoneRecord.getCallstatus()));
-            callPhoneRecord.setCalloutTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getCalloutTime())));
-            callPhoneRecord.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getAnsweredTime())));
-            callPhoneRecord.setCallEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getCallEndTime())));
-            callPhoneRecord.setTimeLenSec(DateUtils.formatTimeLength(callPhoneRecord.getTimeLen()/1000));
-        });
-        return getDataTable(list);
-    }
-    //人工外呼记录查询
-    private TableDataInfo getOutboundRecordsTable(ApiCallRecordQueryParams queryParams) {
-        Map<String, Object> params = new HashMap<>();
-        if (null != queryParams.getTimeLenStart()) {
-            params.put("timeLenStart", queryParams.getTimeLenStart());
-        }
-        if (null != queryParams.getTimeLenEnd()) {
-            params.put("timeLenEnd", queryParams.getTimeLenEnd());
-        }
-        if (null != queryParams.getCalloutTimeStart()) {
-            params.put("calloutTimeStart", queryParams.getCalloutTimeStart());
-        }
-        if (null != queryParams.getCalloutTimeEnd()) {
-            params.put("calloutTimeEnd", queryParams.getCalloutTimeEnd());
-        }
-        if (null != queryParams.getAnsweredTimeStart()) {
-            params.put("answeredTimeStart", queryParams.getAnsweredTimeStart());
-        }
-        if (null != queryParams.getAnsweredTimeEnd()) {
-            params.put("answeredTimeEnd", queryParams.getAnsweredTimeEnd());
-        }
-        if (null != queryParams.getEndTimeStart()) {
-            params.put("endTimeStart", queryParams.getEndTimeStart());
-        }
-        if (null != queryParams.getEndTimeEnd()) {
-            params.put("endTimeEnd", queryParams.getEndTimeEnd());
-        }
-        CcCallPhone ccCallPhone = new CcCallPhone();
-        if (null != queryParams.getBatchId() && queryParams.getBatchId() > 0) {
-            ccCallPhone.setBatchId(queryParams.getBatchId());
-        }
-
-        CcOutboundCdr outboundCdr = new CcOutboundCdr();
-        outboundCdr.setUuid(queryParams.getUuid());
-        outboundCdr.setCaller(queryParams.getTelephone());
-        outboundCdr.setOpnum(queryParams.getExtnum());
-        outboundCdr.setParams(params);
-        startPage(queryParams.getPageNum(), queryParams.getPageSize());
-        List<CcOutboundCdr> list = outboundCdrService.selectCcOutboundCdrYlrzList(outboundCdr);
-        list.forEach(callPhoneRecord -> {
-            if(StringUtils.isNotBlank(callPhoneRecord.getRecordFilename())){
-                if (callPhoneRecord.getRecordFilename().startsWith("/")) {
-                    callPhoneRecord.setRecordFilename("/recordings/files?filename=" + callPhoneRecord.getRecordFilename().substring(1));
-                }else{
-                    callPhoneRecord.setWavFileUrl("/recordings/files?filename=" + callPhoneRecord.getRecordFilename());
-                }
-                callPhoneRecord.setStartTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getStartTime())));
-                callPhoneRecord.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getAnsweredTime())));
-                callPhoneRecord.setEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getEndTime())));
-                callPhoneRecord.setTimeLenSec(DateUtils.formatTimeLength(callPhoneRecord.getTimeLen()/1000));
-                callPhoneRecord.setTimeLenValidStr(DateUtils.formatTimeLength(callPhoneRecord.getTimeLenValid()/1000));
-            }
-
-        });
-        return getDataTable(list);
-    }
-
-    /**
-     * 获取外呼网关列表接口
-     * purposes 1手动外呼电话条,2AI外呼,3不限制
-     * @param queryParams 网关参数
-     * @param req
-     */
-    @PostMapping("/gateway/myList")
-    @ResponseBody
-    public TableDataInfo getGatewayMyList(@RequestBody CcGateways queryParams, HttpServletRequest req){
-        // 校验请求方ip是否合法
-        if (!ClientIpCheck.checkIp(req)) {
-            TableDataInfo tableDataInfo;
-            tableDataInfo = new TableDataInfo();
-            tableDataInfo.setTotal(0);
-            tableDataInfo.setCode(AjaxResult.Type.NO_AUTH.value());
-            tableDataInfo.setMsg("未授权,请联系系统管理员添加ip白名单!");
-            return tableDataInfo;
-        }
-        startPage(queryParams.getPageNum(), queryParams.getPageSize());
-        return getDataTable(ccGatewaysService.selectCcGatewaysList(queryParams));
-    }
-
-    /**
-     * 获取外呼任务接通数据
-     * @param batchIds 任务id集合
-     */
-    @PostMapping("/task/connectSum")
-    @ResponseBody
-    public AjaxResult taskConnectSum(@RequestBody List<Long> batchIds){
-        return AjaxResult.success(ccCallPhoneService.statByBatchIds(batchIds));
-    }
-
-    /**
-     * 根据通话id集合查询uuid不为空的自动外呼数据
-     */
-    @PostMapping( "/getCcCallPhoneByIds")
-    @ResponseBody
-    public AjaxResult getCcCallPhoneByIds(@RequestBody List<String> callPhoneIds)
-    {
-        if(callPhoneIds==null){
-            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "callPhoneIds不能为空!", "");
-        }
-        return AjaxResult.success(ccCallPhoneService.selectCcCallPhoneListByIds(callPhoneIds));
+        String[] rspDatas = new String[]{
+//                "您好,请问您是测试1本人是吗?| /home/Records/23/round-1.wav; ",
+                "这里是中信银行委托方,这个电话号码是测试1在中信银行办理业务时登记的号码,您是测试1吗? | /home/Records/23/round-2-1.wav;/home/Records/23/round-2-2.wav;/home/Records/23/round-2-3.wav",
+                "这里是中信银行委托方,这边主要是通知您一下,您在中信银行卡尾号6388的信用卡已错过到期环款日,当期账单账面欠款总额9097元全国统一核账时间在今天下午5点需要您在此之前处理进来,可以吧?| /home/Records/23/round-3-1.wav;/home/Records/23/round-3-2.wav;",
+                "您的账单现在已经过环款日了,能和我们说一下您是为什么还没处理欠款吗? | /home/Records/23/round-4.wav",
+                "好的,请您在今天下午5点之前至少处理您的最低还款额0元时间和资金都没问题,对吗?|/home/Records/23/round-5.wav",
+                "银行稍后安排工作人员查账,建议您尽快处理欠款,不打扰您了,再见。hangupCall|/home/Records/23/round-6.wav"
+        };
+        JSONObject result = new JSONObject();
+        result.put("code", 200);
+        JSONObject data = new JSONObject();
+        JSONArray choices = new JSONArray();
+        JSONObject delta = new JSONObject();
+        String[] rspData = rspDatas[rounds].split("\\|");
+        delta.put("content", rspData[0].trim());
+        delta.put("wavFilePath", rspData[1].trim());
+        JSONObject choices0 = new JSONObject();
+        choices0.put("delta", delta);
+        choices.add(choices0);
+        data.put("choices", choices);
+        result.put("data", data);
+        return result;
     }
 }

+ 128 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcAsrLanguagesController.java

@@ -0,0 +1,128 @@
+package com.ruoyi.aicall.controller;
+
+import java.util.List;
+
+import com.ruoyi.aicall.domain.CcAsrLanguages;
+import com.ruoyi.aicall.service.ICcAsrLanguagesService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * ASR语种及模型配置Controller
+ * 
+ * @author ruoyi
+ * @date 2026-04-08
+ */
+@Controller
+@RequestMapping("/system/languages")
+public class CcAsrLanguagesController extends BaseController
+{
+    private String prefix = "system/languages";
+
+    @Autowired
+    private ICcAsrLanguagesService ccAsrLanguagesService;
+
+    @RequiresPermissions("system:languages:view")
+    @GetMapping()
+    public String languages()
+    {
+        return prefix + "/languages";
+    }
+
+    /**
+     * 查询ASR语种及模型配置列表
+     */
+    @RequiresPermissions("system:languages:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(CcAsrLanguages ccAsrLanguages)
+    {
+        startPage();
+        List<CcAsrLanguages> list = ccAsrLanguagesService.selectCcAsrLanguagesList(ccAsrLanguages);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出ASR语种及模型配置列表
+     */
+    @RequiresPermissions("system:languages:export")
+    @Log(title = "ASR语种及模型配置", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(CcAsrLanguages ccAsrLanguages)
+    {
+        List<CcAsrLanguages> list = ccAsrLanguagesService.selectCcAsrLanguagesList(ccAsrLanguages);
+        ExcelUtil<CcAsrLanguages> util = new ExcelUtil<CcAsrLanguages>(CcAsrLanguages.class);
+        return util.exportExcel(list, "ASR语种及模型配置数据");
+    }
+
+    /**
+     * 新增ASR语种及模型配置
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存ASR语种及模型配置
+     */
+    @RequiresPermissions("system:languages:add")
+    @Log(title = "ASR语种及模型配置", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(CcAsrLanguages ccAsrLanguages)
+    {
+        return toAjax(ccAsrLanguagesService.insertCcAsrLanguages(ccAsrLanguages));
+    }
+
+    /**
+     * 修改ASR语种及模型配置
+     */
+    @RequiresPermissions("system:languages:edit")
+    @GetMapping("/edit/{id}")
+    public String edit(@PathVariable("id") Long id, ModelMap mmap)
+    {
+        CcAsrLanguages ccAsrLanguages = ccAsrLanguagesService.selectCcAsrLanguagesById(id);
+        mmap.put("ccAsrLanguages", ccAsrLanguages);
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存ASR语种及模型配置
+     */
+    @RequiresPermissions("system:languages:edit")
+    @Log(title = "ASR语种及模型配置", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(CcAsrLanguages ccAsrLanguages)
+    {
+        return toAjax(ccAsrLanguagesService.updateCcAsrLanguages(ccAsrLanguages));
+    }
+
+    /**
+     * 删除ASR语种及模型配置
+     */
+    @RequiresPermissions("system:languages:remove")
+    @Log(title = "ASR语种及模型配置", businessType = BusinessType.DELETE)
+    @PostMapping( "/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(ccAsrLanguagesService.deleteCcAsrLanguagesByIds(ids));
+    }
+}

+ 40 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcCallTaskController.java

@@ -172,6 +172,27 @@ public class CcCallTaskController extends BaseController
             aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
             ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
         }
+        if (StringUtils.isBlank(ccCallTask.getVoiceSource())) {
+            ccCallTask.setVoiceSource("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getVoiceCode())) {
+            ccCallTask.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getAsrProvider())) {
+            ccCallTask.setAsrProvider("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getAsrLanguageCode())) {
+            ccCallTask.setAsrLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccCallTask.getTtsLanguageCode())) {
+            ccCallTask.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccCallTask.getAsrModels())) {
+            ccCallTask.setAsrModels("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getTtsModels())) {
+            ccCallTask.setTtsModels("");
+        }
 
         return toAjax(ccCallTaskService.insertCcCallTask(ccCallTask));
     }
@@ -232,6 +253,24 @@ public class CcCallTaskController extends BaseController
             aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
             ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
         }
+        if (StringUtils.isBlank(ccCallTask.getVoiceSource())) {
+            ccCallTask.setVoiceSource("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getVoiceCode())) {
+            ccCallTask.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getAsrLanguageCode())) {
+            ccCallTask.setAsrLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccCallTask.getTtsLanguageCode())) {
+            ccCallTask.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccCallTask.getAsrModels())) {
+            ccCallTask.setAsrModels("");
+        }
+        if (StringUtils.isBlank(ccCallTask.getTtsModels())) {
+            ccCallTask.setTtsModels("");
+        }
         return toAjax(ccCallTaskService.updateCcCallTask(ccCallTask));
     }
 
@@ -426,6 +465,7 @@ public class CcCallTaskController extends BaseController
                             } else {
                                 bizJson.put("tailNum", phoneNumber);
                             }
+                            bizJson.put("phoneNum", phoneNumber);
                             callPhone.setBizJson(JSONObject.toJSONString(bizJson));
                             phoneList.add(callPhone);
                             phoneMap.put(phoneNumber, rowNum);

+ 42 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcInboundLlmAccountController.java

@@ -155,6 +155,27 @@ public class CcInboundLlmAccountController extends BaseController
             ccInboundLlmAccount.setAiTransferData("");
             ccInboundLlmAccount.setLlmAccountId(-1);
         }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getVoiceSource())) {
+            ccInboundLlmAccount.setVoiceSource("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getVoiceCode())) {
+            ccInboundLlmAccount.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrProvider())) {
+            ccInboundLlmAccount.setAsrProvider("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrLanguageCode())) {
+            ccInboundLlmAccount.setAsrLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getTtsLanguageCode())) {
+            ccInboundLlmAccount.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrModels())) {
+            ccInboundLlmAccount.setAsrModels("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getTtsModels())) {
+            ccInboundLlmAccount.setTtsModels("");
+        }
         return toAjax(ccInboundLlmAccountService.insertCcInboundLlmAccount(ccInboundLlmAccount));
     }
 
@@ -227,6 +248,27 @@ public class CcInboundLlmAccountController extends BaseController
             ccInboundLlmAccount.setAiTransferData("");
             ccInboundLlmAccount.setLlmAccountId(-1);
         }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getVoiceSource())) {
+            ccInboundLlmAccount.setVoiceSource("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getVoiceCode())) {
+            ccInboundLlmAccount.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrProvider())) {
+            ccInboundLlmAccount.setAsrProvider("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrLanguageCode())) {
+            ccInboundLlmAccount.setAsrLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getTtsLanguageCode())) {
+            ccInboundLlmAccount.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getAsrModels())) {
+            ccInboundLlmAccount.setAsrModels("");
+        }
+        if (StringUtils.isBlank(ccInboundLlmAccount.getTtsModels())) {
+            ccInboundLlmAccount.setTtsModels("");
+        }
         return toAjax(ccInboundLlmAccountService.updateCcInboundLlmAccount(ccInboundLlmAccount));
     }
 

+ 20 - 1
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcLlmAgentAccountController.java

@@ -15,6 +15,7 @@ import com.ruoyi.cc.utils.LlmAccountParser;
 import com.ruoyi.common.utils.CommonUtils;
 import com.ruoyi.common.utils.ExceptionUtil;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
 import com.sun.org.apache.xpath.internal.operations.Bool;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -111,7 +112,9 @@ public class CcLlmAgentAccountController extends BaseController
         CcLlmAgentAccount ccLlmAgentAccount = new CcLlmAgentAccount();
         ccLlmAgentAccount.setInterruptIgnoreKeywords(ccParamsService.getParamValueByCode("default_interrupt_ignore_keywords", ""));
         ccLlmAgentAccount.setInterruptFlag(0);
-        ccLlmAgentAccount.setAccountJson("{}");
+        JSONObject accountJson = new JSONObject();
+        accountJson.put("rootId", UuidGenerator.GetOneUuid());
+        ccLlmAgentAccount.setAccountJson(JSONObject.toJSONString(accountJson));
         mmap.put("ccLlmAgentAccount", ccLlmAgentAccount);
         return prefix + "/add";
     }
@@ -158,6 +161,22 @@ public class CcLlmAgentAccountController extends BaseController
                 accountJson.put(key, CommonUtils.maskStringUtil(accountJson.getString(key)));
             }
         }
+        if (StringUtils.isBlank(accountJson.getString("rootId"))) {
+            accountJson.put("rootId", UuidGenerator.GetOneUuid());
+        }
+        String recordingPath = ccParamsService.getParamValueByCode("recording_path", "/home/Records/");
+        if (StringUtils.isNotEmpty(accountJson.getString("openingRemarksWav"))) {
+            accountJson.put("openingRemarksFileUrl", "recordings/files?filename=" +accountJson.getString("openingRemarksWav").replace(recordingPath, ""));
+        }
+        if (StringUtils.isNotEmpty(accountJson.getString("customerNoVoiceTipsWav"))) {
+            accountJson.put("customerNoVoiceTipsFileUrl", "recordings/files?filename=" +accountJson.getString("customerNoVoiceTipsWav").replace(recordingPath, ""));
+        }
+        if (StringUtils.isNotEmpty(accountJson.getString("hangupTipsWav"))) {
+            accountJson.put("hangupTipsFileUrl", "recordings/files?filename=" +accountJson.getString("hangupTipsWav").replace(recordingPath, ""));
+        }
+        if (StringUtils.isNotEmpty(accountJson.getString("transferToAgentTipsWav"))) {
+            accountJson.put("transferToAgentTipsFileUrl", "recordings/files?filename=" +accountJson.getString("transferToAgentTipsWav").replace(recordingPath, ""));
+        }
         ccLlmAgentAccount.setAccountJson(JSONObject.toJSONString(accountJson));
         mmap.put("ccLlmAgentAccount", ccLlmAgentAccount);
 

+ 142 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/CcTtsAliyunController.java

@@ -5,6 +5,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.aicall.domain.CcAsrLanguages;
+import com.ruoyi.aicall.service.ICcAsrLanguagesService;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.system.domain.SysConfig;
 import com.ruoyi.system.service.ISysConfigService;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -12,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Configurable;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.ModelMap;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.enums.BusinessType;
@@ -37,6 +42,8 @@ public class CcTtsAliyunController extends BaseController
     @Autowired
     private ICcTtsAliyunService ccTtsAliyunService;
     @Autowired
+    private ICcAsrLanguagesService asrLanguagesService;
+    @Autowired
     private ISysConfigService sysConfigService;
 
     @RequiresPermissions("aicall:ttsAliyun:view")
@@ -215,4 +222,139 @@ public class CcTtsAliyunController extends BaseController
         }
         return AjaxResult.success(providerMap);
     }
+
+
+
+    /**
+     * 根据asr厂商获取语言
+     */
+    @GetMapping("/asr/language")
+    @ResponseBody
+    public AjaxResult getAsrLanguages(@RequestParam String asrProvider)
+    {
+        List<JSONObject> languageList = new ArrayList<>();
+        Map<String, String> languageCodeMap = new HashMap<>();
+        List<CcAsrLanguages> asrLanguagesList = asrLanguagesService.selectCcAsrLanguagesList(new CcAsrLanguages().setAsrProvider(asrProvider));
+        for (CcAsrLanguages asrLanguages: asrLanguagesList) {
+            if (StringUtils.isNotEmpty(asrLanguages.getLanguageCode())
+                    && StringUtils.isEmpty(languageCodeMap.get(asrLanguages.getLanguageCode()))) {
+                languageCodeMap.put(asrLanguages.getLanguageCode(), asrLanguages.getLanguageName());
+            }
+        }
+        if (CollectionUtils.isEmpty(languageCodeMap)) {
+            languageCodeMap.put("zh-CN", "中文");
+        }
+        for (String key: languageCodeMap.keySet()) {
+            JSONObject obj = new JSONObject();
+            obj.put("key", key);
+            obj.put("value", languageCodeMap.get(key));
+            languageList.add(obj);
+        }
+        return AjaxResult.success(languageList);
+    }
+    /**
+     * 根据tts厂商获取支持语言
+     */
+    @GetMapping("/tts/language")
+    @ResponseBody
+    public AjaxResult getTtsLanguages(@RequestParam String voiceSource)
+    {
+        List<JSONObject> languageList = new ArrayList<>();
+        Map<String, String> languageCodeMap = new HashMap<>();
+        List<CcTtsAliyun> ccTtsAliyunList = ccTtsAliyunService.selectCcTtsAliyunList(new CcTtsAliyun().setVoiceSource(voiceSource));
+        for (CcTtsAliyun ccTtsAliyun: ccTtsAliyunList) {
+            if (StringUtils.isNotEmpty(ccTtsAliyun.getLanguageCode())
+                    && StringUtils.isEmpty(languageCodeMap.get(ccTtsAliyun.getLanguageCode()))) {
+                languageCodeMap.put(ccTtsAliyun.getLanguageCode(), ccTtsAliyun.getLanguageName());
+            }
+        }
+        if (CollectionUtils.isEmpty(languageCodeMap)) {
+            languageCodeMap.put("zh-CN", "中文");
+        }
+        for (String key: languageCodeMap.keySet()) {
+            JSONObject obj = new JSONObject();
+            obj.put("key", key);
+            obj.put("value", languageCodeMap.get(key));
+            languageList.add(obj);
+        }
+        return AjaxResult.success(languageList);
+    }
+
+
+    /**
+     * 根据provider和语言查询音色列表
+     */
+    @GetMapping("/getByLanguageCode")
+    @ResponseBody
+    public AjaxResult getByLanguageCode(@RequestParam String voiceSource,
+                                        @RequestParam(value = "ttsLanguageCode", required = false) String ttsLanguageCode,
+                                        @RequestParam(value = "ttsModels", required = false) String ttsModels)
+    {
+        CcTtsAliyun ccTtsAliyun = new CcTtsAliyun();
+        ccTtsAliyun.setVoiceSource(voiceSource);
+        if (StringUtils.isNotEmpty(ttsLanguageCode)) {
+            ccTtsAliyun.setLanguageCode(ttsLanguageCode);
+        }
+        if (StringUtils.isNotEmpty(ttsModels)) {
+            ccTtsAliyun.setTtsModels(ttsModels);
+        }
+        List<CcTtsAliyun> list = ccTtsAliyunService.selectCcTtsAliyunList(ccTtsAliyun);
+        return AjaxResult.success(list);
+    }
+
+
+    /**
+     * 根据asr厂商和语言获取asr模型列表
+     */
+    @GetMapping("/asr/models")
+    @ResponseBody
+    public AjaxResult getAsrModels(@RequestParam String asrProvider, @RequestParam String asrLanguageCode)
+    {
+        List<JSONObject> modelsList = new ArrayList<>();
+        Map<String, String> asrModelsMap = new HashMap<>();
+        List<CcAsrLanguages> asrLanguagesList = asrLanguagesService.selectCcAsrLanguagesList(new CcAsrLanguages().setAsrProvider(asrProvider).setLanguageCode(asrLanguageCode));
+        for (CcAsrLanguages asrLanguages: asrLanguagesList) {
+            if (StringUtils.isNotEmpty(asrLanguages.getModels())
+                    && StringUtils.isEmpty(asrModelsMap.get(asrLanguages.getModels()))) {
+                asrModelsMap.put(asrLanguages.getModels(), asrLanguages.getModels());
+            }
+        }
+        if (CollectionUtils.isEmpty(asrModelsMap)) {
+            asrModelsMap.put("", "");
+        }
+        for (String key: asrModelsMap.keySet()) {
+            JSONObject obj = new JSONObject();
+            obj.put("key", key);
+            obj.put("value", asrModelsMap.get(key));
+            modelsList.add(obj);
+        }
+        return AjaxResult.success(modelsList);
+    }
+    /**
+     * 根据tts厂商和语言获取asr模型列表
+     */
+    @GetMapping("/tts/models")
+    @ResponseBody
+    public AjaxResult getTtsModels(@RequestParam String voiceSource, @RequestParam String ttsLanguageCode)
+    {
+        List<JSONObject> modelsList = new ArrayList<>();
+        Map<String, String> ttsModelMap = new HashMap<>();
+        List<CcTtsAliyun> ccTtsAliyunList = ccTtsAliyunService.selectCcTtsAliyunList(new CcTtsAliyun().setVoiceSource(voiceSource).setLanguageCode(ttsLanguageCode));
+        for (CcTtsAliyun ccTtsAliyun: ccTtsAliyunList) {
+            if (StringUtils.isNotEmpty(ccTtsAliyun.getTtsModels())
+                    && StringUtils.isEmpty(ttsModelMap.get(ccTtsAliyun.getTtsModels()))) {
+                ttsModelMap.put(ccTtsAliyun.getTtsModels(), ccTtsAliyun.getTtsModels());
+            }
+        }
+        if (CollectionUtils.isEmpty(ttsModelMap)) {
+            ttsModelMap.put("", "");
+        }
+        for (String key: ttsModelMap.keySet()) {
+            JSONObject obj = new JSONObject();
+            obj.put("key", key);
+            obj.put("value", ttsModelMap.get(key));
+            modelsList.add(obj);
+        }
+        return AjaxResult.success(modelsList);
+    }
 }

+ 39 - 39
ruoyi-admin/src/main/java/com/ruoyi/aicall/controller/DoubaoVclController.java

@@ -56,44 +56,44 @@ public class DoubaoVclController extends BaseController {
     @Autowired
     private ICcParamsService ccParamsService;
 
-    /**
-     * Add/update tts speakers
-     * @param nameParam
-     * @param speakerId
-     */
-    private synchronized void addSpeakerId(String nameParam, String speakerId) {
-        CcTtsAliyun ttsSpeaker = ttsAliyunService.selectCcTtsAliyunByVoiceCode(speakerId);
-        String name = nameParam.replace("'", "").replace(" ", "");
-        if (name.length() > 20) {
-            name = name.substring(0, 20);
-        }
-
-        boolean update = false;
-        if (null == ttsSpeaker) {
-            ttsSpeaker = new CcTtsAliyun();
-        } else {
-            update = true;
-        }
-
-        ttsSpeaker.setVoiceCode(speakerId);
-        ttsSpeaker.setVoiceEnabled(1);
-        ttsSpeaker.setVoiceName(String.format("[%s] - %s", speakerId, name));
-        ttsSpeaker.setVoiceSource("doubao_vcl_tts");
-        ttsSpeaker.setPriority(0);
-        ttsSpeaker.setProvider("doubao");
-
-        try {
-            if (update) {
-                ttsAliyunService.updateCcTtsAliyun(ttsSpeaker);
-            } else {
-                ttsAliyunService.insertCcTtsAliyun(ttsSpeaker);
-            }
-            logger.info("save doubaovcl speakerId succeed. {} {}", name, speakerId);
-        } catch (Throwable e) {
-            logger.error("save doubaovcl speakerId error: {} {}", e.toString(),
-                    CommonUtils.getStackTraceString(e.getStackTrace()));
-        }
-    }
+//    /**
+//     * Add/update tts speakers
+//     * @param nameParam
+//     * @param speakerId
+//     */
+//    private synchronized void addSpeakerId(String nameParam, String speakerId) {
+//        CcTtsAliyun ttsSpeaker = ttsAliyunService.selectCcTtsAliyunByVoiceCode(speakerId);
+//        String name = nameParam.replace("'", "").replace(" ", "");
+//        if (name.length() > 20) {
+//            name = name.substring(0, 20);
+//        }
+//
+//        boolean update = false;
+//        if (null == ttsSpeaker) {
+//            ttsSpeaker = new CcTtsAliyun();
+//        } else {
+//            update = true;
+//        }
+//
+//        ttsSpeaker.setVoiceCode(speakerId);
+//        ttsSpeaker.setVoiceEnabled(1);
+//        ttsSpeaker.setVoiceName(String.format("[%s] - %s", speakerId, name));
+//        ttsSpeaker.setVoiceSource("doubao_vcl_tts");
+//        ttsSpeaker.setPriority(0);
+//        ttsSpeaker.setProvider("doubao");
+//
+//        try {
+//            if (update) {
+//                ttsAliyunService.updateCcTtsAliyun(ttsSpeaker);
+//            } else {
+//                ttsAliyunService.insertCcTtsAliyun(ttsSpeaker);
+//            }
+//            logger.info("save doubaovcl speakerId succeed. {} {}", name, speakerId);
+//        } catch (Throwable e) {
+//            logger.error("save doubaovcl speakerId error: {} {}", e.toString(),
+//                    CommonUtils.getStackTraceString(e.getStackTrace()));
+//        }
+//    }
 
     @RequiresPermissions("aicall:doubaovcl:view")
     @GetMapping("voiceclone")
@@ -283,7 +283,7 @@ public class DoubaoVclController extends BaseController {
                 JSONObject jsonObject = JSON.parseObject(response);
                 int statusCode = jsonObject.getJSONObject("BaseResp").getInteger("StatusCode");
                 if (statusCode == 0) {
-                    addSpeakerId(voiceName, spkId);
+//                    addSpeakerId(voiceName, spkId);
                     boolean  modelReady = getStatus(appid, token, spkId);
                     String tips = modelReady ? MessageUtils.message("doubaovcl.admin.model-status-ready") :
                             MessageUtils.message("doubaovcl.admin.model-status-not-ready");

+ 41 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcAsrLanguages.java

@@ -0,0 +1,41 @@
+package com.ruoyi.aicall.domain;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.io.Serializable;
+
+/**
+ * ASR语种及模型配置对象 cc_asr_languages
+ * 
+ * @author ruoyi
+ * @date 2026-04-08
+ */
+@Data
+@Accessors(chain = true)
+public class CcAsrLanguages implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** id */
+    private Long id;
+
+    /** provider of asr */
+    @Excel(name = "provider of asr")
+    private String asrProvider;
+
+    /** models of asr */
+    @Excel(name = "models of asr")
+    private String models;
+
+    /** language code */
+    @Excel(name = "language code")
+    private String languageCode;
+
+    /** language name */
+    @Excel(name = "language name")
+    private String languageName;
+}

+ 2 - 55
ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcCallPhone.java

@@ -162,6 +162,7 @@ public class CcCallPhone implements Serializable {
     private Map<String, Object> params = new HashMap<>();
 
     /** 批次名称 */
+    @Excel(name = "批次名称", readConverterExp = "批次名称")
     private String batchName;
 
     /** manual agent answered time. */
@@ -170,59 +171,5 @@ public class CcCallPhone implements Serializable {
     /** The duration of the manual agent service time. */
     private String manualAnsweredTimeLen;
 
-    private String callstatusName;
-    private String calloutTimeStr;
-    private String answeredTimeStr;
-    private String callEndTimeStr;
-    private String timeLenSec;
-
-    /**
-     * 根据呼叫状态码获取状态名称
-     * @param status 状态码
-     * @return 状态名称
-     */
-    public static String getCallStatusName(Integer status) {
-        if (status == null) {
-            return "未知";
-        }
-        switch (status) {
-            case 0:
-                return "未拨打";
-            case 1:
-                return "已进入呼叫队列";
-            case 2:
-                return "正在拨号";
-            case 3:
-            case 30:
-                return "未接通";
-            case 4:
-                return "已接通";
-            case 5:
-                return "通话中断";
-            case 6:
-                return "成功转人工或 AI";
-            case 7:
-                return "线路故障";
-            case 31:
-                return "客户正在通话中";
-            case 32:
-                return "关机";
-            case 33:
-                return "空号";
-            case 34:
-                return "无人接听";
-            case 35:
-                return "停机";
-            case 36:
-                return "网络忙";
-            case 37:
-                return "语音助手";
-            case 38:
-                return "暂时无法接通";
-            case 39:
-                return "呼叫限制";
-            default:
-                return "未知状态";
-        }
-    }
+
 }

+ 12 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcCallTask.java

@@ -162,4 +162,16 @@ public class CcCallTask implements Serializable {
     /** 是否允许删除 */
     private Integer allowDel;
 
+    /** Language of the asr */
+    private String asrLanguageCode;
+
+    /** Language of the tts */
+    private String ttsLanguageCode;
+
+    /** Models of the asr */
+    private String asrModels;
+
+    /** Models of the voice */
+    private String ttsModels;
+
 }

+ 12 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcInboundLlmAccount.java

@@ -86,4 +86,16 @@ public class CcInboundLlmAccount implements Serializable {
     /** satisfSurveyIvrId */
     private String satisfSurveyIvrId;
 
+    /** Language of the asr */
+    private String asrLanguageCode;
+
+    /** Language of the tts */
+    private String ttsLanguageCode;
+
+    /** Models of the asr */
+    private String asrModels;
+
+    /** Models of the voice */
+    private String ttsModels;
+
 }

+ 9 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/domain/CcTtsAliyun.java

@@ -47,4 +47,13 @@ public class CcTtsAliyun implements Serializable {
     /** provider */
     @Excel(name = "provider")
     private String provider;
+
+    /** Language of the voice */
+    private String languageCode;
+
+    /** Language name of the voice */
+    private String languageName;
+
+    /** Models of the voice */
+    private String ttsModels;
 }

+ 6 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/ILlmCapability.java

@@ -3,6 +3,10 @@ package com.ruoyi.aicall.llm;
 import com.alibaba.fastjson.JSONArray;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
 import com.ruoyi.aicall.llm.model.AccountBaseEntity;
+import com.ruoyi.aicall.model.ChatRequest;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
 
 public interface ILlmCapability {
 
@@ -10,6 +14,8 @@ public interface ILlmCapability {
 
     void setAccount(AccountBaseEntity llmAccount);
 
+    void getStreamingChatContent(SseEmitter emitter, ChatRequest chatRequest) throws IOException;
+
     AccountBaseEntity getAccount();
 
 }

+ 207 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/Coze.java

@@ -1,15 +1,222 @@
 package com.ruoyi.aicall.llm.impl;
 
 import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.coze.openapi.client.auth.OAuthToken;
+import com.coze.openapi.client.chat.CreateChatReq;
+import com.coze.openapi.client.chat.model.ChatEvent;
+import com.coze.openapi.client.chat.model.ChatEventType;
+import com.coze.openapi.client.connversations.message.model.Message;
+import com.coze.openapi.service.auth.JWTOAuthClient;
+import com.coze.openapi.service.auth.TokenAuth;
+import com.coze.openapi.service.service.CozeAPI;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
 import com.ruoyi.aicall.llm.AbstractLlmCapability;
+import com.ruoyi.aicall.llm.model.CozeAccount;
+import com.ruoyi.aicall.model.ChatRequest;
+import com.ruoyi.common.utils.CommonUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
+import io.reactivex.Flowable;
+import io.reactivex.schedulers.Schedulers;
+import link.thingscloud.freeswitch.esl.util.CurrentTimeMillisClock;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @Slf4j
 public class Coze extends AbstractLlmCapability {
+
+    private String conversationId = "";
+    private static final String COZE_TOKEN_TYPE_PAT = "pat";
+    private static final String COZE_TOKEN_TYPE_OAUTH = "oauth";
+    private String token = "";
+    private int expireTime = 0;
+
     @Override
     public String getIntentionByTips(CcLlmAgentAccount ccLlmAgentAccount, JSONArray dialogueContents) {
         String intentionTips = ccLlmAgentAccount.getIntentionTips();
         return null;
     }
+
+    @Override
+    public void getStreamingChatContent(SseEmitter emitter, ChatRequest chatRequest) throws IOException {
+
+        String requestId = chatRequest.getRequestId();
+        String question = chatRequest.getQuestion();
+        if (CollectionUtils.isEmpty(chatRequest.getMessages())) {
+            chatRequest.setMessages(new ArrayList<>());
+        }
+        String url = getAccount().serverUrl;
+        if(!url.endsWith("/")){
+            url = url + "/";
+        }
+        TokenAuth authCli = new TokenAuth(getToken(requestId));
+        // Init the Coze client through the access_token.
+        CozeAPI coze =
+                new CozeAPI.Builder()
+                        .baseURL(url)
+                        .auth(authCli)
+                        .readTimeout(10000)
+                        .build();
+
+        /*
+         * Step one, create chat
+         * Call the coze.chat().stream() method to create a chat. The create method is a streaming
+         * chat and will return a Flowable ChatEvent. Developers should iterate the iterator to get
+         * chat event and handle them.
+         * */
+        String botID = ((CozeAccount)getAccount()).getBotId();
+        CreateChatReq req =
+                CreateChatReq.builder()
+                        .botID(botID)
+                        .userID(requestId)
+                        .messages(Collections.singletonList(Message.buildUserQuestionText(question)))
+                        .build();
+        if(!StringUtils.isEmpty(conversationId)) {
+            req.setConversationID(conversationId);
+        }
+
+        JSONObject userMessage = new JSONObject();
+        userMessage.put("role",  "user");
+        userMessage.put("content",  question);
+        userMessage.put("content_type", "text");
+        chatRequest.getMessages().add(userMessage);
+        Flowable<ChatEvent> resp = coze.chat().stream(req);
+
+        StringBuilder responseBuilder = new StringBuilder();
+        resp.subscribeOn(Schedulers.io())
+
+                .subscribe(
+                        event -> {
+                            if (ChatEventType.CONVERSATION_MESSAGE_DELTA.equals(event.getEvent())) {
+                                System.out.print(event.getMessage().getContent());
+
+                                if(StringUtils.isEmpty(conversationId)){
+                                    conversationId = event.getMessage().getConversationId();
+                                    log.info("{} coze chat conversation_id = {}", requestId, conversationId);
+                                }
+                                String tmpText = event.getMessage().getContent().trim();
+                                responseBuilder.append(tmpText);
+                                JSONObject rsponseData = new JSONObject();
+                                rsponseData.put("requestId", requestId);
+                                rsponseData.put("code", "200");
+                                rsponseData.put("content", tmpText);
+                                chatRequest.setConversationId(conversationId);
+                                rsponseData.put("messages", chatRequest.getMessages());
+                                rsponseData.put("conversationId", chatRequest.getConversationId());
+                                rsponseData.put("msgType", "streamMsgSlice");
+                                log.info(JSONObject.toJSONString(rsponseData));
+                                emitter.send(SseEmitter.event()
+                                        .name("streamMsgSlice")
+                                        .data(JSONObject.toJSONString(rsponseData)));
+
+                            }
+                        },
+                        throwable -> {
+                            System.err.println(": " + throwable.getMessage());
+                            log.error("{} coze error occurred {} {}", requestId, throwable.toString(),
+                                    CommonUtils.getStackTraceString(throwable.getStackTrace()));
+                            release();
+                        },
+                        () -> {
+                            log.info("{} coze talk done.", requestId);
+                            release();
+                        });
+
+        acquire();
+        coze.shutdownExecutor();
+
+
+        JSONObject rsponseData = new JSONObject();
+        rsponseData.put("requestId", requestId);
+        rsponseData.put("code", "200");
+        rsponseData.put("content", responseBuilder.toString());
+        JSONObject assistantMessage = new JSONObject();
+        assistantMessage.put("role",  "assistant");
+        assistantMessage.put("content",  responseBuilder.toString());
+        assistantMessage.put("content_type", "text");
+        chatRequest.getMessages().add(assistantMessage);
+        chatRequest.setConversationId(conversationId);
+        rsponseData.put("messages", chatRequest.getMessages());
+        rsponseData.put("conversationId", chatRequest.getConversationId());
+        rsponseData.put("msgType", "message");
+        log.info(JSONObject.toJSONString(rsponseData));
+        emitter.send(SseEmitter.event()
+                .name("message")
+                .data(JSONObject.toJSONString(rsponseData)));
+    }
+
+
+    private String getToken(String requestId){
+        String cozeTokenType =  ((CozeAccount)getAccount()).getTokenType();
+        if(COZE_TOKEN_TYPE_PAT.equalsIgnoreCase(cozeTokenType)){
+            return ((CozeAccount)getAccount()).getPatToken();
+        }
+
+        String  cozeAPIBase = "https://api.coze.cn";
+        String jwtOauthClientID = ((CozeAccount)getAccount()).getOauthClientId();
+        String jwtOauthPrivateKey = ((CozeAccount)getAccount()).getOauthPrivateKey();
+        String jwtOauthPublicKeyID = ((CozeAccount)getAccount()).getOauthPublicKeyId();
+        JWTOAuthClient oauth = null;
+
+        try {
+            oauth = new JWTOAuthClient.JWTOAuthBuilder()
+                    .clientID(jwtOauthClientID)
+                    .privateKey(jwtOauthPrivateKey)
+                    .publicKey(jwtOauthPublicKeyID)
+                    .baseURL(cozeAPIBase).ttl(24 * 3600)
+                    .build();
+        } catch (Throwable e) {
+            log.error("{} coze getToken error: {} {} ", requestId, e.toString(), CommonUtils.getStackTraceString(e.getStackTrace()));
+            return "";
+        }
+
+        Long expiredMillsLeft = expireTime - (CurrentTimeMillisClock.now()/1000);
+        //过期前1小时
+        if (StringUtils.isEmpty(token) || expiredMillsLeft < 3600 || expireTime == 0) {
+            synchronized (jwtOauthClientID.intern()) {
+                if (StringUtils.isEmpty(token) || expiredMillsLeft < 3600 || expireTime == 0) {
+                    OAuthToken aAuthToken = null;
+                    try {
+                        aAuthToken = oauth.getAccessToken();
+                        expireTime = aAuthToken.getExpiresIn();
+                        token = aAuthToken.getAccessToken();
+                        log.info("{} successfully getAccessToken={} , expireTime={} ",
+                                requestId, aAuthToken.getAccessToken().substring(0, 30) + "***", aAuthToken.getExpiresIn());
+                    } catch (Throwable e) {
+                        log.error("{} coze getAccessToken error: {} ", requestId, CommonUtils.getStackTraceString(e.getStackTrace()));
+                        return "";
+                    }
+                }
+            }
+        }
+        return token;
+    }
+
+
+    private  final Object waitHandle = new Object();
+    private  void release(){
+        synchronized (waitHandle) {
+            waitHandle.notify();
+        }
+    }
+    private  void acquire(){
+        try {
+            synchronized (waitHandle) {
+                waitHandle.wait();
+            }
+        }
+        catch (Throwable throwable){
+            log.error("acquire error: {} {} ", throwable.toString() ,
+                    CommonUtils.getStackTraceString(throwable.getStackTrace()));
+        }
+    }
 }

+ 131 - 1
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/DeepSeekChat.java

@@ -4,13 +4,21 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
+import com.ruoyi.aicall.domain.CcLlmKb;
 import com.ruoyi.aicall.llm.AbstractLlmCapability;
 import com.ruoyi.aicall.llm.model.LlmAccount;
+import com.ruoyi.aicall.model.ChatRequest;
+import com.ruoyi.aicall.service.ICcLlmKbService;
 import com.ruoyi.common.utils.ExceptionUtil;
-import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
 import okio.BufferedSource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpStatus;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -64,4 +72,126 @@ public class DeepSeekChat extends AbstractLlmCapability {
         }
         return null;
     }
+
+    @Override
+    public  void getStreamingChatContent(SseEmitter emitter, ChatRequest chatRequest) throws IOException {
+
+        String requestId = chatRequest.getRequestId();
+        String llmTips = ((LlmAccount) getAccount()).getLlmTips();
+        Long catId = llmAccountInfo.kbCatId;
+        String topicsVar = "${kbTopicList}";
+        if(catId != -1) {
+            if (llmTips.contains(topicsVar)) {
+                List<CcLlmKb> kbList = SpringUtils.getBean(ICcLlmKbService.class).selectCcLlmKbList(new CcLlmKb().setCatId(catId));
+                StringBuilder topics = new StringBuilder();
+                for (CcLlmKb llmKb : kbList) {
+                    topics.append("*").append(llmKb.getTitle()).append("\r\n");
+                }
+                topics.append("\r\n");
+                llmTips = llmTips.replace(topicsVar, topics.toString());
+                log.info("{} topic list: {}", requestId, topics.toString());
+            } else {
+                log.warn("{} {} tag not found, the knowledge base function will be unavailable. ", requestId, topicsVar);
+            }
+        }else {
+            log.warn("{} The current model {} doesn’t have a knowledge base linked to it. ", requestId, ((LlmAccount) getAccount()).getModelName());
+        }
+
+        String tips = llmTips  + "\r\n\r\n" + ((LlmAccount) getAccount()).getFaqContext();
+
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("model", ((LlmAccount)getAccount()).getModelName());
+        requestBody.put("stream", true);
+        JSONArray messagesArray = new JSONArray();
+        JSONObject systemMessage = new JSONObject();
+        systemMessage.put("role",  "system");
+        systemMessage.put("content", tips);
+        systemMessage.put("content_type", "text");
+        messagesArray.add(systemMessage);
+        if (CollectionUtils.isEmpty(chatRequest.getMessages())) {
+            chatRequest.setMessages(new ArrayList<>());
+        }
+        JSONObject userMessage = new JSONObject();
+        userMessage.put("role",  "user");
+        userMessage.put("content", chatRequest.getQuestion());
+        userMessage.put("content_type", "text");
+        chatRequest.getMessages().add(userMessage);
+        messagesArray.addAll(chatRequest.getMessages());
+        requestBody.put("messages", messagesArray);
+
+        RequestBody body = RequestBody.create(
+                MediaType.parse("application/json"),
+                requestBody.toJSONString()
+        );
+
+        Request request = new Request.Builder()
+                .url(getAccount().serverUrl)
+                .post(body)
+                .addHeader("Authorization", "Bearer " + ((LlmAccount)getAccount()).getApiKey())
+                .build();
+
+        try (Response response = CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                log.error("Model api error: http-code={}, msg={}, url={}",
+                        response.code(),
+                        response.message(),
+                        getAccount().serverUrl
+                );
+                if(response.code() == HttpStatus.SC_UNAUTHORIZED || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+                    throw new IOException("Unexpected code " + response);
+                }else{
+                    return ;
+                }
+            }
+
+            StringBuilder responseBuilder = new StringBuilder();
+            BufferedSource source = response.body().source();
+
+            while (!source.exhausted()) {
+                String line = source.readUtf8Line();
+                if (line != null && line.startsWith("data: ")) {
+                    String jsonData = line.substring(6).trim(); // 去掉 "data: " 前缀
+                    if (jsonData.equals("[DONE]")) {
+                        JSONObject rsponseData = new JSONObject();
+                        rsponseData.put("requestId", requestId);
+                        rsponseData.put("code", "200");
+                        rsponseData.put("content", responseBuilder.toString());
+                        JSONObject assistantMessage = new JSONObject();
+                        assistantMessage.put("role",  "assistant");
+                        assistantMessage.put("content",  responseBuilder.toString());
+                        assistantMessage.put("content_type", "text");
+                        chatRequest.getMessages().add(assistantMessage);
+                        rsponseData.put("messages", chatRequest.getMessages());
+                        rsponseData.put("msgType", "message");
+                        log.info(JSONObject.toJSONString(rsponseData));
+                        emitter.send(SseEmitter.event()
+                                .name("message")
+                                .data(JSONObject.toJSONString(rsponseData)));
+                        break; // 流式响应结束
+                    }
+
+                    JSONObject jsonResponse = JSON.parseObject(jsonData);
+                    JSONObject message = jsonResponse.getJSONArray("choices")
+                            .getJSONObject(0)
+                            .getJSONObject("delta"); // 注意:流式响应中消息在 "delta" 字段中
+
+                    if (message.containsKey("content")) {
+                        String speechContent = message.getString("content");
+                        responseBuilder.append(speechContent);
+                        JSONObject rsponseData = new JSONObject();
+                        rsponseData.put("requestId", requestId);
+                        rsponseData.put("code", "200");
+                        rsponseData.put("content", speechContent);
+                        rsponseData.put("messages", chatRequest.getMessages());
+                        rsponseData.put("msgType", "streamMsgSlice");
+                        log.info(JSONObject.toJSONString(rsponseData));
+                        emitter.send(SseEmitter.event()
+                                .name("streamMsgSlice")
+                                .data(JSONObject.toJSONString(rsponseData)));
+
+                    }
+                }
+            }
+        }
+    }
 }

+ 126 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/Dify.java

@@ -1,9 +1,24 @@
 package com.ruoyi.aicall.llm.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
 import com.ruoyi.aicall.llm.AbstractLlmCapability;
+import com.ruoyi.aicall.llm.model.LlmAccount;
+import com.ruoyi.aicall.model.ChatRequest;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
 import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okio.BufferedSource;
+import org.apache.http.HttpStatus;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
 
 @Slf4j
 public class Dify extends AbstractLlmCapability {
@@ -12,4 +27,115 @@ public class Dify extends AbstractLlmCapability {
         String intentionTips = ccLlmAgentAccount.getIntentionTips();
         return null;
     }
+
+    @Override
+    public void getStreamingChatContent(SseEmitter emitter, ChatRequest chatRequest) throws IOException {
+
+        String requestId = chatRequest.getRequestId();
+        String question = chatRequest.getQuestion();
+        String conversationId = chatRequest.getConversationId();
+
+        JSONObject userMessage = new JSONObject();
+        userMessage.put("role",  "user");
+        userMessage.put("content",  question);
+        userMessage.put("content_type", "text");
+        chatRequest.getMessages().add(userMessage);
+
+        JSONObject requestBody = new JSONObject();
+        JSONObject inputs = new JSONObject();
+        requestBody.put("inputs", inputs);
+        requestBody.put("query", question);
+        requestBody.put("response_mode", "streaming");
+        if (!StringUtils.isEmpty(conversationId)) {
+            requestBody.put("conversation_id", conversationId);
+            log.info("{} set Dify chat_id = {}", requestId, conversationId);
+        }
+        requestBody.put("user", requestId);
+
+        RequestBody body = RequestBody.create(
+                MediaType.parse("application/json"),
+                requestBody.toJSONString()
+        );
+
+        Request request = new Request.Builder()
+                .url(getAccount().serverUrl)
+                .post(body)
+                .addHeader("Authorization", "Bearer " +  ((LlmAccount)getAccount()).getApiKey())
+                .build();
+
+        try (Response response = CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                log.error("Dify api error: http-code={}, msg={}, url={}",
+                        response.code(),
+                        response.message(),
+                        getAccount().serverUrl
+                );
+                if(response.code() == HttpStatus.SC_UNAUTHORIZED || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+                    throw new IOException("Unexpected code " + response);
+                }else{
+                    return ;
+                }
+            }
+
+            BufferedSource source = response.body().source();
+            StringBuilder responseBuilder = new StringBuilder();
+
+//   data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " I", "created_at": 1679586595}
+            while (!source.exhausted()) {
+                String line = source.readUtf8Line();
+                if (line != null && line.startsWith("data: ")) {
+                    String jsonData = line.substring(6).trim(); // 去掉 "data: " 前缀
+                    JSONObject jsonResponse = JSON.parseObject(jsonData);
+                    if ("message".equalsIgnoreCase(jsonResponse.getString("event"))) {
+                        String conversation_id = jsonResponse.getString("conversation_id");
+                        if (!StringUtils.isEmpty(conversation_id)) {
+                            conversationId = conversation_id;
+                            log.info("{} successfully get Dify chat_id = {}", requestId, conversationId);
+                        }
+                        String speechContent = jsonResponse.getString("answer");
+                        if (StringUtils.isEmpty(speechContent)) {
+                            continue;
+                        }
+
+                        if (!StringUtils.isEmpty(speechContent)) {
+
+                            responseBuilder.append(speechContent);
+                            JSONObject rsponseData = new JSONObject();
+                            rsponseData.put("requestId", requestId);
+                            rsponseData.put("code", "200");
+                            rsponseData.put("content", speechContent);
+                            chatRequest.setConversationId(conversationId);
+                            rsponseData.put("messages", chatRequest.getMessages());
+                            rsponseData.put("conversationId", chatRequest.getConversationId());
+                            rsponseData.put("msgType", "streamMsgSlice");
+                            log.info(JSONObject.toJSONString(rsponseData));
+                            emitter.send(SseEmitter.event()
+                                    .name("streamMsgSlice")
+                                    .data(JSONObject.toJSONString(rsponseData)));
+
+                        }
+                    }
+                }
+            }
+
+
+            JSONObject rsponseData = new JSONObject();
+            rsponseData.put("requestId", requestId);
+            rsponseData.put("code", "200");
+            rsponseData.put("content", responseBuilder.toString());
+            JSONObject assistantMessage = new JSONObject();
+            assistantMessage.put("role",  "assistant");
+            assistantMessage.put("content",  responseBuilder.toString());
+            assistantMessage.put("content_type", "text");
+            chatRequest.getMessages().add(assistantMessage);
+            chatRequest.setConversationId(conversationId);
+            rsponseData.put("messages", chatRequest.getMessages());
+            rsponseData.put("conversationId", chatRequest.getConversationId());
+            rsponseData.put("msgType", "message");
+            log.info(JSONObject.toJSONString(rsponseData));
+            emitter.send(SseEmitter.event()
+                    .name("message")
+                    .data(JSONObject.toJSONString(rsponseData)));
+        }
+    }
 }

+ 9 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/impl/MaxKB.java

@@ -3,7 +3,11 @@ package com.ruoyi.aicall.llm.impl;
 import com.alibaba.fastjson.JSONArray;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
 import com.ruoyi.aicall.llm.AbstractLlmCapability;
+import com.ruoyi.aicall.model.ChatRequest;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
 
 @Slf4j
 public class MaxKB extends AbstractLlmCapability {
@@ -12,4 +16,9 @@ public class MaxKB extends AbstractLlmCapability {
         String intentionTips = ccLlmAgentAccount.getIntentionTips();
         return null;
     }
+
+    @Override
+    public void getStreamingChatContent(SseEmitter emitter, ChatRequest chatRequest) throws IOException {
+
+    }
 }

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/llm/model/AccountBaseEntity.java

@@ -46,4 +46,6 @@ package com.ruoyi.aicall.llm.model;
      *  List of keywords excluded from triggering speech interruption.
      */
     public String interruptIgnoreKeywords;
+
+    public Long kbCatId;
 }

+ 62 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/mapper/CcAsrLanguagesMapper.java

@@ -0,0 +1,62 @@
+package com.ruoyi.aicall.mapper;
+
+import com.ruoyi.aicall.domain.CcAsrLanguages;
+
+import java.util.List;
+
+/**
+ * ASR语种及模型配置Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2026-04-08
+ */
+public interface CcAsrLanguagesMapper 
+{
+    /**
+     * 查询ASR语种及模型配置
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return ASR语种及模型配置
+     */
+    public CcAsrLanguages selectCcAsrLanguagesById(Long id);
+
+    /**
+     * 查询ASR语种及模型配置列表
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return ASR语种及模型配置集合
+     */
+    public List<CcAsrLanguages> selectCcAsrLanguagesList(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 新增ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    public int insertCcAsrLanguages(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 修改ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    public int updateCcAsrLanguages(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 删除ASR语种及模型配置
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return 结果
+     */
+    public int deleteCcAsrLanguagesById(Long id);
+
+    /**
+     * 批量删除ASR语种及模型配置
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteCcAsrLanguagesByIds(String[] ids);
+}

+ 1 - 11
ruoyi-admin/src/main/java/com/ruoyi/aicall/mapper/CcCallPhoneMapper.java

@@ -2,8 +2,8 @@ package com.ruoyi.aicall.mapper;
 
 import com.ruoyi.aicall.domain.CcCallPhone;
 import com.ruoyi.aicall.model.CallTaskStatModel;
-import com.ruoyi.aicall.model.CommonPhoneModel;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
 
@@ -69,12 +69,6 @@ public interface CcCallPhoneMapper
      * @return
      */
     CallTaskStatModel statByBatchId(Long batchId);
-    /**
-     * 根据batchIds统计外呼数据
-     * @param batchIds
-     * @return
-     */
-    public List<CallTaskStatModel> statByBatchIds(List<Long> batchIds);
 
     /**
      * 批量入库
@@ -95,8 +89,4 @@ public interface CcCallPhoneMapper
     void bakCallPhoneByBatchId(Long batchId);
 
     void delCallPhoneByBatchId(Long batchId);
-
-    List<CcCallPhone> selectCcCallPhoneYlrzList(CcCallPhone ccCallPhone);
-
-    List<CcCallPhone> selectCcCallPhoneListByIds(List<String> callPhoneIds);
 }

+ 7 - 16
ruoyi-admin/src/main/java/com/ruoyi/aicall/model/ApiCallRecordQueryParams.java

@@ -20,34 +20,25 @@ public class ApiCallRecordQueryParams implements Serializable {
     /** 类型(01:呼入, 02:AI外呼, 03:人工外呼) */
     private String callType;
 
-    /** callType=02时可用 */
-    private Long batchId;
-
     /** 呼入caller/AI外呼telephone/人工外呼callee */
     private String telephone;
 
+    /** 呼入inboundTime/AI外呼calloutTime/人工外呼startTime */
+    private String calloutTimeStart;
+    private String calloutTimeEnd;
+
 
     /** 通话总时长起止 */
     private Integer timeLenStart;
     private Integer timeLenEnd;
 
-    /** 呼入inboundTime/AI外呼calloutTime/人工外呼startTime */
-    private String calloutTimeStart;
-    private String calloutTimeEnd;
-    /** 接听时间起止 */
-    private String answeredTimeStart;
-    private String answeredTimeEnd;
-    /** 自动外呼挂机时间起止 */
-    private String callEndTimeStart;
-    private String callEndTimeEnd;
-    /** 手动外呼挂机时间起止 */
-    private String endTimeStart;
-    private String endTimeEnd;
-
 
     /** 分机号 */
     private String extnum;
 
+    /** callType=02时可用 */
+    private Long batchId;
+
     /** callType=02时可用 */
     /** 0. 未拨打;1. 排队中;2. 正在拨打 ;3. 未接通 ;6. 成功转接;7. 线路故障 */
     private Integer callstatus;

+ 0 - 1
ruoyi-admin/src/main/java/com/ruoyi/aicall/model/CallTaskStatModel.java

@@ -15,5 +15,4 @@ public class CallTaskStatModel {
 
     /** 接通名单量 */
     private Integer connectCount = 0;
-
 }

+ 28 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/model/ChatRequest.java

@@ -0,0 +1,28 @@
+package com.ruoyi.aicall.model;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ChatRequest {
+
+    /** 客服号码 */
+    private String inboundCallee;
+
+    /** 客户问题 */
+    private String question;
+
+    /** 请求id,每次请求的唯一标识 */
+    private String requestId;
+
+
+
+    /** 会话id(Coze或者Dify的参数,在该会话首次调用Coze和Dify后返回),每次请求带上上一次返回的即可 */
+    private String conversationId;
+
+    /** 历史对话内容(为空代表是新的会话),每次请求带上上一次返回的即可 */
+    List<JSONObject> messages;
+
+}

+ 62 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/service/ICcAsrLanguagesService.java

@@ -0,0 +1,62 @@
+package com.ruoyi.aicall.service;
+
+import com.ruoyi.aicall.domain.CcAsrLanguages;
+
+import java.util.List;
+
+/**
+ * ASR语种及模型配置Service接口
+ * 
+ * @author ruoyi
+ * @date 2026-04-08
+ */
+public interface ICcAsrLanguagesService 
+{
+    /**
+     * 查询ASR语种及模型配置
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return ASR语种及模型配置
+     */
+    public CcAsrLanguages selectCcAsrLanguagesById(Long id);
+
+    /**
+     * 查询ASR语种及模型配置列表
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return ASR语种及模型配置集合
+     */
+    public List<CcAsrLanguages> selectCcAsrLanguagesList(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 新增ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    public int insertCcAsrLanguages(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 修改ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    public int updateCcAsrLanguages(CcAsrLanguages ccAsrLanguages);
+
+    /**
+     * 批量删除ASR语种及模型配置
+     * 
+     * @param ids 需要删除的ASR语种及模型配置主键集合
+     * @return 结果
+     */
+    public int deleteCcAsrLanguagesByIds(String ids);
+
+    /**
+     * 删除ASR语种及模型配置信息
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return 结果
+     */
+    public int deleteCcAsrLanguagesById(Long id);
+}

+ 1 - 10
ruoyi-admin/src/main/java/com/ruoyi/aicall/service/ICcCallPhoneService.java

@@ -2,6 +2,7 @@ package com.ruoyi.aicall.service;
 
 import com.ruoyi.aicall.domain.CcCallPhone;
 import com.ruoyi.aicall.domain.CcLlmAgentAccount;
+import com.ruoyi.aicall.model.CallPhoneExportVo;
 import com.ruoyi.aicall.model.CallTaskStatModel;
 
 import java.util.List;
@@ -68,12 +69,6 @@ public interface ICcCallPhoneService
      * @return
      */
     public CallTaskStatModel statByBatchId(Long batchId);
-    /**
-     * 根据batchIds统计外呼数据
-     * @param batchIds
-     * @return
-     */
-    public List<CallTaskStatModel> statByBatchIds(List<Long> batchIds);
 
     /**
      * 批量入库
@@ -92,8 +87,4 @@ public interface ICcCallPhoneService
     void bakCallPhoneByBatchId(Long batchId);
 
     void delCallPhoneByBatchId(Long batchId);
-
-    List<CcCallPhone> selectCcCallPhoneYlrzList(CcCallPhone ccCallPhone);
-
-    List<CcCallPhone> selectCcCallPhoneListByIds(List<String> callPhoneIds);
 }

+ 95 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/service/impl/CcAsrLanguagesServiceImpl.java

@@ -0,0 +1,95 @@
+package com.ruoyi.aicall.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.aicall.domain.CcAsrLanguages;
+import com.ruoyi.aicall.mapper.CcAsrLanguagesMapper;
+import com.ruoyi.aicall.service.ICcAsrLanguagesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.text.Convert;
+
+/**
+ * ASR语种及模型配置Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2026-04-08
+ */
+@Service
+public class CcAsrLanguagesServiceImpl implements ICcAsrLanguagesService
+{
+    @Autowired
+    private CcAsrLanguagesMapper ccAsrLanguagesMapper;
+
+    /**
+     * 查询ASR语种及模型配置
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return ASR语种及模型配置
+     */
+    @Override
+    public CcAsrLanguages selectCcAsrLanguagesById(Long id)
+    {
+        return ccAsrLanguagesMapper.selectCcAsrLanguagesById(id);
+    }
+
+    /**
+     * 查询ASR语种及模型配置列表
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return ASR语种及模型配置
+     */
+    @Override
+    public List<CcAsrLanguages> selectCcAsrLanguagesList(CcAsrLanguages ccAsrLanguages)
+    {
+        return ccAsrLanguagesMapper.selectCcAsrLanguagesList(ccAsrLanguages);
+    }
+
+    /**
+     * 新增ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    @Override
+    public int insertCcAsrLanguages(CcAsrLanguages ccAsrLanguages)
+    {
+        return ccAsrLanguagesMapper.insertCcAsrLanguages(ccAsrLanguages);
+    }
+
+    /**
+     * 修改ASR语种及模型配置
+     * 
+     * @param ccAsrLanguages ASR语种及模型配置
+     * @return 结果
+     */
+    @Override
+    public int updateCcAsrLanguages(CcAsrLanguages ccAsrLanguages)
+    {
+        return ccAsrLanguagesMapper.updateCcAsrLanguages(ccAsrLanguages);
+    }
+
+    /**
+     * 批量删除ASR语种及模型配置
+     * 
+     * @param ids 需要删除的ASR语种及模型配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCcAsrLanguagesByIds(String ids)
+    {
+        return ccAsrLanguagesMapper.deleteCcAsrLanguagesByIds(Convert.toStrArray(ids));
+    }
+
+    /**
+     * 删除ASR语种及模型配置信息
+     * 
+     * @param id ASR语种及模型配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCcAsrLanguagesById(Long id)
+    {
+        return ccAsrLanguagesMapper.deleteCcAsrLanguagesById(id);
+    }
+}

+ 0 - 72
ruoyi-admin/src/main/java/com/ruoyi/aicall/service/impl/CcCallPhoneServiceImpl.java

@@ -1,6 +1,5 @@
 package com.ruoyi.aicall.service.impl;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -10,7 +9,6 @@ import com.ruoyi.aicall.domain.CcLlmAgentAccount;
 import com.ruoyi.aicall.llm.ILlmCapability;
 import com.ruoyi.aicall.llm.model.AccountBaseEntity;
 import com.ruoyi.aicall.model.CallTaskStatModel;
-import com.ruoyi.aicall.model.CommonPhoneModel;
 import com.ruoyi.cc.utils.LlmAccountParser;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.StringUtils;
@@ -110,60 +108,6 @@ public class CcCallPhoneServiceImpl implements ICcCallPhoneService
         return ccCallPhoneMapper.selectCcCallPhoneList(ccCallPhone);
     }
 
-    /**
-     * 查询外呼号码列表
-     *
-     * @param ccCallPhone 外呼号码
-     * @return 外呼号码
-     */
-    @Override
-    public List<CcCallPhone> selectCcCallPhoneYlrzList(CcCallPhone ccCallPhone)
-    {
-        Map<String, Object> params = ccCallPhone.getParams();
-        if (null == params) {
-            params = new HashMap<>();
-        }
-        if (null != params.get("timeLenStart")
-                && !"".equals(params.get("timeLenStart"))) {
-            params.put("timeLenStart", Double.valueOf((String)params.get("timeLenStart")) * 60 * 1000L);
-        }
-        if (null != params.get("timeLenEnd")
-                && !"".equals(params.get("timeLenEnd"))) {
-            params.put("timeLenEnd", Double.valueOf((String)params.get("timeLenEnd")) * 60 * 1000L);
-        }
-        if (null != params.get("calloutTimeStart")
-                && !"".equals(params.get("calloutTimeStart"))) {
-            params.put("calloutTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeStart")).getTime());
-        }
-        if (null != params.get("calloutTimeEnd")
-                && !"".equals(params.get("calloutTimeEnd"))) {
-            params.put("calloutTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeEnd")).getTime());
-        }
-        if (null != params.get("answeredTimeStart")
-                && !"".equals(params.get("answeredTimeStart"))) {
-            params.put("answeredTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("answeredTimeStart")).getTime());
-        }
-        if (null != params.get("answeredTimeEnd")
-                && !"".equals(params.get("answeredTimeEnd"))) {
-            params.put("answeredTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("answeredTimeEnd")).getTime());
-        }
-        if (null != params.get("callEndTimeStart")
-                && !"".equals(params.get("callEndTimeStart"))) {
-            params.put("callEndTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("callEndTimeStart")).getTime());
-        }
-        if (null != params.get("callEndTimeEnd")
-                && !"".equals(params.get("callEndTimeEnd"))) {
-            params.put("callEndTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("callEndTimeEnd")).getTime());
-        }
-        ccCallPhone.setParams(params);
-        return ccCallPhoneMapper.selectCcCallPhoneYlrzList(ccCallPhone);
-    }
-
-    @Override
-    public List<CcCallPhone> selectCcCallPhoneListByIds(List<String> callPhoneIds) {
-        return ccCallPhoneMapper.selectCcCallPhoneListByIds(callPhoneIds);
-    }
-
     /**
      * 新增外呼号码
      * 
@@ -226,22 +170,6 @@ public class CcCallPhoneServiceImpl implements ICcCallPhoneService
         }
         return callTaskStatModel;
     }
-    @Override
-    public List<CallTaskStatModel> statByBatchIds(List<Long> batchIds) {
-        List<CallTaskStatModel> callTaskStatModels = ccCallPhoneMapper.statByBatchIds(batchIds);
-        callTaskStatModels.forEach(callTaskStatModel -> {
-            if (null == callTaskStatModel.getPhoneCount()) {
-                callTaskStatModel.setPhoneCount(0);
-            }
-            if (null == callTaskStatModel.getCallCount()) {
-                callTaskStatModel.setCallCount(0);
-            }
-            if (null == callTaskStatModel.getConnectCount()) {
-                callTaskStatModel.setConnectCount(0);
-            }
-        });
-        return callTaskStatModels;
-    }
 
     @Override
     public void batchInsertCcCallPhone(List<CcCallPhone> phoneList) {

+ 175 - 175
ruoyi-admin/src/main/java/com/ruoyi/aicall/tencent/TencentCloudManage.java

@@ -1,175 +1,175 @@
-package com.ruoyi.aicall.tencent;
-
-import com.tencentcloudapi.common.AbstractModel;
-
-import com.tencentcloudapi.common.Credential;
-import com.tencentcloudapi.common.profile.ClientProfile;
-import com.tencentcloudapi.common.profile.HttpProfile;
-import com.tencentcloudapi.common.exception.TencentCloudSDKException;
-import com.tencentcloudapi.lighthouse.v20200324.LighthouseClient;
-import com.tencentcloudapi.lighthouse.v20200324.models.*;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.Arrays;
-
-@Slf4j
-public class TencentCloudManage
-{
-    public static void main(String [] args) {
-        String secretId = "";
-        String secretKey = "";
-        String region = "";
-
-    }
-
-
-    public void describeInstances(String secretId, String secretKey, String region) {
-        try{
-            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
-            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
-            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
-            // 请参见:https://cloud.tencent.com/document/product/1278/85305
-            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
-            Credential cred = new Credential(secretId, secretKey);
-            // 使用临时密钥示例
-            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
-            // 实例化一个http选项,可选的,没有特殊需求可以跳过
-            HttpProfile httpProfile = new HttpProfile();
-            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
-            // 实例化一个client选项,可选的,没有特殊需求可以跳过
-            ClientProfile clientProfile = new ClientProfile();
-            clientProfile.setHttpProfile(httpProfile);
-            // 实例化要请求产品的client对象,clientProfile是可选的
-            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
-            // 实例化一个请求对象,每个接口都会对应一个request对象
-            DescribeInstancesRequest req = new DescribeInstancesRequest();
-
-            // 返回的resp是一个DescribeInstancesResponse的实例,与请求对象对应
-            DescribeInstancesResponse resp = client.DescribeInstances(req);
-            // 输出json格式的字符串回包
-            log.info(AbstractModel.toJsonString(resp));
-        } catch (TencentCloudSDKException e) {
-            log.info(e.toString());
-        }
-    }
-
-    public void describeFirewallRules(String secretId, String secretKey, String region, String instanceId){
-        try{
-            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
-            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
-            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
-            // 请参见:https://cloud.tencent.com/document/product/1278/85305
-            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
-            Credential cred = new Credential(secretId, secretKey);
-            // 使用临时密钥示例
-            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
-            // 实例化一个http选项,可选的,没有特殊需求可以跳过
-            HttpProfile httpProfile = new HttpProfile();
-            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
-            // 实例化一个client选项,可选的,没有特殊需求可以跳过
-            ClientProfile clientProfile = new ClientProfile();
-            clientProfile.setHttpProfile(httpProfile);
-            // 实例化要请求产品的client对象,clientProfile是可选的
-            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
-            // 实例化一个请求对象,每个接口都会对应一个request对象
-            DescribeFirewallRulesRequest req = new DescribeFirewallRulesRequest();
-            req.setInstanceId(instanceId);
-
-            // 返回的resp是一个DescribeFirewallRulesResponse的实例,与请求对象对应
-            DescribeFirewallRulesResponse resp = client.DescribeFirewallRules(req);
-            // 输出json格式的字符串回包
-            log.info(AbstractModel.toJsonString(resp));
-        } catch (TencentCloudSDKException e) {
-            log.info(e.toString());
-        }
-    }
-
-    public void createFirewallRules(String secretId, String secretKey, String region, String instanceId, String allowedIp){
-
-        try{
-            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
-            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
-            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
-            // 请参见:https://cloud.tencent.com/document/product/1278/85305
-            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
-            // 接口文档:https://console.cloud.tencent.com/api/explorer?Product=lighthouse&Version=2020-03-24&Action=DescribeFirewallRules
-
-            Credential cred = new Credential(secretId, secretKey);
-            // 使用临时密钥示例
-            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
-            // 实例化一个http选项,可选的,没有特殊需求可以跳过
-            HttpProfile httpProfile = new HttpProfile();
-            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
-            // 实例化一个client选项,可选的,没有特殊需求可以跳过
-            ClientProfile clientProfile = new ClientProfile();
-            clientProfile.setHttpProfile(httpProfile);
-            // 实例化要请求产品的client对象,clientProfile是可选的
-            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
-            // 实例化一个请求对象,每个接口都会对应一个request对象
-            CreateFirewallRulesRequest req = new CreateFirewallRulesRequest();
-            req.setInstanceId(instanceId);
-            FirewallRule[] firewallRules1 = new FirewallRule[1];
-            FirewallRule firewallRule1 = new FirewallRule();
-            firewallRule1.setPort("5080");
-            firewallRule1.setProtocol("UDP");
-
-            // 关键:设置CIDR表示的IP范围,限制只有指定IP可以访问
-            // 支持格式:单个IP (如 "1.1.1.1/32") 或 IP段 (如 "192.168.1.0/24")
-            // 多个IP用逗号分隔,如 "1.1.1.1/32,2.2.2.2/32"
-            firewallRule1.setCidrBlock(allowedIp + "/32");
-            // 可选:设置防火墙规则备注/描述
-            firewallRule1.setFirewallRuleDescription("允许指定IP访问5080 UDP端口");
-            // 设置动作,默认为ACCEPT(接受),可选值:ACCEPT/DROP
-            firewallRule1.setAction("ACCEPT");
-
-            firewallRules1[0] = firewallRule1;
-            req.setFirewallRules(firewallRules1);
-
-            // 返回的resp是一个CreateFirewallRulesResponse的实例,与请求对象对应
-            CreateFirewallRulesResponse resp = client.CreateFirewallRules(req);
-            // 输出json格式的字符串回包
-            log.info(AbstractModel.toJsonString(resp));
-        } catch (TencentCloudSDKException e) {
-            log.info(e.toString());
-        }
-    }
-
-    public void deleteFirewallRules(String secretId, String secretKey, String region, String instanceId, String allowedIp){
-        try{
-            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
-            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
-            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
-            // 请参见:https://cloud.tencent.com/document/product/1278/85305
-            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
-            Credential cred = new Credential(secretId, secretKey);
-            // 使用临时密钥示例
-            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
-            // 实例化一个http选项,可选的,没有特殊需求可以跳过
-            HttpProfile httpProfile = new HttpProfile();
-            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
-            // 实例化一个client选项,可选的,没有特殊需求可以跳过
-            ClientProfile clientProfile = new ClientProfile();
-            clientProfile.setHttpProfile(httpProfile);
-            // 实例化要请求产品的client对象,clientProfile是可选的
-            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
-            // 实例化一个请求对象,每个接口都会对应一个request对象
-            DeleteFirewallRulesRequest req = new DeleteFirewallRulesRequest();
-            req.setInstanceId(instanceId);
-
-            FirewallRule[] firewallRules1 = new FirewallRule[1];
-            FirewallRule firewallRule1 = new FirewallRule();
-            firewallRule1.setProtocol("UDP");
-            firewallRule1.setPort("5080");
-            firewallRule1.setCidrBlock(allowedIp + "/32");
-            firewallRules1[0] = firewallRule1;
-            req.setFirewallRules(firewallRules1);
-
-            // 返回的resp是一个DeleteFirewallRulesResponse的实例,与请求对象对应
-            DeleteFirewallRulesResponse resp = client.DeleteFirewallRules(req);
-            // 输出json格式的字符串回包
-            log.info(AbstractModel.toJsonString(resp));
-        } catch (TencentCloudSDKException e) {
-            log.info(e.toString());
-        }
-    }
-}
+//package com.ruoyi.aicall.tencent;
+//
+//import com.tencentcloudapi.common.AbstractModel;
+//
+//import com.tencentcloudapi.common.Credential;
+//import com.tencentcloudapi.common.profile.ClientProfile;
+//import com.tencentcloudapi.common.profile.HttpProfile;
+//import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+//import com.tencentcloudapi.lighthouse.v20200324.LighthouseClient;
+//import com.tencentcloudapi.lighthouse.v20200324.models.*;
+//import lombok.extern.slf4j.Slf4j;
+//
+//import java.util.Arrays;
+//
+//@Slf4j
+//public class TencentCloudManage
+//{
+//    public static void main(String [] args) {
+//        String secretId = "";
+//        String secretKey = "";
+//        String region = "";
+//
+//    }
+//
+//
+//    public void describeInstances(String secretId, String secretKey, String region) {
+//        try{
+//            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
+//            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
+//            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
+//            // 请参见:https://cloud.tencent.com/document/product/1278/85305
+//            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
+//            Credential cred = new Credential(secretId, secretKey);
+//            // 使用临时密钥示例
+//            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
+//            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+//            HttpProfile httpProfile = new HttpProfile();
+//            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
+//            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+//            ClientProfile clientProfile = new ClientProfile();
+//            clientProfile.setHttpProfile(httpProfile);
+//            // 实例化要请求产品的client对象,clientProfile是可选的
+//            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
+//            // 实例化一个请求对象,每个接口都会对应一个request对象
+//            DescribeInstancesRequest req = new DescribeInstancesRequest();
+//
+//            // 返回的resp是一个DescribeInstancesResponse的实例,与请求对象对应
+//            DescribeInstancesResponse resp = client.DescribeInstances(req);
+//            // 输出json格式的字符串回包
+//            log.info(AbstractModel.toJsonString(resp));
+//        } catch (TencentCloudSDKException e) {
+//            log.info(e.toString());
+//        }
+//    }
+//
+//    public void describeFirewallRules(String secretId, String secretKey, String region, String instanceId){
+//        try{
+//            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
+//            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
+//            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
+//            // 请参见:https://cloud.tencent.com/document/product/1278/85305
+//            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
+//            Credential cred = new Credential(secretId, secretKey);
+//            // 使用临时密钥示例
+//            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
+//            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+//            HttpProfile httpProfile = new HttpProfile();
+//            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
+//            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+//            ClientProfile clientProfile = new ClientProfile();
+//            clientProfile.setHttpProfile(httpProfile);
+//            // 实例化要请求产品的client对象,clientProfile是可选的
+//            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
+//            // 实例化一个请求对象,每个接口都会对应一个request对象
+//            DescribeFirewallRulesRequest req = new DescribeFirewallRulesRequest();
+//            req.setInstanceId(instanceId);
+//
+//            // 返回的resp是一个DescribeFirewallRulesResponse的实例,与请求对象对应
+//            DescribeFirewallRulesResponse resp = client.DescribeFirewallRules(req);
+//            // 输出json格式的字符串回包
+//            log.info(AbstractModel.toJsonString(resp));
+//        } catch (TencentCloudSDKException e) {
+//            log.info(e.toString());
+//        }
+//    }
+//
+//    public void createFirewallRules(String secretId, String secretKey, String region, String instanceId, String allowedIp){
+//
+//        try{
+//            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
+//            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
+//            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
+//            // 请参见:https://cloud.tencent.com/document/product/1278/85305
+//            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
+//            // 接口文档:https://console.cloud.tencent.com/api/explorer?Product=lighthouse&Version=2020-03-24&Action=DescribeFirewallRules
+//
+//            Credential cred = new Credential(secretId, secretKey);
+//            // 使用临时密钥示例
+//            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
+//            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+//            HttpProfile httpProfile = new HttpProfile();
+//            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
+//            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+//            ClientProfile clientProfile = new ClientProfile();
+//            clientProfile.setHttpProfile(httpProfile);
+//            // 实例化要请求产品的client对象,clientProfile是可选的
+//            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
+//            // 实例化一个请求对象,每个接口都会对应一个request对象
+//            CreateFirewallRulesRequest req = new CreateFirewallRulesRequest();
+//            req.setInstanceId(instanceId);
+//            FirewallRule[] firewallRules1 = new FirewallRule[1];
+//            FirewallRule firewallRule1 = new FirewallRule();
+//            firewallRule1.setPort("5080");
+//            firewallRule1.setProtocol("UDP");
+//
+//            // 关键:设置CIDR表示的IP范围,限制只有指定IP可以访问
+//            // 支持格式:单个IP (如 "1.1.1.1/32") 或 IP段 (如 "192.168.1.0/24")
+//            // 多个IP用逗号分隔,如 "1.1.1.1/32,2.2.2.2/32"
+//            firewallRule1.setCidrBlock(allowedIp + "/32");
+//            // 可选:设置防火墙规则备注/描述
+//            firewallRule1.setFirewallRuleDescription("允许指定IP访问5080 UDP端口");
+//            // 设置动作,默认为ACCEPT(接受),可选值:ACCEPT/DROP
+//            firewallRule1.setAction("ACCEPT");
+//
+//            firewallRules1[0] = firewallRule1;
+//            req.setFirewallRules(firewallRules1);
+//
+//            // 返回的resp是一个CreateFirewallRulesResponse的实例,与请求对象对应
+//            CreateFirewallRulesResponse resp = client.CreateFirewallRules(req);
+//            // 输出json格式的字符串回包
+//            log.info(AbstractModel.toJsonString(resp));
+//        } catch (TencentCloudSDKException e) {
+//            log.info(e.toString());
+//        }
+//    }
+//
+//    public void deleteFirewallRules(String secretId, String secretKey, String region, String instanceId, String allowedIp){
+//        try{
+//            // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
+//            // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
+//            // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
+//            // 请参见:https://cloud.tencent.com/document/product/1278/85305
+//            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
+//            Credential cred = new Credential(secretId, secretKey);
+//            // 使用临时密钥示例
+//            // Credential cred = new Credential("SecretId", "SecretKey", "Token");
+//            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+//            HttpProfile httpProfile = new HttpProfile();
+//            httpProfile.setEndpoint("lighthouse.tencentcloudapi.com");
+//            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+//            ClientProfile clientProfile = new ClientProfile();
+//            clientProfile.setHttpProfile(httpProfile);
+//            // 实例化要请求产品的client对象,clientProfile是可选的
+//            LighthouseClient client = new LighthouseClient(cred, region, clientProfile);
+//            // 实例化一个请求对象,每个接口都会对应一个request对象
+//            DeleteFirewallRulesRequest req = new DeleteFirewallRulesRequest();
+//            req.setInstanceId(instanceId);
+//
+//            FirewallRule[] firewallRules1 = new FirewallRule[1];
+//            FirewallRule firewallRule1 = new FirewallRule();
+//            firewallRule1.setProtocol("UDP");
+//            firewallRule1.setPort("5080");
+//            firewallRule1.setCidrBlock(allowedIp + "/32");
+//            firewallRules1[0] = firewallRule1;
+//            req.setFirewallRules(firewallRules1);
+//
+//            // 返回的resp是一个DeleteFirewallRulesResponse的实例,与请求对象对应
+//            DeleteFirewallRulesResponse resp = client.DeleteFirewallRules(req);
+//            // 输出json格式的字符串回包
+//            log.info(AbstractModel.toJsonString(resp));
+//        } catch (TencentCloudSDKException e) {
+//            log.info(e.toString());
+//        }
+//    }
+//}

+ 3 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/utils/ClientIpCheck.java

@@ -16,6 +16,9 @@ public class ClientIpCheck {
         ICcParamsService ccParamsService = SpringUtils.getBean(ICcParamsService.class);
         String whiteIpList = ccParamsService.getParamValueByCode("api-client-white-ips", "");
         if (StringUtils.isNotEmpty(whiteIpList)) {
+            if (whiteIpList.contains("0.0.0.0")) {
+                return true;
+            }
             for (String ip: whiteIpList.split(",")) {
                 if (ip.equals(clientIp)) {
                     return true;

+ 1102 - 0
ruoyi-admin/src/main/java/com/ruoyi/aicall/utils/LlmTest.java

@@ -0,0 +1,1102 @@
+package com.ruoyi.aicall.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.aicall.llm.model.LlmAccount;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import okio.BufferedSource;
+import org.apache.http.HttpStatus;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class LlmTest {
+
+    final static OkHttpClient CLIENT =  new OkHttpClient.Builder()
+            .connectTimeout(90, TimeUnit.SECONDS)
+            .readTimeout(90, TimeUnit.SECONDS)
+            .build();
+
+    public static void main(String[] args) {
+        try {
+//            chatgptTest();
+//            claudeTest();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void chatgptTest() throws Exception {
+
+        String apiKey = "";
+        String url = "https://api.openai.com/v1/chat/completions";
+        JSONObject params = new JSONObject();
+        params.put("model", "gpt-4.1-mini");
+        params.put("stream", true);
+        JSONArray messages = new JSONArray();
+        JSONObject message1 = new JSONObject();
+        message1.put("role", "user");
+        message1.put("content", "Introduction to AI");
+        messages.add(message1);
+        params.put("messages", messages);
+        log.info("url:{}, params:{}", url, params);
+        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), JSONObject.toJSONString(params));
+        Request request = new Request.Builder()
+                .post(requestBody)
+                .header("Authorization", "Bearer " + apiKey)
+                .header("Content-Type", "application/json")
+                .url(url).build();
+
+        try (Response response = CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                log.error("Model api error: http-code={}, msg={}, url={}",
+                        response.code(),
+                        response.message(),
+                        url
+                );
+
+                if (response.code() == HttpStatus.SC_UNAUTHORIZED
+                        || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+                    throw new IOException("Unexpected code " + response);
+                } else {
+                    return;
+                }
+            }
+
+            BufferedSource source = response.body().source();
+            StringBuilder responseBuilder = new StringBuilder();
+
+//                data: {"choices":[{"delta":{"content":"AI"},"index":0}]}
+//                data: {"choices":[{"delta":{"content":"像星辰"},"index":0}]}
+//                data: {"choices":[{"delta":{"content":"照亮未来"},"index":0}]}
+//                data: [DONE]
+            while (!source.exhausted()) {
+                String line = source.readUtf8Line();
+                System.out.println(line);
+                if (line != null && line.startsWith("data: ")) {
+                    String jsonData = line.substring(5).trim(); // 去掉 "data: " 前缀
+                    if (jsonData.equals("[DONE]")) {
+                        break; // 流式响应结束
+                    }
+
+                    JSONObject jsonResponse = JSON.parseObject(jsonData);
+                    JSONObject message = jsonResponse.getJSONArray("choices")
+                            .getJSONObject(0)
+                            .getJSONObject("delta"); // 注意:流式响应中消息在 "delta" 字段中
+
+                    if (message.containsKey("content")) {
+                        String speechContent = message.getString("content");
+                        if (StringUtils.isNotEmpty(speechContent)) {
+                            responseBuilder.append(speechContent);
+                        }
+                    }
+                }
+            }
+            String answer = responseBuilder.toString();
+            log.info("answer:{}", answer);
+        }
+
+
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"obfuscation":"RBvjf"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Certainly"},"logprobs":null,"finish_reason":null}],"obfuscation":"EW8l7cX80ANzvU"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"obfuscation":"QVNwn0"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Here"},"logprobs":null,"finish_reason":null}],"obfuscation":"yA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"’s"},"logprobs":null,"finish_reason":null}],"obfuscation":"CghmO"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" an"},"logprobs":null,"finish_reason":null}],"obfuscation":"WfWa"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" introduction"},"logprobs":null,"finish_reason":null}],"obfuscation":"pJabWf7Zwj"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"B9vD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Artificial"},"logprobs":null,"finish_reason":null}],"obfuscation":"oq3Ie6fQfJil"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Intelligence"},"logprobs":null,"finish_reason":null}],"obfuscation":"AqGHQvb95W"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"BbPAm"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"faZ4h"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"):\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"9"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"---\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"###"},"logprobs":null,"finish_reason":null}],"obfuscation":"33oG"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Introduction"},"logprobs":null,"finish_reason":null}],"obfuscation":"baLbFyetMl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"qTw6"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Artificial"},"logprobs":null,"finish_reason":null}],"obfuscation":"MDvOnOJKtNLP"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Intelligence"},"logprobs":null,"finish_reason":null}],"obfuscation":"MbzoIL91p6"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"ViJ76"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"Le0Mz"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":")\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"m9"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"**"},"logprobs":null,"finish_reason":null}],"obfuscation":"Gak4o"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Artificial"},"logprobs":null,"finish_reason":null}],"obfuscation":"QNHD0uMPsigBG"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Intelligence"},"logprobs":null,"finish_reason":null}],"obfuscation":"wo4lVRX8ax"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"JRufs"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"foCel"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":")**"},"logprobs":null,"finish_reason":null}],"obfuscation":"XSf0"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" refers"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"XOYj"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}],"obfuscation":"jFo"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" simulation"},"logprobs":null,"finish_reason":null}],"obfuscation":"sr0GwKpqldV2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"ejS4"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" human"},"logprobs":null,"finish_reason":null}],"obfuscation":"9"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" intelligence"},"logprobs":null,"finish_reason":null}],"obfuscation":"A637N3Fy5F"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"obfuscation":"gvBt"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" machines"},"logprobs":null,"finish_reason":null}],"obfuscation":"BiZRsotw2kn5kQ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" that"},"logprobs":null,"finish_reason":null}],"obfuscation":"T5"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" are"},"logprobs":null,"finish_reason":null}],"obfuscation":"XGD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" programmed"},"logprobs":null,"finish_reason":null}],"obfuscation":"eo9jo1SxplxQ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"hFbL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" think"},"logprobs":null,"finish_reason":null}],"obfuscation":"l"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"mMFDHa"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" learn"},"logprobs":null,"finish_reason":null}],"obfuscation":"n"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"729CSK"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"m6q"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" make"},"logprobs":null,"finish_reason":null}],"obfuscation":"Zk"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" decisions"},"logprobs":null,"finish_reason":null}],"obfuscation":"bOcodqACrcwn1"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"obfuscation":"feNf64"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Unlike"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" traditional"},"logprobs":null,"finish_reason":null}],"obfuscation":"21uWgLa2Eja"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" software"},"logprobs":null,"finish_reason":null}],"obfuscation":"9UYGB5SddVcCZK"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" that"},"logprobs":null,"finish_reason":null}],"obfuscation":"QP"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" follows"},"logprobs":null,"finish_reason":null}],"obfuscation":"suaIhg1XewGhE3g"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" explicit"},"logprobs":null,"finish_reason":null}],"obfuscation":"wZmwRhuoTVLynq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" instructions"},"logprobs":null,"finish_reason":null}],"obfuscation":"npVrD0YUwN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"yBQDUq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"2TDA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" systems"},"logprobs":null,"finish_reason":null}],"obfuscation":"Bgq68PrXZIdea9H"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" use"},"logprobs":null,"finish_reason":null}],"obfuscation":"FwN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" data"},"logprobs":null,"finish_reason":null}],"obfuscation":"VI"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"eG5"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" algorithms"},"logprobs":null,"finish_reason":null}],"obfuscation":"HZrkUaIsheCq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"7Btu"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" perform"},"logprobs":null,"finish_reason":null}],"obfuscation":"iNHnmHUCyD8nFh5"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" tasks"},"logprobs":null,"finish_reason":null}],"obfuscation":"k"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" that"},"logprobs":null,"finish_reason":null}],"obfuscation":"Ep"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" typically"},"logprobs":null,"finish_reason":null}],"obfuscation":"7s993GlpKSrKt"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" require"},"logprobs":null,"finish_reason":null}],"obfuscation":"caPCwRgn35f1PQq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" human"},"logprobs":null,"finish_reason":null}],"obfuscation":"c"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" intelligence"},"logprobs":null,"finish_reason":null}],"obfuscation":"vQAppiygvT"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"Pe"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"####"},"logprobs":null,"finish_reason":null}],"obfuscation":"lR8"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Key"},"logprobs":null,"finish_reason":null}],"obfuscation":"cGt"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Concepts"},"logprobs":null,"finish_reason":null}],"obfuscation":"VF36YmtMPhoLwl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"obfuscation":"Rom3"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"Pi5J"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"ab0V"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"FHXZTD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" **"},"logprobs":null,"finish_reason":null}],"obfuscation":"LoOM"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Machine"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Learning"},"logprobs":null,"finish_reason":null}],"obfuscation":"w6wLMun1Szkoeo"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"UBkxS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"ML"},"logprobs":null,"finish_reason":null}],"obfuscation":"MAPzR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"):"},"logprobs":null,"finish_reason":null}],"obfuscation":"Y33ho"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"**"},"logprobs":null,"finish_reason":null}],"obfuscation":"XXsSR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" A"},"logprobs":null,"finish_reason":null}],"obfuscation":"uJaCz"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" subset"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"MHcw"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"p97t"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" where"},"logprobs":null,"finish_reason":null}],"obfuscation":"l"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" machines"},"logprobs":null,"finish_reason":null}],"obfuscation":"MjrVgqWXjh9pDv"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" learn"},"logprobs":null,"finish_reason":null}],"obfuscation":"2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"obfuscation":"dn"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" data"},"logprobs":null,"finish_reason":null}],"obfuscation":"Jd"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"nHQw"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" improve"},"logprobs":null,"finish_reason":null}],"obfuscation":"gvE6hP7Y82Psfba"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" their"},"logprobs":null,"finish_reason":null}],"obfuscation":"O"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" performance"},"logprobs":null,"finish_reason":null}],"obfuscation":"06rpYkbB5uh"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" on"},"logprobs":null,"finish_reason":null}],"obfuscation":"G8LL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" tasks"},"logprobs":null,"finish_reason":null}],"obfuscation":"j"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" without"},"logprobs":null,"finish_reason":null}],"obfuscation":"EkMMR6LABjLevls"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" being"},"logprobs":null,"finish_reason":null}],"obfuscation":"Q"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" explicitly"},"logprobs":null,"finish_reason":null}],"obfuscation":"ILFXXpx2DXsS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" programmed"},"logprobs":null,"finish_reason":null}],"obfuscation":"f82OJOXOfB4s"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"BkFB"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"qq8fdh"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" **"},"logprobs":null,"finish_reason":null}],"obfuscation":"hwmW"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Natural"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Language"},"logprobs":null,"finish_reason":null}],"obfuscation":"0dcGbgJoFsRYhI"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Processing"},"logprobs":null,"finish_reason":null}],"obfuscation":"wbOoRhN1shAf"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"MRCSE"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}],"obfuscation":"smahVg"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"LP"},"logprobs":null,"finish_reason":null}],"obfuscation":"GlYHU"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"):"},"logprobs":null,"finish_reason":null}],"obfuscation":"Rq5EX"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"**"},"logprobs":null,"finish_reason":null}],"obfuscation":"bNSYZ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Enables"},"logprobs":null,"finish_reason":null}],"obfuscation":"Mb6ozVFtoHyopz2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" machines"},"logprobs":null,"finish_reason":null}],"obfuscation":"827YZbjPNlsF8z"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"vWVD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" understand"},"logprobs":null,"finish_reason":null}],"obfuscation":"zaSjel3xBDZ5"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"KwxKwh"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" interpret"},"logprobs":null,"finish_reason":null}],"obfuscation":"K2LySFSJwyLVp"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"MtudA2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"tmp"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" generate"},"logprobs":null,"finish_reason":null}],"obfuscation":"EpI6VYqaanTYht"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" human"},"logprobs":null,"finish_reason":null}],"obfuscation":"S"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" language"},"logprobs":null,"finish_reason":null}],"obfuscation":"0wghxYODQ9t2MH"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"kuTR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"XzAURt"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" **"},"logprobs":null,"finish_reason":null}],"obfuscation":"KGKr"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Computer"},"logprobs":null,"finish_reason":null}],"obfuscation":"KSJjbzNO8F0xeZl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Vision"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":**"},"logprobs":null,"finish_reason":null}],"obfuscation":"NaUL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" The"},"logprobs":null,"finish_reason":null}],"obfuscation":"QFB"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ability"},"logprobs":null,"finish_reason":null}],"obfuscation":"ddixNbync6kinbh"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"4TiZ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" machines"},"logprobs":null,"finish_reason":null}],"obfuscation":"Pu5eKgOjePVUnl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"dmvP"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" interpret"},"logprobs":null,"finish_reason":null}],"obfuscation":"7XLEyndvw6spL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"sgv"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" analyze"},"logprobs":null,"finish_reason":null}],"obfuscation":"Wumri8mhVRgtnCl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" visual"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" data"},"logprobs":null,"finish_reason":null}],"obfuscation":"lm"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"obfuscation":"TF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}],"obfuscation":"dQq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" world"},"logprobs":null,"finish_reason":null}],"obfuscation":"Z"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"lHYj"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"85FOuD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" **"},"logprobs":null,"finish_reason":null}],"obfuscation":"xb2v"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Rob"},"logprobs":null,"finish_reason":null}],"obfuscation":"EVFl"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"otics"},"logprobs":null,"finish_reason":null}],"obfuscation":"cq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":**"},"logprobs":null,"finish_reason":null}],"obfuscation":"rv1a"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"QmNq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" applied"},"logprobs":null,"finish_reason":null}],"obfuscation":"XIFxgvtYbSQnXmp"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"Kdyn"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" design"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"EyM"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" operate"},"logprobs":null,"finish_reason":null}],"obfuscation":"QBtGs4fvpkI2swU"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" robots"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"bLG3"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"7daC3U"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" **"},"logprobs":null,"finish_reason":null}],"obfuscation":"Pbq2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"Expert"},"logprobs":null,"finish_reason":null}],"obfuscation":"N"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Systems"},"logprobs":null,"finish_reason":null}],"obfuscation":"PotZtlQ3MiwTTu0"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":**"},"logprobs":null,"finish_reason":null}],"obfuscation":"y9XA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Programs"},"logprobs":null,"finish_reason":null}],"obfuscation":"Hk2XLJVSvvv3aM"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" that"},"logprobs":null,"finish_reason":null}],"obfuscation":"Bg"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" mimic"},"logprobs":null,"finish_reason":null}],"obfuscation":"2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" decision"},"logprobs":null,"finish_reason":null}],"obfuscation":"nOhDGHus1dxrfA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-making"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" abilities"},"logprobs":null,"finish_reason":null}],"obfuscation":"HMuEHFYXPrkPa"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"qedF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"obfuscation":"mfjTg"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" human"},"logprobs":null,"finish_reason":null}],"obfuscation":"W"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" expert"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"3E"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"####"},"logprobs":null,"finish_reason":null}],"obfuscation":"Wca"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Applications"},"logprobs":null,"finish_reason":null}],"obfuscation":"S0ByQAfAKN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"NhhS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"r9jS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"Mgb5"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"Y9O7Ty"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Voice"},"logprobs":null,"finish_reason":null}],"obfuscation":"H"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" assistants"},"logprobs":null,"finish_reason":null}],"obfuscation":"t1ZlSyJjWRuZ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"e8hJN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"e"},"logprobs":null,"finish_reason":null}],"obfuscation":"0ujF7v"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".g"},"logprobs":null,"finish_reason":null}],"obfuscation":"zQoQx"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".,"},"logprobs":null,"finish_reason":null}],"obfuscation":"flyfz"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Siri"},"logprobs":null,"finish_reason":null}],"obfuscation":"3h"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"wdP89K"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Alexa"},"logprobs":null,"finish_reason":null}],"obfuscation":"0"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":")\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"jWFp"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"qcTXLs"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Recommendation"},"logprobs":null,"finish_reason":null}],"obfuscation":"gByxFMGm"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" systems"},"logprobs":null,"finish_reason":null}],"obfuscation":"DSGkZD10PjRodJv"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" ("},"logprobs":null,"finish_reason":null}],"obfuscation":"uB93g"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"e"},"logprobs":null,"finish_reason":null}],"obfuscation":"YL6sWG"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".g"},"logprobs":null,"finish_reason":null}],"obfuscation":"qKwqb"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".,"},"logprobs":null,"finish_reason":null}],"obfuscation":"6mSIf"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Netflix"},"logprobs":null,"finish_reason":null}],"obfuscation":"t2eXHTVOvBV3jnz"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"8dNerc"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Amazon"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":")\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"8yQm"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"3XGKrv"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Autonomous"},"logprobs":null,"finish_reason":null}],"obfuscation":"hBrckbI2aboV"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" vehicles"},"logprobs":null,"finish_reason":null}],"obfuscation":"m1OrPSuLtBoHxk"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"LceZL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"ZUC7fO"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Fraud"},"logprobs":null,"finish_reason":null}],"obfuscation":"t"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" detection"},"logprobs":null,"finish_reason":null}],"obfuscation":"hcABRZ6tNllvq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"obfuscation":"o4FN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" finance"},"logprobs":null,"finish_reason":null}],"obfuscation":"bKAWCW3bH70JCZE"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"whkdq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}],"obfuscation":"9Oa5DF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Medical"},"logprobs":null,"finish_reason":null}],"obfuscation":"i8VQKwtfIrozqDW"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" diagnosis"},"logprobs":null,"finish_reason":null}],"obfuscation":"XLUfSwOlFfki4"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"ZRd"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" treatment"},"logprobs":null,"finish_reason":null}],"obfuscation":"tjm1ealoBuzMm"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" planning"},"logprobs":null,"finish_reason":null}],"obfuscation":"cmw9GDcAhALVZO"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"uQU"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"####"},"logprobs":null,"finish_reason":null}],"obfuscation":"013"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" Importance"},"logprobs":null,"finish_reason":null}],"obfuscation":"Oe7WzoLfiYaj"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"PoHG"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"tizb"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":":\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"0oCe"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"21T9j"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" is"},"logprobs":null,"finish_reason":null}],"obfuscation":"bVej"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" transforming"},"logprobs":null,"finish_reason":null}],"obfuscation":"IcXN69wla4"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" industries"},"logprobs":null,"finish_reason":null}],"obfuscation":"NnMVQGL0MtVr"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" by"},"logprobs":null,"finish_reason":null}],"obfuscation":"RzOA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" increasing"},"logprobs":null,"finish_reason":null}],"obfuscation":"Gu8A7JBz7UVD"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" efficiency"},"logprobs":null,"finish_reason":null}],"obfuscation":"HwxkobsR29sX"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"0AKCwA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" autom"},"logprobs":null,"finish_reason":null}],"obfuscation":"l"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"ating"},"logprobs":null,"finish_reason":null}],"obfuscation":"IH"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" repetitive"},"logprobs":null,"finish_reason":null}],"obfuscation":"3Dzi8zss656U"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" tasks"},"logprobs":null,"finish_reason":null}],"obfuscation":"7"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"LKbJ4a"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"sbF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" providing"},"logprobs":null,"finish_reason":null}],"obfuscation":"rtms2nHZWrm8J"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" insights"},"logprobs":null,"finish_reason":null}],"obfuscation":"7N3dteOtGE5BX9"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"obfuscation":"bn"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" large"},"logprobs":null,"finish_reason":null}],"obfuscation":"R"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" datasets"},"logprobs":null,"finish_reason":null}],"obfuscation":"1SPGh1TguJJss2"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"obfuscation":"2Q0qNa"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" It"},"logprobs":null,"finish_reason":null}],"obfuscation":"n0wH"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" has"},"logprobs":null,"finish_reason":null}],"obfuscation":"r6K"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}],"obfuscation":"urA"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" potential"},"logprobs":null,"finish_reason":null}],"obfuscation":"MmTMhmDJXJwzz"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"obfuscation":"d4HC"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" solve"},"logprobs":null,"finish_reason":null}],"obfuscation":"S"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" complex"},"logprobs":null,"finish_reason":null}],"obfuscation":"IFVvVKknjDAsz8d"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" problems"},"logprobs":null,"finish_reason":null}],"obfuscation":"lpjR2b7LzyDnRW"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"obfuscation":"fIF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" drive"},"logprobs":null,"finish_reason":null}],"obfuscation":"T"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" innovation"},"logprobs":null,"finish_reason":null}],"obfuscation":"E834QQ8SN5Ac"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" across"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" various"},"logprobs":null,"finish_reason":null}],"obfuscation":"Jkz5jrUPKr2sAgZ"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" fields"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":".\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":"Qv"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"---\n\n"},"logprobs":null,"finish_reason":null}],"obfuscation":""}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"If"},"logprobs":null,"finish_reason":null}],"obfuscation":"FqxnS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" you"},"logprobs":null,"finish_reason":null}],"obfuscation":"J75"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" want"},"logprobs":null,"finish_reason":null}],"obfuscation":"sE"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"obfuscation":"JXoFqN"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" I"},"logprobs":null,"finish_reason":null}],"obfuscation":"0DDjR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" can"},"logprobs":null,"finish_reason":null}],"obfuscation":"Oup"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" provide"},"logprobs":null,"finish_reason":null}],"obfuscation":"RwPwQoIXpCxlUk1"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" more"},"logprobs":null,"finish_reason":null}],"obfuscation":"xq"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" detailed"},"logprobs":null,"finish_reason":null}],"obfuscation":"4gqnOnR5sRSC1p"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" information"},"logprobs":null,"finish_reason":null}],"obfuscation":"IjjlKYJ6IOL"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" on"},"logprobs":null,"finish_reason":null}],"obfuscation":"lXuS"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" specific"},"logprobs":null,"finish_reason":null}],"obfuscation":"hsEzU0PFcUwynR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" aspects"},"logprobs":null,"finish_reason":null}],"obfuscation":"9yGLWEtHWTRm3ha"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"obfuscation":"FHzF"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":" AI"},"logprobs":null,"finish_reason":null}],"obfuscation":"HAaR"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"obfuscation":"7ZkSmM"}
+//
+//        data: {"id":"chatcmpl-DJrZdgYFl10tw2xQcBXzlWkW4RuhT","object":"chat.completion.chunk","created":1773626541,"model":"gpt-4.1-mini-2025-04-14","service_tier":"default","system_fingerprint":"fp_a1611518a7","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"obfuscation":"U"}
+//
+//        data: [DONE]
+
+    }
+
+
+    public static void claudeTest() throws Exception {
+
+        String apiKey = "";
+        String anthropicVersion = "2023-06-01";
+        Integer maxTokens = 1024;
+        String url = "https://api.anthropic.com/v1/messages";
+        JSONObject params = new JSONObject();
+        params.put("model", "claude-sonnet-4-20250514");
+        params.put("max_tokens", maxTokens);
+        params.put("stream", true);
+        JSONArray messages = new JSONArray();
+        JSONObject message1 = new JSONObject();
+        message1.put("role", "user");
+        message1.put("content", "Introduction to AI");
+        messages.add(message1);
+        params.put("messages", messages);
+        log.info("url:{}, params:{}", url, params);
+        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), JSONObject.toJSONString(params));
+        Request request = new Request.Builder()
+                .post(requestBody)
+                .header("x-api-key", apiKey)
+                .header("anthropic-version", anthropicVersion)
+                .header("content-type", "application/json")
+                .url(url).build();
+
+        try (Response response = CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                log.error("Model api error: http-code={}, msg={}, url={}",
+                        response.code(),
+                        response.message(),
+                        url
+                );
+
+                if (response.code() == HttpStatus.SC_UNAUTHORIZED
+                        || response.code() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+                    throw new IOException("Unexpected code " + response);
+                } else {
+                    return;
+                }
+            }
+
+            BufferedSource source = response.body().source();
+            StringBuilder responseBuilder = new StringBuilder();
+
+//                event: message_start
+//                data: {...}
+//
+//                event: content_block_delta
+//                data: {"delta":{"text":"人工"}}
+//
+//                event: content_block_delta
+//                data: {"delta":{"text":"智能"}}
+//
+//                event: message_stop
+//                data: {}
+            while (!source.exhausted()) {
+                String line = source.readUtf8Line();
+                System.out.println(line);
+                if (line != null && line.equals("event: message_stop")) {
+                    break; // 流式响应结束
+                }
+                if (line != null && line.startsWith("data: ")) {
+                    String jsonData = line.substring(5).trim(); // 去掉 "data: " 前缀
+                    if (jsonData.equals("{}")) {
+                        break; // 流式响应结束
+                    }
+
+                    JSONObject jsonResponse = JSON.parseObject(jsonData);
+                    JSONObject message = jsonResponse.getJSONObject("delta"); // 注意:流式响应中消息在 "delta" 字段中
+
+                    if (null != message && message.containsKey("text")) {
+                        String speechContent = message.getString("text");
+                        if (StringUtils.isNotEmpty(speechContent)) {
+                            responseBuilder.append(speechContent);
+                        }
+                    }
+                }
+            }
+            String answer = responseBuilder.toString();
+            log.info("answer:{}", answer);
+        }
+//
+//        event: message_start
+//        data: {"type":"message_start","message":{"model":"claude-sonnet-4-20250514","id":"msg_01EhrU93VJ2N89fHjgxXnnpZ","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}}    }
+//
+//        event: content_block_start
+//        data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
+//
+//        event: ping
+//        data: {"type": "ping"}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"#"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Introduction to"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Artificial Intelligence ("}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"AI)\n\n## What is AI?"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\nArtificial Intelligence refers to computer"}     }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" systems that can perform tasks typically requiring human"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" intelligence."}         }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" These tasks"}      }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" include:\n- Learning"}      }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" reasoning\n- Problem-solving\n-"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Pattern"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" recognition\n- Understanding"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" language\n- Making decisions\n\n## Types"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" of AI\n\n### **"}         }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Narrow AI ("}      }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Weak"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" AI)**\n-"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Designed"}     }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for"}               }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" specific tasks"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Most"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" AI"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" today"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" falls"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" into this category\n- Examples: Virtual"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" assistants, recommendation systems,"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" chess"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" programs"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n### **General AI (Strong AI"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":")**\n- Hypoth"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"etical AI with"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" human-level intelligence across"}         }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" all domains\n- Can"}               }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" understand"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":", learn, and apply knowledge broadly"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Does"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" not currently"}     }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" exist\n\n###"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" **"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Superint"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"elligence"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**\n- Theoretical"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" AI"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" that surpasses human intelligence\n-"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Subject"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" of"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" ongoing"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" debate and research"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n## Key AI"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Technologies"}    }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n### **Machine Learning ("} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ML)**\n- Systems"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" that improve"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" through experience\n- Learns"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" patterns"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" from data without"}      }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" explicit programming"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n### **Deep"}     }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Learning**\n- Uses"}               }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" artificial"}       }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" neural networks with"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" multiple"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" layers\n- Exc"}    }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"els at image"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" recognition, natural language processing\n\n### **"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Natural Language Processing (NLP)**"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Enables computers to understand and generate"}       }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" human language\n- Powers"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" chat"}       }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"bots, translation"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" services, voice"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" assistants\n\n##"}             }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Current"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Applications"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n-"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" **Healthcare"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**: Medical"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" diagnosis, drug discovery\n- **Transportation"}               }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**: Autonomous vehicles, route"}         }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" optimization\n- **Finance"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**: Fraud detection, algorithmic trading"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- **Entertainment"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**: Content recommendations"}    }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":", game"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" AI"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- **Business"}       }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"**: Customer service, data"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" analysis\n\n## Benefits"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Challenges\n\n### **Benefits**"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Increased"}               }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" efficiency"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and productivity"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Enhanced"}       }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" decision-making"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Automation of repetitive tasks"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- New"}   }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" insights"} }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" from"}        }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" data analysis"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n### **Challenges**\n-"}}
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Job"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" displacement concerns"}            }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n- Privacy"}          }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and security issues\n- Bias in"}     }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" AI"}              }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" systems\n- Ethical considerations"}    }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\nWould"}           }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" you like me to explore"}         }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" any particular"}  }
+//
+//        event: content_block_delta
+//        data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" aspect of AI in more detail?"}              }
+//
+//        event: content_block_stop
+//        data: {"type":"content_block_stop","index":0   }
+//
+//        event: message_delta
+//        data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":10,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":406}       }
+//
+//        event: message_stop
+//
+
+    }
+}

+ 140 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcFirewalldController.java

@@ -0,0 +1,140 @@
+package com.ruoyi.cc.controller;
+
+import java.util.List;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.cc.domain.CcParams;
+import com.ruoyi.cc.domain.FirewallRule;
+import com.ruoyi.cc.service.IFirewallService;
+import com.ruoyi.common.utils.ExceptionUtil;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ *  firewalld Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@Controller
+@RequestMapping("/system/firewalld")
+public class CcFirewalldController extends BaseController
+{
+    private String prefix = "system/firewalld";
+
+    @Autowired
+    private IFirewallService firewallService;
+
+    @RequiresPermissions("system:firewalld:view")
+    @GetMapping()
+    public String firewalld()
+    {
+        return prefix + "/firewalld";
+    }
+
+    /**
+     * list firewall rules
+     */
+    @RequiresPermissions("system:firewalld:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list() throws Exception
+    {
+        startPage();
+        List<FirewallRule> list = firewallService.getAllList();
+        return getDataTable(list);
+    }
+
+    @RequiresPermissions("system:firewalld:export")
+    @Log(title = "firewalld rules", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export() throws Exception {
+        List<FirewallRule> list = firewallService.getAllList();
+        ExcelUtil<FirewallRule> util = new ExcelUtil<FirewallRule>(FirewallRule.class);
+        return util.exportExcel(list, "firewalld rules");
+    }
+
+    @GetMapping("/add")
+    public String add(ModelMap mmap)
+    {
+        FirewallRule rule = new FirewallRule();
+        mmap.put("firewallRule", rule);
+        return prefix + "/add";
+    }
+
+    @RequiresPermissions("system:firewalld:add")
+    @Log(title = "firewalld add rules", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(FirewallRule rule) throws Exception {
+        AjaxResult result = toAjax(firewallService.add(rule));
+        String reloadRsp = firewallService.reloadFirewalld();
+        result.put("firewalld_response", reloadRsp);
+        return result;
+    }
+
+    @GetMapping("/edit/{id}")
+    public String edit(@PathVariable("id") String id, ModelMap mmap) {
+        try {
+            List<FirewallRule> list = firewallService.getAllList();
+            for (FirewallRule rule: list) {
+                if (id.equals(rule.getId())) {
+                    mmap.put("firewallRule", rule);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return prefix + "/edit";
+    }
+
+
+    @RequiresPermissions("system:firewalld:edit")
+    @Log(title = "firewalld rule edit", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(FirewallRule rule) throws Exception {
+        AjaxResult result = toAjax(firewallService.update(rule));
+        String reloadRsp = firewallService.reloadFirewalld();
+        result.put("firewalld_response", reloadRsp);
+        return result;
+    }
+
+    @RequiresPermissions("system:firewalld:remove")
+    @Log(title = "firewalld rules remove", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids) {
+        try {
+            if (StringUtils.isNotEmpty(ids)) {
+                List<FirewallRule> list = firewallService.getAllList();
+                for (String id: ids.split(",")) {
+                    logger.info("待删除id:{}", id);
+                    for (FirewallRule firewallRule: list) {
+                        logger.info("规则:{}", JSONObject.toJSONString(firewallRule));
+                        if (id.equals(firewallRule.getId())) {
+                            firewallService.delete(firewallRule);
+                        }
+                    }
+                }
+            }
+            firewallService.reloadFirewalld();
+        } catch (Exception e) {
+            logger.error(ExceptionUtil.getExceptionMessage(e));
+        }
+       return toAjax(1);
+    }
+
+}

+ 8 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcGatewaysController.java

@@ -101,6 +101,10 @@ public class CcGatewaysController extends BaseController
     public AjaxResult addSave(CcGateways ccGateways)
     {
 
+        if (null != ccGateways.getRegister() && ccGateways.getRegister() == 0) {
+            ccGateways.setAuthUsername("");
+            ccGateways.setAuthPassword("");
+        }
         if (null != ccGateways.getRegister() && ccGateways.getRegister() == 2) {
             ccGateways.setAuthUsername(ccGateways.getAuthUsername2());
             ccGateways.setAuthPassword("");
@@ -193,6 +197,10 @@ public class CcGatewaysController extends BaseController
     @ResponseBody
     public AjaxResult editSave(CcGateways ccGateways)
     {
+        if (null != ccGateways.getRegister() && ccGateways.getRegister() == 0) {
+            ccGateways.setAuthUsername("");
+            ccGateways.setAuthPassword("");
+        }
         if (null != ccGateways.getRegister() && ccGateways.getRegister() == 2) {
             ccGateways.setAuthUsername(ccGateways.getAuthUsername2());
             ccGateways.setAuthPassword("");

+ 24 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcIvrController.java

@@ -111,6 +111,18 @@ public class CcIvrController extends BaseController
         if ("0".equals(ccIvr.getParentNodeId()) || StringUtils.isBlank(ccIvr.getDigit())) {
             ccIvr.setDigit("-1");
         }
+        if (StringUtils.isBlank(ccIvr.getTtsProvider())) {
+            ccIvr.setTtsProvider("");
+        }
+        if (StringUtils.isBlank(ccIvr.getVoiceCode())) {
+            ccIvr.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccIvr.getTtsLanguageCode())) {
+            ccIvr.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccIvr.getTtsModels())) {
+            ccIvr.setTtsModels("");
+        }
         return toAjax(ccIvrService.insertCcIvr(ccIvr));
     }
 
@@ -175,6 +187,18 @@ public class CcIvrController extends BaseController
         if ("0".equals(ccIvr.getParentNodeId())) {
             ccIvr.setDigit("-1");
         }
+        if (StringUtils.isBlank(ccIvr.getTtsProvider())) {
+            ccIvr.setTtsProvider("");
+        }
+        if (StringUtils.isBlank(ccIvr.getVoiceCode())) {
+            ccIvr.setVoiceCode("");
+        }
+        if (StringUtils.isBlank(ccIvr.getTtsLanguageCode())) {
+            ccIvr.setTtsLanguageCode("zh-CN");
+        }
+        if (StringUtils.isBlank(ccIvr.getTtsModels())) {
+            ccIvr.setTtsModels("");
+        }
         return toAjax(ccIvrService.updateCcIvr(ccIvr));
     }
 

+ 14 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/controller/CcParamsController.java

@@ -4,6 +4,7 @@ import java.util.List;
 
 import com.ruoyi.cc.domain.CcParams;
 import com.ruoyi.cc.service.ICcParamsService;
+import com.ruoyi.common.utils.MessageUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.http.HttpUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -50,6 +51,18 @@ public class CcParamsController extends BaseController
     {
         startPage();
         List<CcParams> list = ccParamsService.selectCcParamsList(ccParams);
+        for (CcParams data: list) {
+            String paramName = "";
+            try {
+                paramName = MessageUtils.message("_cc.params." + data.getParamCode());
+            } catch (Exception e) {
+
+            }
+            if (StringUtils.isBlank(paramName)) {
+                paramName = data.getParamName();
+            }
+            data.setParamName(paramName);
+        }
         return getDataTable(list);
     }
 
@@ -140,6 +153,7 @@ public class CcParamsController extends BaseController
         if (StringUtils.isBlank(defaultValue)) {
             defaultValue = "";
         }
+
         return AjaxResult.success("", ccParamsService.getParamValueByCode(paramCode, defaultValue));
     }
 }

+ 210 - 11
ruoyi-admin/src/main/java/com/ruoyi/cc/controller/FsConfController.java

@@ -2,19 +2,22 @@ package com.ruoyi.cc.controller;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.aicall.domain.CcCallPhone;
+import com.ruoyi.aicall.service.ICcCallPhoneService;
+import com.ruoyi.aicall.service.ICcInboundLlmAccountService;
+import com.ruoyi.cc.domain.CcInboundCdr;
+import com.ruoyi.cc.domain.CcOutboundCdr;
 import com.ruoyi.cc.domain.CcParams;
 import com.ruoyi.cc.model.FsConfProfile;
 import com.ruoyi.cc.model.FsMod;
 import com.ruoyi.cc.model.ProfileRegExtnumModel;
 import com.ruoyi.cc.model.ProfileStatusModel;
-import com.ruoyi.cc.service.ICcGatewaysService;
-import com.ruoyi.cc.service.ICcParamsService;
-import com.ruoyi.cc.service.IFsConfService;
-import com.ruoyi.cc.service.IFsVariablesService;
+import com.ruoyi.cc.service.*;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.utils.CommonUtils;
+import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.web.domain.server.Sys;
 import link.thingscloud.freeswitch.esl.EslConnectionUtil;
@@ -23,6 +26,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.ModelMap;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -59,6 +63,12 @@ public class FsConfController extends BaseController {
     private ICcParamsService ccParamsService;
     @Autowired
     private ICcGatewaysService ccGatewaysService;
+    @Autowired
+    private ICcCallPhoneService callPhoneService;
+    @Autowired
+    private ICcInboundCdrService inboundCdrService;
+    @Autowired
+    private ICcOutboundCdrService outboundCdrService;
 
 
     /**
@@ -666,9 +676,32 @@ public class FsConfController extends BaseController {
         String ccLogs = fsConfService.getLogs(uuid, ccLogFiles, "cc");
         if (StringUtils.isNotEmpty(fsLogs)
                 && StringUtils.isBlank(ccLogs)) {
-            String ccHisLogFiles = ccLogFiles.replace(".log", ".*.log");
-            logger.info(ccHisLogFiles);
-            ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+//            String ccHisLogFiles = ccLogFiles.replace(".log", ".*.log");
+//            logger.info(ccHisLogFiles);
+//            ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+            List<CcCallPhone> callPhoneList = callPhoneService.selectCcCallPhoneList(new CcCallPhone().setUuid(uuid));
+            if (!CollectionUtils.isEmpty(callPhoneList)) {
+                String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(callPhoneList.get(0).getCalloutTime()));
+                String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                logger.info(ccHisLogFiles);
+                ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+            } else {
+                List<CcInboundCdr> inboundCdrList = inboundCdrService.selectCcInboundCdrList(new CcInboundCdr().setUuid(uuid));
+                if (!CollectionUtils.isEmpty(inboundCdrList)) {
+                    String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(inboundCdrList.get(0).getInboundTime()));
+                    String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                    logger.info(ccHisLogFiles);
+                    ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+                } else {
+                    List<CcOutboundCdr> outboundCdrList = outboundCdrService.selectCcOutboundCdrList(new CcOutboundCdr().setUuid(uuid));
+                    if (!CollectionUtils.isEmpty(outboundCdrList)) {
+                        String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(outboundCdrList.get(0).getStartTime()));
+                        String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                        logger.info(ccHisLogFiles);
+                        ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+                    }
+                }
+            }
         }
         result.put("ccLogs", ccLogs); // callcenter日志
         return AjaxResult.success("success", result);
@@ -701,10 +734,34 @@ public class FsConfController extends BaseController {
         String ccLogFiles = ccParamsService.getParamValueByCode("cc_log_file_path", "");
         String ccLogs = fsConfService.getLogs(uuid, ccLogFiles, "cc");
 
-        if (StringUtils.isNotEmpty(fsLogs) && StringUtils.isBlank(ccLogs)) {
-            String ccHisLogFiles = ccLogFiles.replace(".log", ".*.log");
-            logger.info(ccHisLogFiles);
-            ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+        if (StringUtils.isNotEmpty(fsLogs)
+                && StringUtils.isBlank(ccLogs)) {
+//            String ccHisLogFiles = ccLogFiles.replace(".log", ".*.log");
+//            logger.info(ccHisLogFiles);
+//            ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+            List<CcCallPhone> callPhoneList = callPhoneService.selectCcCallPhoneList(new CcCallPhone().setUuid(uuid));
+            if (!CollectionUtils.isEmpty(callPhoneList)) {
+                String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(callPhoneList.get(0).getCalloutTime()));
+                String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                logger.info(ccHisLogFiles);
+                ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+            } else {
+                List<CcInboundCdr> inboundCdrList = inboundCdrService.selectCcInboundCdrList(new CcInboundCdr().setUuid(uuid));
+                if (!CollectionUtils.isEmpty(inboundCdrList)) {
+                    String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(inboundCdrList.get(0).getInboundTime()));
+                    String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                    logger.info(ccHisLogFiles);
+                    ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+                } else {
+                    List<CcOutboundCdr> outboundCdrList = outboundCdrService.selectCcOutboundCdrList(new CcOutboundCdr().setUuid(uuid));
+                    if (!CollectionUtils.isEmpty(outboundCdrList)) {
+                        String date = DateUtils.parseDateToStr("yyyy-MM-dd", new Date(outboundCdrList.get(0).getStartTime()));
+                        String ccHisLogFiles = ccLogFiles.replace(".log", "." + date + ".0.log");
+                        logger.info(ccHisLogFiles);
+                        ccLogs = fsConfService.getLogs(uuid, ccHisLogFiles, "cc");
+                    }
+                }
+            }
         }
 
         String errorLogFiles = ccLogFiles.replace("easycallcenter365.log", "easycallcenter365-ERROR.log");
@@ -1037,4 +1094,146 @@ public class FsConfController extends BaseController {
         return list;
     }
 
+    /**
+     * ASR(亚马逊)参数配置
+     * @return
+     */
+    @GetMapping(value = "/awsasrconf")
+    public String awsAsrConf() {
+        return "cc/awsasrconf/awsasrconf";
+    }
+
+    /**
+     * 获取亚马逊ASR配置
+     * @return
+     */
+    @GetMapping(value = "/getAwsAsrConf")
+    @ResponseBody
+    public AjaxResult getAwsAsrConf() {
+        String asrFileName = "/autoload_configs/aws_asr.conf.xml";
+        return getConfigFileJsonData(asrFileName, 5);
+    }
+
+    /**
+     * 保存ASR配置
+     * @param params
+     * @return
+     */
+    @PostMapping(value = "/setAwsAsrConf")
+    @ResponseBody
+    public AjaxResult setAwsAsrConf(@RequestBody JSONArray params) {
+        String asrFileName = "/autoload_configs/aws_asr.conf.xml";
+        String moduleName = "mod_aws_asr";
+        return saveAndReloadAsrModule(asrFileName, moduleName, params, "aws");
+    }
+
+
+    /**
+     * TTS(亚马逊)参数配置
+     * @return
+     */
+    @RequiresPermissions("cc:awsttsconf:view")
+    @GetMapping(value = "/awsttsconf")
+    public String awsTtsConf() {
+        return "cc/awsttsconf/awsttsconf";
+    }
+
+    /**
+     * 获取亚马逊tts配置
+     * @return
+     */
+    @GetMapping(value = "/getAwsTtsConf")
+    @ResponseBody
+    public AjaxResult getAwsTtsConf() {
+        String ttsFileName = "/autoload_configs/aws_tts.conf.xml";
+        return getConfigFileJsonData(ttsFileName, 5);
+    }
+
+
+    /**
+     * 保存TTS配置
+     * @param params
+     * @return
+     */
+    @PostMapping(value = "/setAwsTtsConf")
+    @ResponseBody
+    public AjaxResult setAwsTtsConf(@RequestBody JSONArray params) {
+        String asrFileName = "/autoload_configs/aws_tts.conf.xml";
+        String moduleName = "mod_aws_tts";
+        AjaxResult result = saveAndReloadTtsModule("aws-tts-account-json",
+                asrFileName, moduleName, params);
+        return result;
+    }
+
+
+    /**
+     * ASR(deepgram)参数配置
+     * @return
+     */
+    @GetMapping(value = "/deepgramasrconf")
+    public String deepgramAsrConf() {
+        return "cc/deepgramasrconf/deepgramasrconf";
+    }
+
+    /**
+     * 获取Deepgram ASR配置
+     * @return
+     */
+    @GetMapping(value = "/getDeepgramAsrConf")
+    @ResponseBody
+    public AjaxResult getDeepgramAsrConf() {
+        String asrFileName = "/autoload_configs/deepgram_asr.conf.xml";
+        return getConfigFileJsonData(asrFileName, 5);
+    }
+
+    /**
+     * 保存ASR配置
+     * @param params
+     * @return
+     */
+    @PostMapping(value = "/setDeepgramAsrConf")
+    @ResponseBody
+    public AjaxResult setDeepgramAsrConf(@RequestBody JSONArray params) {
+        String asrFileName = "/autoload_configs/deepgram_asr.conf.xml";
+        String moduleName = "mod_deepgram_asr";
+        return saveAndReloadAsrModule(asrFileName, moduleName, params, "deepgram");
+    }
+
+
+    /**
+     * TTS(deepgram)参数配置
+     * @return
+     */
+    @RequiresPermissions("cc:deepgramttsconf:view")
+    @GetMapping(value = "/deepgramttsconf")
+    public String deepgramTtsConf() {
+        return "cc/deepgramttsconf/deepgramttsconf";
+    }
+
+    /**
+     * 获取deepgram tts配置
+     * @return
+     */
+    @GetMapping(value = "/getDeepgramTtsConf")
+    @ResponseBody
+    public AjaxResult getDeepgramTtsConf() {
+        String ttsFileName = "/autoload_configs/deepgram_tts.conf.xml";
+        return getConfigFileJsonData(ttsFileName, 5);
+    }
+
+
+    /**
+     * 保存TTS配置
+     * @param params
+     * @return
+     */
+    @PostMapping(value = "/setDeepgramTtsConf")
+    @ResponseBody
+    public AjaxResult setDeepgramTtsConf(@RequestBody JSONArray params) {
+        String asrFileName = "/autoload_configs/deepgram_tts.conf.xml";
+        String moduleName = "mod_deepgram_tts";
+        AjaxResult result = saveAndReloadTtsModule("deepgram-tts-account-json",
+                asrFileName, moduleName, params);
+        return result;
+    }
 }

+ 4 - 4
ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcCustInfo.java

@@ -3,7 +3,10 @@ package com.ruoyi.cc.domain;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.experimental.Accessors;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
 
 import java.io.Serializable;
 import java.util.Date;
@@ -77,8 +80,5 @@ public class CcCustInfo implements Serializable {
 
     // 详情带的参数
     private List<CcCustCallRecord> callRecordList;
-    //登陆账号
-    private String opNum;
-    //用户名称
-    private String userName;
+
 }

+ 0 - 6
ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcGateways.java

@@ -11,7 +11,6 @@ import com.ruoyi.common.core.domain.BaseEntity;
 import java.io.Serializable;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -80,10 +79,5 @@ public class CcGateways implements Serializable {
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private Map<String, Object> params = new HashMap<>();
 
-    private Integer pageSize;
-
-    private Integer pageNum;
-
-    private List<Long> gatewayIds;
 
 }

+ 6 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcIvr.java

@@ -108,6 +108,9 @@ public class CcIvr implements Serializable {
     /** hangupTips */
     private String hangupTips;
 
+    /** Language of the tts */
+    private String ttsLanguageCode;
+
 
     private String ttsTextfileUrl;
 
@@ -121,4 +124,7 @@ public class CcIvr implements Serializable {
     private String pressKeyInvalidTipsWav;
 
     private String hangupTipsWav;
+
+    /** Models of the voice */
+    private String ttsModels;
 }

+ 0 - 5
ruoyi-admin/src/main/java/com/ruoyi/cc/domain/CcOutboundCdr.java

@@ -71,9 +71,4 @@ public class CcOutboundCdr implements Serializable {
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private Map<String, Object> params;
 
-    private String startTimeStr;
-    private String answeredTimeStr;
-    private String endTimeStr;
-    private String timeLenSec;
-    private String timeLenValidStr;
 }

+ 22 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/domain/FirewallRule.java

@@ -0,0 +1,22 @@
+package com.ruoyi.cc.domain;
+
+import lombok.Data;
+
+@Data
+public class FirewallRule {
+
+    private String id;
+
+    private String protocol;
+
+    private Integer portStart;
+
+    private Integer portEnd;
+
+    private boolean enableFromSource;
+
+    private String fromSource;
+
+    private Integer allowEdit;
+
+}

+ 0 - 9
ruoyi-admin/src/main/java/com/ruoyi/cc/mapper/CcExtNumMapper.java

@@ -64,13 +64,4 @@ public interface CcExtNumMapper
      * @return
      */
     List<CcExtNum> selectUnBindCcExtNumList();
-
-    /**
-     * 根据用户名修改分机绑定
-     * @param extNum
-     * @return
-     */
-    int updateCcExtNumByUserCode(CcExtNum extNum);
-
-    int cleanCcExtNumByUserCode(String loginName);
 }

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/cc/mapper/CcParamsMapper.java

@@ -66,5 +66,5 @@ public interface CcParamsMapper
      * @param paramCode
      * @param paramValue
      */
-    void updateParamsValue(@Param("paramCode") String paramCode, @Param("paramValue") String paramValue);
+    int updateParamsValue(@Param("paramCode") String paramCode, @Param("paramValue") String paramValue);
 }

+ 0 - 9
ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcExtNumService.java

@@ -89,13 +89,4 @@ public interface ICcExtNumService
      * @return
      */
     List<CcExtNum> selectUnBindCcExtNumList();
-
-    /**
-     * 根据用户名修改分机绑定
-     * @param extNum
-     * @return
-     */
-    int updateCcExtNumByUserCode(CcExtNum extNum);
-
-    int cleanCcExtNumByUserCode(String loginName);
 }

+ 0 - 2
ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcOutboundCdrService.java

@@ -58,6 +58,4 @@ public interface ICcOutboundCdrService
      * @return 结果
      */
     public int deleteCcOutboundCdrById(String id);
-
-    List<CcOutboundCdr> selectCcOutboundCdrYlrzList(CcOutboundCdr outboundCdr);
 }

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/cc/service/ICcParamsService.java

@@ -72,7 +72,7 @@ public interface ICcParamsService
      * @param paramCode
      * @param paramValue
      */
-    void updateParamsValue(String paramCode, String paramValue);
+    int updateParamsValue(String paramCode, String paramValue);
 
     /**
      *

+ 17 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/service/IFirewallService.java

@@ -0,0 +1,17 @@
+package com.ruoyi.cc.service;
+
+import com.ruoyi.cc.domain.FirewallRule;
+
+import java.util.List;
+
+public interface IFirewallService {
+    boolean add(FirewallRule rule) throws Exception;
+
+    List<FirewallRule> getAllList() throws Exception;
+
+    boolean delete(FirewallRule rule) throws  Exception;
+
+    boolean update(FirewallRule rule) throws Exception;
+
+    String reloadFirewalld()  throws Exception ;
+}

+ 2 - 10
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcExtNumServiceImpl.java

@@ -10,11 +10,13 @@ import com.auth0.jwt.algorithms.Algorithm;
 import com.ruoyi.cc.service.ICcParamsService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import com.ruoyi.cc.mapper.CcExtNumMapper;
 import com.ruoyi.cc.domain.CcExtNum;
 import com.ruoyi.cc.service.ICcExtNumService;
 import com.ruoyi.common.core.text.Convert;
+import org.springframework.util.CollectionUtils;
 
 /**
  * 【请填写功能名称】Service业务层处理
@@ -160,14 +162,4 @@ public class CcExtNumServiceImpl implements ICcExtNumService
     public List<CcExtNum> selectUnBindCcExtNumList() {
         return ccExtNumMapper.selectUnBindCcExtNumList();
     }
-
-    @Override
-    public int updateCcExtNumByUserCode(CcExtNum extNum) {
-        return ccExtNumMapper.updateCcExtNumByUserCode(extNum);
-    }
-
-    @Override
-    public int cleanCcExtNumByUserCode(String loginName) {
-        return ccExtNumMapper.cleanCcExtNumByUserCode(loginName);
-    }
 }

+ 0 - 56
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcOutboundCdrServiceImpl.java

@@ -49,14 +49,6 @@ public class CcOutboundCdrServiceImpl implements ICcOutboundCdrService
         if (null == params) {
             params = new HashMap<>();
         }
-        if (null != params.get("calloutTimeStart")
-                && !"".equals(params.get("calloutTimeStart"))) {
-            params.put("calloutTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeStart")).getTime());
-        }
-        if (null != params.get("calloutTimeEnd")
-                && !"".equals(params.get("calloutTimeEnd"))) {
-            params.put("calloutTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeEnd")).getTime());
-        }
         if (null != params.get("startTimeStart")
                 && !"".equals(params.get("startTimeStart"))) {
             params.put("startTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("startTimeStart")).getTime());
@@ -102,54 +94,6 @@ public class CcOutboundCdrServiceImpl implements ICcOutboundCdrService
         ccOutboundCdr.setParams(params);
         return ccOutboundCdrMapper.selectCcOutboundCdrList(ccOutboundCdr);
     }
-    /**
-     * 查询外呼记录列表
-     *
-     * @param ccOutboundCdr 外呼记录
-     * @return 外呼记录
-     */
-    @Override
-    public List<CcOutboundCdr> selectCcOutboundCdrYlrzList(CcOutboundCdr ccOutboundCdr)
-    {
-        Map<String, Object> params = ccOutboundCdr.getParams();
-        if (null == params) {
-            params = new HashMap<>();
-        }
-        if (null != params.get("timeLenStart")
-                && !"".equals(params.get("timeLenStart"))) {
-            params.put("timeLenStart", Double.valueOf((String)params.get("timeLenStart")) * 60 * 1000L);
-        }
-        if (null != params.get("timeLenEnd")
-                && !"".equals(params.get("timeLenEnd"))) {
-            params.put("timeLenEnd", Double.valueOf((String)params.get("timeLenEnd")) * 60 * 1000L);
-        }
-        if (null != params.get("calloutTimeStart")
-                && !"".equals(params.get("calloutTimeStart"))) {
-            params.put("calloutTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeStart")).getTime());
-        }
-        if (null != params.get("calloutTimeEnd")
-                && !"".equals(params.get("calloutTimeEnd"))) {
-            params.put("calloutTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("calloutTimeEnd")).getTime());
-        }
-        if (null != params.get("answeredTimeStart")
-                && !"".equals(params.get("answeredTimeStart"))) {
-            params.put("answeredTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("answeredTimeStart")).getTime());
-        }
-        if (null != params.get("answeredTimeEnd")
-                && !"".equals(params.get("answeredTimeEnd"))) {
-            params.put("answeredTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("answeredTimeEnd")).getTime());
-        }
-        if (null != params.get("endTimeStart")
-                && !"".equals(params.get("endTimeStart"))) {
-            params.put("endTimeStart", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("endTimeStart")).getTime());
-        }
-        if (null != params.get("endTimeEnd")
-                && !"".equals(params.get("endTimeEnd"))) {
-            params.put("endTimeEnd", DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", (String)params.get("endTimeEnd")).getTime());
-        }
-        ccOutboundCdr.setParams(params);
-        return ccOutboundCdrMapper.selectCcOutboundCdrList(ccOutboundCdr);
-    }
 
     /**
      * 新增外呼记录

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/CcParamsServiceImpl.java

@@ -151,8 +151,8 @@ public class CcParamsServiceImpl implements ICcParamsService
     }
 
     @Override
-    public void updateParamsValue(String paramCode, String paramValue) {
-        ccParamsMapper.updateParamsValue(paramCode, paramValue);
+    public int updateParamsValue(String paramCode, String paramValue) {
+        return ccParamsMapper.updateParamsValue(paramCode, paramValue);
     }
 
     @Override

+ 53 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/FirewallServiceImpl.java

@@ -0,0 +1,53 @@
+package com.ruoyi.cc.service.impl;
+
+import com.ruoyi.cc.domain.FirewallRule;
+import com.ruoyi.cc.service.ICcParamsService;
+import com.ruoyi.cc.service.IFirewallService;
+import com.ruoyi.cc.utils.FirewalldXmlUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@Slf4j
+public class FirewallServiceImpl implements IFirewallService {
+
+    @Autowired
+    private ICcParamsService ccParamsService;
+
+    @Autowired
+    private FirewalldXmlUtil firewalldXmlUtil;
+
+    @Override
+    public boolean add(FirewallRule rule) throws Exception {
+        return  firewalldXmlUtil.addRule(rule);
+    }
+
+    @Override
+    public List<FirewallRule> getAllList() throws Exception {
+        return firewalldXmlUtil.getAllRules();
+    }
+
+    @Override
+    public boolean delete(FirewallRule rule) throws  Exception {
+         return firewalldXmlUtil.delete(rule);
+    }
+
+    @Override
+    public boolean update(FirewallRule rule) throws Exception {
+        List<FirewallRule> list = getAllList();
+        for (FirewallRule firewallRule: list) {
+            if (firewallRule.getId().equals(rule.getId())) {
+                delete(firewallRule);
+            }
+        }
+        return add(rule);
+    }
+
+    @Override
+    public String reloadFirewalld()  throws Exception  {
+        return ccParamsService.reloadParams();
+    }
+}

+ 0 - 1
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/FsConfServiceImpl.java

@@ -534,7 +534,6 @@ public class FsConfServiceImpl implements IFsConfService {
         commands.add("sh");
         commands.add("-c");
         if (StringUtils.isBlank(uuid)) {
-//            commands.add("cat " + logFile);
             commands.add("tail -n 20 " + logFile);
         } else {
             commands.add("cat " + logFile + " | grep '" + uuid + "'");

+ 304 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/utils/FirewalldXmlUtil.java

@@ -0,0 +1,304 @@
+package com.ruoyi.cc.utils;
+
+import com.ruoyi.cc.domain.FirewallRule;
+import com.ruoyi.cc.service.ICcParamsService;
+import io.netty.util.internal.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import sun.awt.AppContext;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Service
+@Slf4j
+public class FirewalldXmlUtil {
+
+    @Autowired
+    private  ICcParamsService ccParamsService;
+    @Value("${server.port:8899}")
+    private Integer serverPort;
+
+    private  String templateFilePath = "";
+    private  String getFirewallTemplateFilePath(){
+        if(StringUtil.isNullOrEmpty(templateFilePath)) {
+            String fsConfDir = ccParamsService.getParamValueByCode("fs_conf_directory", "");
+            templateFilePath = String.format("%s%s", fsConfDir, "/autoload_configs/firewalld-template.xml");
+        }
+        return templateFilePath;
+    }
+
+    public  List<FirewallRule> getAllRules() throws Exception {
+
+        List<FirewallRule> list = new ArrayList<>();
+
+        Document doc = load();
+
+        Element root = doc.getDocumentElement(); // <zone>
+
+        // 遍历 zone 的直接子节点
+        NodeList children = root.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node node = children.item(i);
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                Element elem = (Element) node;
+                String tagName = elem.getTagName();
+
+                if ("port".equals(tagName)) {
+                    // 全局端口(直接子节点)
+                    FirewallRule rule = parsePortElement(elem, null);
+                    if (rule != null) {
+
+                        rule.setAllowEdit(1);
+                        if (serverPort.equals(rule.getPortStart()) && serverPort.equals(rule.getPortEnd())) {
+                            rule.setAllowEdit(0);
+                        }
+                        rule.setId(rule.getProtocol() + "-" + rule.getPortStart() + "-" + rule.getPortEnd());
+
+                        list.add(rule);
+                    }
+                } else if ("rule".equals(tagName)) {
+                    // rule 内的端口
+                    FirewallRule rule = parseRuleElement(elem);
+                    if (rule != null) {
+
+                        rule.setAllowEdit(1);
+                        if (serverPort.equals(rule.getPortStart()) && serverPort.equals(rule.getPortEnd())) {
+                            rule.setAllowEdit(0);
+                        }
+                        rule.setId(rule.getProtocol() + "-" + rule.getPortStart() + "-" + rule.getPortEnd()+ "-" + rule.getFromSource());
+                        list.add(rule);
+                    }
+                }
+            }
+        }
+        return list;
+    }
+
+    private static FirewallRule parsePortElement(Element portElem, String sourceAddress) {
+        String portStr = portElem.getAttribute("port");
+        String protocol = portElem.getAttribute("protocol");
+
+        if (portStr.isEmpty() || protocol.isEmpty()) {
+            return null;
+        }
+
+        FirewallRule rule = new FirewallRule();
+        rule.setId(UUID.randomUUID().toString());
+        rule.setProtocol(protocol);
+
+        // 解析端口范围
+        parsePortRange(rule, portStr);
+
+        // 设置source相关属性
+        if (sourceAddress != null && !sourceAddress.isEmpty()) {
+            rule.setEnableFromSource(true);
+            rule.setFromSource(sourceAddress);
+        } else {
+            rule.setEnableFromSource(false);
+            rule.setFromSource(null);
+        }
+
+        return rule;
+    }
+
+    private static FirewallRule parseRuleElement(Element ruleElem) {
+        // 获取source地址
+        NodeList sourceNodes = ruleElem.getElementsByTagName("source");
+        String sourceAddress = null;
+        if (sourceNodes.getLength() > 0) {
+            Element sourceElem = (Element) sourceNodes.item(0);
+            sourceAddress = sourceElem.getAttribute("address");
+        }
+
+        // 只获取当前 rule 下的 port(不是全局的)
+        NodeList portNodes = ruleElem.getElementsByTagName("port");
+        if (portNodes.getLength() > 0) {
+            Element portElem = (Element) portNodes.item(0);
+            return parsePortElement(portElem, sourceAddress);
+        }
+
+        return null;
+    }
+
+    private static void parsePortRange(FirewallRule rule, String portStr) {
+        if (portStr.contains("-")) {
+            String[] parts = portStr.split("-");
+            rule.setPortStart(Integer.parseInt(parts[0].trim()));
+            rule.setPortEnd(Integer.parseInt(parts[1].trim()));
+        } else {
+            int port = Integer.parseInt(portStr.trim());
+            rule.setPortStart(port);
+            rule.setPortEnd(port);
+        }
+    }
+
+    public  boolean addRule(FirewallRule rule) throws Exception {
+
+        Document doc = load();
+
+        Element root = doc.getDocumentElement();
+
+        if (rule.isEnableFromSource()) {
+
+            Element ruleNode = doc.createElement("rule");
+            ruleNode.setAttribute("family", "ipv4");
+
+            Element source = doc.createElement("source");
+            source.setAttribute("address", rule.getFromSource());
+
+            Element port = doc.createElement("port");
+            port.setAttribute("protocol", rule.getProtocol());
+
+            if (rule.getPortStart().equals(rule.getPortEnd())) {
+                port.setAttribute("port", String.valueOf(rule.getPortStart()));
+            } else {
+                port.setAttribute("port",
+                        rule.getPortStart() + "-" + rule.getPortEnd());
+            }
+
+            Element accept = doc.createElement("accept");
+
+            ruleNode.appendChild(source);
+            ruleNode.appendChild(port);
+            ruleNode.appendChild(accept);
+
+            root.appendChild(ruleNode);
+
+        } else {
+
+            Element port = doc.createElement("port");
+
+            port.setAttribute("protocol", rule.getProtocol());
+
+            if (rule.getPortStart().equals(rule.getPortEnd())) {
+                port.setAttribute("port", String.valueOf(rule.getPortStart()));
+            } else {
+                port.setAttribute("port",
+                        rule.getPortStart() + "-" + rule.getPortEnd());
+            }
+
+            root.appendChild(port);
+        }
+
+        save(doc);
+        return true;
+    }
+
+    public  boolean delete(FirewallRule ruleParam) throws Exception {
+        String protocol = ruleParam.getProtocol();
+        int portStart = ruleParam.getPortStart();
+        int portEnd = ruleParam.getPortEnd();
+        String sourceIp = ruleParam.getFromSource();
+
+        Document doc = load();
+
+        String targetPort;
+
+        if (portStart == portEnd) {
+            targetPort = String.valueOf(portStart);
+        } else {
+            targetPort = portStart + "-" + portEnd;
+        }
+
+        Element root = doc.getDocumentElement();
+
+        // 删除普通 <port> 规则
+        if (sourceIp == null || sourceIp.trim().isEmpty()) {
+
+            NodeList ports = doc.getElementsByTagName("port");
+
+            for (int i = ports.getLength() - 1; i >= 0; i--) {
+
+                Element port = (Element) ports.item(i);
+
+                if (!port.getParentNode().equals(root)) {
+                    continue;
+                }
+
+                String proto = port.getAttribute("protocol");
+                String portValue = port.getAttribute("port");
+
+                if (protocol.equals(proto) && targetPort.equals(portValue)) {
+
+                    root.removeChild(port);
+                }
+            }
+
+        } else {
+
+            // 删除 <rule> 规则
+            NodeList rules = doc.getElementsByTagName("rule");
+
+            for (int i = rules.getLength() - 1; i >= 0; i--) {
+
+                Element rule = (Element) rules.item(i);
+
+                NodeList sources = rule.getElementsByTagName("source");
+                NodeList ports = rule.getElementsByTagName("port");
+
+                if (sources.getLength() == 0 || ports.getLength() == 0) {
+                    continue;
+                }
+
+                Element source = (Element) sources.item(0);
+                Element port = (Element) ports.item(0);
+
+                String address = source.getAttribute("address");
+                String proto = port.getAttribute("protocol");
+                String portValue = port.getAttribute("port");
+
+                if (sourceIp.equals(address)
+                        && protocol.equals(proto)
+                        && targetPort.equals(portValue)) {
+
+                    root.removeChild(rule);
+                }
+            }
+        }
+
+        save(doc);
+        return true;
+    }
+
+    private  Document load() throws Exception {
+
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+
+        DocumentBuilder builder = factory.newDocumentBuilder();
+
+        return builder.parse(new File(getFirewallTemplateFilePath()));
+    }
+
+    private  boolean save(Document doc) throws Exception {
+
+        TransformerFactory factory = TransformerFactory.newInstance();
+
+        Transformer transformer = factory.newTransformer();
+
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+        DOMSource source = new DOMSource(doc);
+
+        StreamResult result = new StreamResult(new File(getFirewallTemplateFilePath()));
+
+        transformer.transform(source, result);
+
+        return false;
+    }
+}

+ 123 - 0
ruoyi-admin/src/main/java/com/ruoyi/cc/utils/MyFileUtil.java

@@ -0,0 +1,123 @@
+package com.ruoyi.cc.utils;
+
+import com.ruoyi.common.utils.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+
+/**
+ * 文件操作工具类
+ */
+public class MyFileUtil {
+	private static final Logger logger = LoggerFactory.getLogger(MyFileUtil.class);
+	public static boolean writeToLocal(String path, byte[] data) throws IOException {
+		File file = new File(path);
+		if (!file.exists()) {
+			file.createNewFile();
+		}
+		FileOutputStream fop = new FileOutputStream(file);
+		if (!file.exists()) {
+			file.createNewFile();
+		}
+		fop.write(data);
+		fop.flush();
+		fop.close();
+		return true;
+	}
+	/**
+	 * 读取文件内容为二进制数组
+	 * 
+	 * @param filePath
+	 * @return
+	 * @throws IOException
+	 */
+	public static byte[] read2ByteArray(String filePath) throws IOException {
+
+		InputStream in = new FileInputStream(filePath);
+		byte[] data = inputStream2ByteArray(in);
+		in.close();
+
+		return data;
+	}
+
+	/**
+	 * 流转二进制数组
+	 * 
+	 * @param in
+	 * @return
+	 * @throws IOException
+	 */
+	public static byte[] inputStream2ByteArray(InputStream in) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		byte[] buffer = new byte[1024 * 4];
+		int n = 0;
+		while ((n = in.read(buffer)) != -1) {
+			out.write(buffer, 0, n);
+		}
+		return out.toByteArray();
+	}
+	
+	public static boolean WriteStringToFile(String filePath, String content) {
+        try {  
+            PrintWriter pw = new PrintWriter(new FileWriter(filePath));  
+            pw.println(content);    
+            pw.close();
+            return true;
+        } catch (Throwable e) {
+        	logger.error("Error! Failed to write file '{}' ! {} {}", filePath, e.toString(), CommonUtils.getStackTraceString(e.getStackTrace()));
+        }
+        return false;
+    }  
+	
+	  public static void copyFile(File fromFile,File toFile) throws IOException{
+	        FileInputStream ins = new FileInputStream(fromFile);
+	        FileOutputStream out = new FileOutputStream(toFile);
+	        byte[] b = new byte[1024];
+	        int n=0;
+	        while((n=ins.read(b))!=-1){
+	            out.write(b, 0, n);
+	        }
+	        
+	        ins.close();
+	        out.close();
+	    }
+	  
+	  public static byte[] readFile(String path)  {
+	        File f = new File(path);
+	        if(!f.exists()){
+	            try {
+	                throw new FileNotFoundException(path);
+	            } catch (FileNotFoundException e) {
+	                e.printStackTrace();
+	            }
+	        }
+	        try(BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
+				ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length())){
+	            int buf_size = 9999;
+	            byte[] buffer = new byte[buf_size];
+	            int len = 0;
+	            while(-1 != (len = in.read(buffer,0,buf_size))){
+	                bos.write(buffer,0,len);
+	            }
+	            return bos.toByteArray();
+	        }catch (IOException e) {
+	            e.printStackTrace();
+	        }
+	        return null;
+	    }
+	  
+	public static boolean delFile(File file) {
+	        if (!file.exists()) {
+	            return false;
+	        }
+	        if (file.isDirectory()) {
+	            File[] files = file.listFiles();
+	            for (File f : files) {
+	                delFile(f);
+	            }
+	        }
+	        return file.delete();
+	    }
+}

+ 16 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java

@@ -1,6 +1,10 @@
 package com.ruoyi.web.controller.system;
 
 import java.util.List;
+
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -51,6 +55,18 @@ public class SysConfigController extends BaseController
     {
         startPage();
         List<SysConfig> list = configService.selectConfigList(config);
+        for (SysConfig data: list) {
+            String configName = "";
+            try {
+                configName = MessageUtils.message("_sys.config." + data.getConfigKey());
+            } catch (Exception e) {
+
+            }
+            if (StringUtils.isBlank(configName)) {
+                configName = data.getConfigName();
+            }
+            data.setConfigName(configName);
+        }
         return getDataTable(list);
     }
 

+ 16 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java

@@ -1,6 +1,10 @@
 package com.ruoyi.web.controller.system;
 
 import java.util.List;
+
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -48,6 +52,18 @@ public class SysDictDataController extends BaseController
     {
         startPage();
         List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        for (SysDictData data: list) {
+            String dictLabel = "";
+            try {
+                dictLabel = MessageUtils.message("_sys.dict.data.label." + data.getDictType() + "." + data.getDictValue());
+            } catch (Exception e) {
+
+            }
+            if (StringUtils.isBlank(dictLabel)) {
+                dictLabel = data.getDictLabel();
+            }
+            data.setDictLabel(dictLabel);
+        }
         return getDataTable(list);
     }
 

+ 16 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java

@@ -1,6 +1,10 @@
 package com.ruoyi.web.controller.system;
 
 import java.util.List;
+
+import com.ruoyi.cc.domain.CcParams;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -49,6 +53,18 @@ public class SysDictTypeController extends BaseController
     {
         startPage();
         List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        for (SysDictType data: list) {
+            String dictName = "";
+            try {
+                dictName = MessageUtils.message("_sys.dict.type." + data.getDictType());
+            } catch (Exception e) {
+
+            }
+            if (StringUtils.isBlank(dictName)) {
+                dictName = data.getDictName();
+            }
+            data.setDictName(dictName);
+        }
         return getDataTable(list);
     }
 

+ 67 - 3
ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SysInitConfig.java

@@ -1,21 +1,85 @@
 package com.ruoyi.web.core.config;
 
+import com.ruoyi.cc.domain.CcExtNum;
+import com.ruoyi.cc.domain.CcParams;
+import com.ruoyi.cc.service.ICcExtNumService;
+import com.ruoyi.cc.service.ICcParamsService;
+import com.ruoyi.cc.utils.MyFileUtil;
+import com.ruoyi.common.utils.CommonUtils;
+import com.ruoyi.common.utils.uuid.UuidGenerator;
 import com.ruoyi.system.service.ISysConfigService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.env.Environment;
 
 import javax.annotation.PostConstruct;
+import java.io.File;
+import java.util.List;
+import java.util.UUID;
 
 @Slf4j
 @Configuration
-public class SysInitConfig {
+public class SysInitConfig implements ApplicationRunner {
 
-    @PostConstruct
-    public void init() throws Exception {
+    @Autowired
+    private ICcExtNumService ccExtNumService;
+    @Autowired
+    private ICcParamsService ccParamsService;
+    @Value("${sysconfig.init-reset-secret:false}")
+    private Boolean initResetSecret;
 
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        if (initResetSecret) {
+            String lockerFile = getLockerFile();
+            // run once on system first startup
+            if (!new File(lockerFile).exists()) {
+                log.info("=============reset loginTokenSecret and reset allExtensionsPassword when first startup============");
+                resetLoginTokenSecret();
+                resetAllExtensionsPassword();
+                MyFileUtil.WriteStringToFile(lockerFile, "1");
+            } else {
+                log.info("==============file exists, not to reset============");
+            }
+        }
+    }
+
+
+    private String getLockerFile() {
+        return String.format("%s%s",
+                ccParamsService.getParamValueByCode("fs_conf_directory", "/home/freeswitch/etc/freeswitch"),
+                "/autoload_configs/install_lock.data"
+        );
+    }
+
+    private void resetAllExtensionsPassword(){
+        List<CcExtNum> extNumList = ccExtNumService.selectCcExtNumList(new CcExtNum());
+        for (CcExtNum ext : extNumList) {
+            String uuid = UuidGenerator.GetOneUuid();
+            String newPassword = CommonUtils.shuffleString(uuid);
+            ext.setExtPass(newPassword);
+            int affectRow = ccExtNumService.updateCcExtNum(ext);
+            if(affectRow == 1){
+                log.info("reset extension password successfully. extnum={}", ext.getExtNum());
+            }else{
+                log.info("Failed to reset extension password ! extnum={}", ext.getExtNum());
+            }
+        }
+    }
+
+    private void resetLoginTokenSecret() {
+        log.info("try to resetLoginTokenSecret...");
+        String newSecretKey = UUID.randomUUID().toString().replace("-", "").substring(0, 21);
+        int affectRow = ccParamsService.updateParamsValue("ws-server-auth-token-secret", newSecretKey);
+        if (1 == affectRow) {
+            log.info("resetLoginTokenSecret finished successfully!");
+        } else {
+            log.info("Database Error! Failed to resetLoginTokenSecret!");
+        }
     }
 }

+ 5 - 4
ruoyi-admin/src/main/resources/application-dev.yml

@@ -7,9 +7,9 @@ spring:
     druid:
       # 主库数据源
       master:
-        url: jdbc:mysql://129.28.164.235:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        url: jdbc:mysql://172.200.115.112:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
         username: root
-        password: easycallcenter365
+        password: Tydic2020!@#dicfin!@#$%
       slave:
         # 从数据源开关/默认关闭
         enabled: false
@@ -125,7 +125,7 @@ server:
       max: 800
       # Tomcat启动初始化的线程数,默认值10
       min-spare: 100
- 
+
 # 日志配置
 logging:
   level:
@@ -206,5 +206,6 @@ sysconfig:
   sysVersion: v20260217
   # 是否开启登陆时选择业务组
   show-dynamic-groupid: true
-
+  # 是否执行resetSecret
+  init-reset-secret: false
 

+ 4 - 7
ruoyi-admin/src/main/resources/application-local.yml

@@ -7,14 +7,9 @@ spring:
     druid:
       # 主库数据源
       master:
-        url: jdbc:mysql://172.200.115.112:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        url: jdbc:mysql://192.168.119.228:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
         username: root
-        password: Tydic2020!@#dicfin!@#$%
-#      # 主库数据源
-#      master:
-#        url: jdbc:mysql://192.168.66.70:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-#        username: root
-#        password: tydic202x888
+        password: easycallcenter365
       slave:
         # 从数据源开关/默认关闭
         enabled: false
@@ -211,4 +206,6 @@ sysconfig:
   sysVersion: v20260217
   # 是否开启登陆时选择业务组
   show-dynamic-groupid: true
+  # 是否执行resetSecret
+  init-reset-secret: false
 

+ 211 - 0
ruoyi-admin/src/main/resources/application-nongdan.yml

@@ -0,0 +1,211 @@
+# Spring配置
+spring:
+  # 数据源配置
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 主库数据源
+      master:
+        url: jdbc:mysql://192.168.66.150:3306/easycallcenter365?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: Tydic2020!@#dicfin!@#$%
+      slave:
+        # 从数据源开关/默认关闭
+        enabled: false
+        url:
+        username:
+        password:
+      # 初始连接数
+      initialSize: 5
+      # 最小连接池数量
+      minIdle: 10
+      # 最大连接池数量
+      maxActive: 20
+      # 配置获取连接等待超时的时间
+      maxWait: 60000
+      # 配置连接超时时间
+      connectTimeout: 30000
+      # 配置网络超时时间
+      socketTimeout: 60000
+      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      timeBetweenEvictionRunsMillis: 60000
+      # 配置一个连接在池中最小生存的时间,单位是毫秒
+      minEvictableIdleTimeMillis: 300000
+      # 配置一个连接在池中最大生存的时间,单位是毫秒
+      maxEvictableIdleTimeMillis: 900000
+      # 配置检测连接是否有效
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 设置白名单,不填则允许所有访问
+        allow:
+        url-pattern: /druid/*
+        # 控制台管理用户名和密码
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 慢SQL记录
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 模板引擎
+  thymeleaf:
+    mode: HTML
+    encoding: utf-8
+    # 禁用缓存
+    cache: false
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: static/i18n/messages
+  jackson:
+    time-zone: GMT+8
+    date-format: yyyy-MM-dd HH:mm:ss
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 10MB
+      # 设置总上传的文件大小
+      max-request-size: 20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+
+# MyBatis
+mybatis:
+  # 搜索指定包别名
+  typeAliasesPackage: com.ruoyi.**.domain
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 加载全局的配置文件
+  configLocation: classpath:mybatis/mybatis-config.xml
+
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: RuoYi
+  # 版本
+  version: 4.7.9
+  # 版权年份
+  copyrightYear: 2024
+  # 实例演示开关
+  demoEnabled: false
+  # 文件路径 示例( Windows配置D:/easycallcenter365/uploadPath,Linux配置 /home/easycallcenter365/uploadPath)
+  profile: /home/easycallcenter365/uploadPath
+  # 获取ip地址开关
+  addressEnabled: false
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为80
+  port: 8899
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # 连接数满后的排队数,默认为100
+    accept-count: 1000
+    threads:
+      # tomcat最大线程数,默认为200
+      max: 800
+      # Tomcat启动初始化的线程数,默认值10
+      min-spare: 100
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# 用户配置
+user:
+  password:
+    # 密码错误{maxRetryCount}次锁定10分钟
+    maxRetryCount: 5
+
+# PageHelper分页插件
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Shiro
+shiro:
+  user:
+    # 登录地址
+    loginUrl: /login
+    # 权限认证失败地址
+    unauthorizedUrl: /unauth
+    # 首页地址
+    indexUrl: /index
+    # 验证码开关
+    captchaEnabled: false
+    # 验证码类型 math 数字计算 char 字符验证
+    captchaType: math
+  cookie:
+    # 设置Cookie的域名 默认空,即当前访问的域名
+    domain:
+    # 设置cookie的有效访问路径
+    path: /
+    # 设置HttpOnly属性
+    httpOnly: true
+    # 设置Cookie的过期时间,天为单位
+    maxAge: 30
+    # 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)Base64.encodeToString(CipherUtils.generateNewKey(128, "AES").getEncoded()) (默认启动生成随机秘钥,随机秘钥会导致之前客户端RememberMe Cookie无效,如设置固定秘钥RememberMe Cookie则有效)
+    cipherKey:
+  session:
+    # Session超时时间,-1代表永不过期(默认30分钟)
+    expireTime: 30
+    # 同步session到数据库的周期(默认1分钟)
+    dbSyncPeriod: 1
+    # 相隔多久检查一次session的有效性,默认就是10分钟
+    validationInterval: 10
+    # 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)
+    maxSession: -1
+    # 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
+    kickoutAfter: false
+  rememberMe:
+    # 是否开启记住我
+    enabled: true
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice/*
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# Swagger配置
+swagger:
+  # 是否开启swagger
+  enabled: true
+
+sysconfig:
+  # 包含哪些关键词的配置文件参数,将会被mask打码隐藏
+  hidden-key-list: api/app/token/key/secret/access
+  # 是否开启敏感参数隐藏功能
+  hide-secret: true
+  # 系统版本号
+  sysVersion: v20260217
+  # 是否开启登陆时选择业务组
+  show-dynamic-groupid: true
+  # 是否执行resetSecret
+  init-reset-secret: false

+ 3 - 1
ruoyi-admin/src/main/resources/application-pro.yml

@@ -206,4 +206,6 @@ sysconfig:
   # 系统版本号
   sysVersion: v20260217
   # 是否开启登陆时选择业务组
-  show-dynamic-groupid: true
+  show-dynamic-groupid: true
+  # 是否执行resetSecret
+  init-reset-secret: true

+ 3 - 1
ruoyi-admin/src/main/resources/application-test.yml

@@ -207,4 +207,6 @@ sysconfig:
   # 系统版本号
   sysVersion: v20260217
   # 是否开启登陆时选择业务组
-  show-dynamic-groupid: true
+  show-dynamic-groupid: true
+  # 是否执行resetSecret
+  init-reset-secret: false

+ 1 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test

+ 72 - 0
ruoyi-admin/src/main/resources/mapper/aicall/CcAsrLanguagesMapper.xml

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.aicall.mapper.CcAsrLanguagesMapper">
+    
+    <resultMap type="CcAsrLanguages" id="CcAsrLanguagesResult">
+        <result property="id"    column="id"    />
+        <result property="asrProvider"    column="asr_provider"    />
+        <result property="models"    column="models"    />
+        <result property="languageCode"    column="language_code"    />
+        <result property="languageName"    column="language_name"    />
+    </resultMap>
+
+    <sql id="selectCcAsrLanguagesVo">
+        select id, asr_provider, models, language_code, language_name from cc_asr_languages
+    </sql>
+
+    <select id="selectCcAsrLanguagesList" parameterType="CcAsrLanguages" resultMap="CcAsrLanguagesResult">
+        <include refid="selectCcAsrLanguagesVo"/>
+        <where>  
+            <if test="asrProvider != null  and asrProvider != ''"> and asr_provider = #{asrProvider}</if>
+            <if test="models != null  and models != ''"> and models = #{models}</if>
+            <if test="languageCode != null  and languageCode != ''"> and language_code = #{languageCode}</if>
+            <if test="languageName != null  and languageName != ''"> and language_name like concat('%', #{languageName}, '%')</if>
+        </where>
+    </select>
+    
+    <select id="selectCcAsrLanguagesById" parameterType="Long" resultMap="CcAsrLanguagesResult">
+        <include refid="selectCcAsrLanguagesVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertCcAsrLanguages" parameterType="CcAsrLanguages" useGeneratedKeys="true" keyProperty="id">
+        insert into cc_asr_languages
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="asrProvider != null and asrProvider != ''">asr_provider,</if>
+            <if test="models != null and models != ''">models,</if>
+            <if test="languageCode != null and languageCode != ''">language_code,</if>
+            <if test="languageName != null and languageName != ''">language_name,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="asrProvider != null and asrProvider != ''">#{asrProvider},</if>
+            <if test="models != null and models != ''">#{models},</if>
+            <if test="languageCode != null and languageCode != ''">#{languageCode},</if>
+            <if test="languageName != null and languageName != ''">#{languageName},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCcAsrLanguages" parameterType="CcAsrLanguages">
+        update cc_asr_languages
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="asrProvider != null and asrProvider != ''">asr_provider = #{asrProvider},</if>
+            <if test="models != null and models != ''">models = #{models},</if>
+            <if test="languageCode != null and languageCode != ''">language_code = #{languageCode},</if>
+            <if test="languageName != null and languageName != ''">language_name = #{languageName},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCcAsrLanguagesById" parameterType="Long">
+        delete from cc_asr_languages where id = #{id}
+    </delete>
+
+    <delete id="deleteCcAsrLanguagesByIds" parameterType="String">
+        delete from cc_asr_languages where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 0 - 74
ruoyi-admin/src/main/resources/mapper/aicall/CcCallPhoneMapper.xml

@@ -226,18 +226,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM cc_call_phone
         WHERE batch_id = #{batchId}
     </select>
-    <select id="statByBatchIds" parameterType="java.util.List" resultType="com.ruoyi.aicall.model.CallTaskStatModel">
-        SELECT batch_id AS batchId,
-               COUNT(1) AS phoneCount,
-               SUM(CASE WHEN callout_time > 0 THEN 1 ELSE 0 END) AS callCount,
-               SUM(CASE WHEN answered_time > 0 THEN 1 ELSE 0 END) AS connectCount
-        FROM cc_call_phone
-        WHERE batch_id IN
-        <foreach item="item" collection="list" open="(" separator="," close=")">
-            #{item}
-         </foreach>
-        GROUP BY batch_id
-    </select>
 
     <insert id="batchInsertCcCallPhone" parameterType="java.util.List">
         INSERT INTO cc_call_phone (
@@ -294,66 +282,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <delete id="delCallPhoneByBatchId" parameterType="Long">
         delete from cc_call_phone where batch_id = #{batchId}
     </delete>
-
-    <select id="selectCcCallPhoneYlrzList" parameterType="CcCallPhone" resultMap="CcCallPhoneResult">
-        select ccp.*, cct.batch_name as batchName from cc_call_phone ccp left join cc_call_task cct on cct.batch_id = ccp.batch_id
-        <where>
-            <if test="uuid != null and uuid != '' "> and ccp.uuid = #{uuid}</if>
-            <if test="batchId != null "> and ccp.batch_id = #{batchId}</if>
-            <if test="telephone != null  and telephone != ''"> and ccp.telephone = #{telephone}</if>
-            <if test="custName != null  and custName != ''"> and ccp.cust_name = #{custName}</if>
-            <if test="callstatus != null "> and ccp.callstatus = #{callstatus}</if>
-            <if test="params.calloutTimeStart != null and params.calloutTimeStart != ''"><!-- 开始时间检索 -->
-                AND ccp.callout_time &gt;= #{params.calloutTimeStart}
-            </if>
-            <if test="params.calloutTimeEnd != null and params.calloutTimeEnd != ''"><!-- 结束时间检索 -->
-                AND ccp.callout_time &lt;= #{params.calloutTimeEnd}
-            </if>
-            <if test="params.callEndTimeStart != null and params.callEndTimeStart != ''"><!-- 开始时间检索 -->
-                AND ccp.call_end_time &gt;= #{params.callEndTimeStart}
-            </if>
-            <if test="params.callEndTimeEnd != null and params.callEndTimeEnd != ''"><!-- 结束时间检索 -->
-                AND ccp.call_end_time &lt;= #{params.callEndTimeEnd}
-            </if>
-            <if test="params.timeLenStart != null and params.timeLenStart != ''"><!-- 开始时间检索 -->
-                AND ccp.time_len &gt;= #{params.timeLenStart}
-            </if>
-            <if test="params.timeLenEnd != null and params.timeLenEnd != ''"><!-- 结束时间检索 -->
-                AND ccp.time_len &lt;= #{params.timeLenEnd}
-            </if>
-            <if test="params.connectedTimeStart != null and params.connectedTimeStart != ''"><!-- 开始时间检索 -->
-                AND ccp.connected_time &gt;= #{params.connectedTimeStart}
-            </if>
-            <if test="params.connectedTimeEnd != null and params.connectedTimeEnd != ''"><!-- 结束时间检索 -->
-                AND ccp.connected_time &lt;= #{connectedTimeEnd}
-            </if>
-            <if test="params.answeredTimeStart != null and params.answeredTimeStart != ''"><!-- 开始时间检索 -->
-                AND ccp.answered_time &gt;= #{params.answeredTimeStart}
-            </if>
-            <if test="params.answeredTimeEnd != null and params.answeredTimeEnd != ''"><!-- 结束时间检索 -->
-                AND ccp.answered_time &lt;= #{params.answeredTimeEnd}
-            </if>
-            <if test="batchId != null and batchId != ''">
-                AND ccp.batch_id = #{batchId}
-            </if>
-            <if test="intent != null and intent != ''">
-                AND ccp.intent = #{intent}
-            </if>
-            <if test="billingStatus != null ">
-                and ccp.billing_status = #{billingStatus}
-            </if>
-            <if test="callerNumber != null and callerNumber != '' ">
-                and ccp.caller_number = #{callerNumber}
-            </if>
-            AND ccp.callstatus >= 3 AND ccp.call_end_time > 0
-        </where>
-        order by ccp.call_end_time desc
-    </select>
-    <select id="selectCcCallPhoneListByIds" parameterType="java.util.List" resultMap="CcCallPhoneResult">
-        <include refid="selectCcCallPhoneVo"/>
-        where uuid is not null and id in
-        <foreach item="id" collection="list" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </select>
 </mapper>

+ 36 - 7
ruoyi-admin/src/main/resources/mapper/aicall/CcCallTaskMapper.xml

@@ -30,10 +30,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="aiTransferData"    column="ai_transfer_data"    />
         <result property="autoStop"    column="auto_stop"    />
         <result property="ivrId"    column="ivr_id"    />
+        <result property="asrLanguageCode"    column="asr_language_code"    />
+        <result property="ttsLanguageCode"    column="tts_language_code"    />
+        <result property="asrModels"    column="asr_models"    />
+        <result property="ttsModels"    column="tts_models"    />
     </resultMap>
 
     <sql id="selectCcCallTaskVo">
-        select batch_id, group_id, batch_name, ifcall, rate, thread_num, createtime, executing, stop_time, userid, task_type, gateway_id, voice_code, voice_source, avg_ring_time_len, avg_call_talk_time_len, avg_call_end_process_time_len, call_node_no, llm_account_id, play_times, asr_provider, ai_transfer_type, ai_transfer_data, auto_stop, ivr_id from cc_call_task
+        select batch_id, group_id, batch_name, ifcall, rate, thread_num,
+               createtime, executing, stop_time, userid, task_type,
+               gateway_id, voice_code, voice_source, avg_ring_time_len,
+               avg_call_talk_time_len, avg_call_end_process_time_len,
+               call_node_no, llm_account_id, play_times, asr_provider,
+               ai_transfer_type, ai_transfer_data, auto_stop,
+               ivr_id, asr_language_code, tts_language_code, asr_models, tts_models from cc_call_task
     </sql>
 
     <select id="selectCcCallTaskList" parameterType="CcCallTask" resultMap="CcCallTaskResult">
@@ -57,6 +67,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="asrProvider != null and asrProvider != ''" > and asr_provider = #{asrProvider}</if>
             <if test="aiTransferType != null and aiTransferType != ''" > and ai_transfer_type = #{aiTransferType}</if>
             <if test="aiTransferData != null and aiTransferData != ''" > and ai_transfer_data = #{aiTransferData}</if>
+            <if test="asrLanguageCode != null  and asrLanguageCode != ''"> and asr_language_code = #{asrLanguageCode}</if>
+            <if test="ttsLanguageCode != null  and ttsLanguageCode != ''"> and tts_language_code = #{ttsLanguageCode}</if>
+            <if test="asrModels != null  and asrModels != ''"> and asr_models = #{asrModels}</if>
+            <if test="ttsModels != null  and ttsModels != ''"> and tts_models = #{ttsModels}</if>
+
         </where>
         order by batch_id desc
     </select>
@@ -80,8 +95,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userid != null and userid != ''">userid,</if>
             <if test="taskType != null">task_type,</if>
             <if test="gatewayId != null">gateway_id,</if>
-            <if test="voiceCode != null and voiceCode != ''">voice_code,</if>
-            <if test="voiceSource != null and voiceSource != ''">voice_source,</if>
+            voice_code,
+            voice_source,
             <if test="avgRingTimeLen != null">avg_ring_time_len,</if>
             <if test="avgCallTalkTimeLen != null">avg_call_talk_time_len,</if>
             <if test="avgCallEndProcessTimeLen != null">avg_call_end_process_time_len,</if>
@@ -93,6 +108,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="aiTransferData != null and aiTransferData != ''">ai_transfer_data,</if>
             <if test="autoStop != null ">auto_stop,</if>
             <if test="ivrId != null and ivrId != ''">ivr_id,</if>
+            asr_language_code,
+            tts_language_code,
+            asr_models,
+            tts_models,
+
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="groupId != null and groupId != ''">#{groupId},</if>
@@ -106,8 +126,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userid != null and userid != ''">#{userid},</if>
             <if test="taskType != null">#{taskType},</if>
             <if test="gatewayId != null">#{gatewayId},</if>
-            <if test="voiceCode != null and voiceCode != ''">#{voiceCode},</if>
-            <if test="voiceSource != null and voiceSource != ''">#{voiceSource},</if>
+            #{voiceCode},
+            #{voiceSource},
             <if test="avgRingTimeLen != null">#{avgRingTimeLen},</if>
             <if test="avgCallTalkTimeLen != null">#{avgCallTalkTimeLen},</if>
             <if test="avgCallEndProcessTimeLen != null">#{avgCallEndProcessTimeLen},</if>
@@ -119,6 +139,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="aiTransferData != null and aiTransferData != ''">#{aiTransferData},</if>
             <if test="autoStop != null ">#{autoStop},</if>
             <if test="ivrId != null and ivrId != ''">#{ivrId},</if>
+            #{asrLanguageCode},
+            #{ttsLanguageCode},
+            #{asrModels},
+            #{ttsModels},
+
         </trim>
     </insert>
 
@@ -136,8 +161,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userid != null and userid != ''">userid = #{userid},</if>
             <if test="gatewayId != null">gateway_id = #{gatewayId},</if>
             <if test="taskType != null">task_type = #{taskType},</if>
-            <if test="voiceCode != null and voiceCode != ''">voice_code = #{voiceCode},</if>
-            <if test="voiceSource != null and voiceSource != ''">voice_source = #{voiceSource},</if>
+            voice_code = #{voiceCode},
+            voice_source = #{voiceSource},
             <if test="avgRingTimeLen != null">avg_ring_time_len = #{avgRingTimeLen},</if>
             <if test="avgCallTalkTimeLen != null">avg_call_talk_time_len = #{avgCallTalkTimeLen},</if>
             <if test="avgCallEndProcessTimeLen != null">avg_call_end_process_time_len = #{avgCallEndProcessTimeLen},</if>
@@ -149,6 +174,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="aiTransferData != null and aiTransferData != ''">ai_transfer_data = #{aiTransferData},</if>
             <if test="autoStop != null ">auto_stop = #{autoStop},</if>
             <if test="ivrId != null and ivrId != ''">ivr_id = #{ivrId},</if>
+            asr_language_code = #{asrLanguageCode},
+            tts_language_code = #{ttsLanguageCode},
+            asr_models = #{asrModels},
+            tts_models = #{ttsModels},
 
         </trim>
         where batch_id = #{batchId}

+ 33 - 8
ruoyi-admin/src/main/resources/mapper/aicall/CcInboundLlmAccountMapper.xml

@@ -17,10 +17,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="aiTransferData"    column="ai_transfer_data"    />
         <result property="ivrId"    column="ivr_id"    />
         <result property="satisfSurveyIvrId"    column="satisf_survey_ivr_id"    />
+        <result property="asrLanguageCode"    column="asr_language_code"    />
+        <result property="ttsLanguageCode"    column="tts_language_code"    />
+        <result property="asrModels"    column="asr_models"    />
+        <result property="ttsModels"    column="tts_models"    />
     </resultMap>
 
     <sql id="selectCcInboundLlmAccountVo">
-        select id, llm_account_id, callee, voice_code, voice_source, service_type, asr_provider, ai_transfer_type, ai_transfer_data, ivr_id, satisf_survey_ivr_id, inbound_alias from cc_inbound_llm_account
+        select id, llm_account_id, callee, voice_code, voice_source,
+               service_type, asr_provider, ai_transfer_type, ai_transfer_data,
+               ivr_id, satisf_survey_ivr_id, inbound_alias,
+               asr_language_code, tts_language_code, asr_models, tts_models from cc_inbound_llm_account
     </sql>
 
     <select id="selectCcInboundLlmAccountList" parameterType="CcInboundLlmAccount" resultMap="CcInboundLlmAccountResult">
@@ -35,6 +42,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="asrProvider != null  and asrProvider != ''"> and asr_provider = #{asrProvider}</if>
             <if test="aiTransferType != null  and aiTransferType != ''"> and ai_transfer_type = #{aiTransferType}</if>
             <if test="aiTransferData != null  and aiTransferData != ''"> and ai_transfer_data = #{aiTransferData}</if>
+            <if test="asrLanguageCode != null  and asrLanguageCode != ''"> and asr_language_code = #{asrLanguageCode}</if>
+            <if test="ttsLanguageCode != null  and ttsLanguageCode != ''"> and tts_language_code = #{ttsLanguageCode}</if>
+            <if test="asrModels != null  and asrModels != ''"> and asr_models = #{asrModels}</if>
+            <if test="ttsModels != null  and ttsModels != ''"> and tts_models = #{ttsModels}</if>
+
         </where>
         order by id desc
     </select>
@@ -51,28 +63,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="llmAccountId != null">llm_account_id,</if>
             <if test="callee != null and callee != ''">callee,</if>
             <if test="inboundAlias != null and inboundAlias != ''">inbound_alias,</if>
-            <if test="voiceCode != null and voiceCode != ''">voice_code,</if>
-            <if test="voiceSource != null and voiceSource != ''">voice_source,</if>
+            voice_code,
+            voice_source,
             <if test="serviceType != null and serviceType != ''">service_type,</if>
             <if test="asrProvider != null and asrProvider != ''">asr_provider,</if>
             <if test="aiTransferType != null and aiTransferType != ''">ai_transfer_type,</if>
             <if test="aiTransferData != null and aiTransferData != ''">ai_transfer_data,</if>
             <if test="ivrId != null">ivr_id,</if>
             <if test="satisfSurveyIvrId != null">satisf_survey_ivr_id,</if>
+            asr_language_code,
+            tts_language_code,
+            asr_models,
+            tts_models,
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="llmAccountId != null">#{llmAccountId},</if>
             <if test="callee != null and callee != ''">#{callee},</if>
             <if test="inboundAlias != null and inboundAlias != ''">#{inboundAlias},</if>
-            <if test="voiceCode != null and voiceCode != ''">#{voiceCode},</if>
-            <if test="voiceSource != null and voiceSource != ''">#{voiceSource},</if>
+            #{voiceCode},
+            #{voiceSource},
             <if test="serviceType != null and serviceType != ''">#{serviceType},</if>
             <if test="asrProvider != null and asrProvider != ''">#{asrProvider},</if>
             <if test="aiTransferType != null and aiTransferType != ''">#{aiTransferType},</if>
             <if test="aiTransferData != null and aiTransferData != ''">#{aiTransferData},</if>
             <if test="ivrId != null">#{ivrId},</if>
             <if test="satisfSurveyIvrId != null">#{satisfSurveyIvrId},</if>
+            #{asrLanguageCode},
+            #{ttsLanguageCode},
+            #{asrModels},
+            #{ttsModels},
         </trim>
     </insert>
 
@@ -82,14 +102,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="llmAccountId != null">llm_account_id = #{llmAccountId},</if>
             <if test="callee != null and callee != ''">callee = #{callee},</if>
             <if test="inboundAlias != null and inboundAlias != ''">inbound_alias = #{inboundAlias},</if>
-            <if test="voiceCode != null and voiceCode != ''">voice_code = #{voiceCode},</if>
-            <if test="voiceSource != null and voiceSource != ''">voice_source = #{voiceSource},</if>
+            voice_code = #{voiceCode},
+            voice_source = #{voiceSource},
             <if test="serviceType != null and serviceType != ''">service_type = #{serviceType},</if>
             <if test="asrProvider != null and asrProvider != ''">asr_provider = #{asrProvider},</if>
             <if test="aiTransferType != null and aiTransferType != ''">ai_transfer_type = #{aiTransferType},</if>
             <if test="aiTransferData != null and aiTransferData != ''">ai_transfer_data = #{aiTransferData},</if>
             <if test="ivrId != null">ivr_id = #{ivrId},</if>
             <if test="satisfSurveyIvrId != null">satisf_survey_ivr_id = #{satisfSurveyIvrId},</if>
+            asr_language_code = #{asrLanguageCode},
+            tts_language_code = #{ttsLanguageCode},
+            asr_models = #{asrModels},
+            tts_models = #{ttsModels},
+
         </set>
         where id = #{id}
     </update>
@@ -105,7 +130,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </delete>
 
-    <select id="selectCcInboundLlmAccountByCallee" parameterType="String">
+    <select id="selectCcInboundLlmAccountByCallee" parameterType="String" resultMap="CcInboundLlmAccountResult">
         <include refid="selectCcInboundLlmAccountVo"/>
            where callee = #{callee}
     </select>

+ 18 - 2
ruoyi-admin/src/main/resources/mapper/aicall/CcTtsAliyunMapper.xml

@@ -12,10 +12,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="voiceSource"    column="voice_source"    />
         <result property="priority"    column="priority"    />
         <result property="provider"    column="provider"    />
+        <result property="languageCode"    column="language_code"    />
+        <result property="languageName"    column="language_name"    />
+        <result property="ttsModels"    column="tts_models"    />
     </resultMap>
 
     <sql id="selectCcTtsAliyunVo">
-        select id, voice_name, voice_code, voice_enabled, voice_source, priority, provider from cc_tts_aliyun
+        select id, voice_name, voice_code, voice_enabled, voice_source, priority,
+               provider, language_code, language_name, tts_models from cc_tts_aliyun
     </sql>
 
     <select id="selectCcTtsAliyunList" parameterType="CcTtsAliyun" resultMap="CcTtsAliyunResult">
@@ -27,6 +31,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="voiceSource != null  and voiceSource != ''"> and voice_source = #{voiceSource}</if>
             <if test="priority != null "> and priority = #{priority}</if>
             <if test="provider != null  and provider != ''"> and provider = #{provider}</if>
+            <if test="languageCode != null  and languageCode != ''"> and language_code = #{languageCode}</if>
+            <if test="languageName != null  and languageName != ''"> and language_name = #{languageName}</if>
+            <if test="ttsModels != null  and ttsModels != ''"> and tts_models = #{ttsModels}</if>
         </where>
          order by priority asc
     </select>
@@ -45,6 +52,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             voice_source,
             priority,
             provider,
+            language_code,
+            language_name,
+            tts_models,
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             #{voiceName},
@@ -53,6 +63,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{voiceSource},
             #{priority},
             #{provider},
+            #{languageCode},
+            #{languageName},
+            #{ttsModels},
         </trim>
     </insert>
 
@@ -64,7 +77,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             voice_enabled = #{voiceEnabled},
             voice_source = #{voiceSource},
             priority =  #{priority},
-            provider = #{provider}
+            provider = #{provider},
+            language_code = #{languageCode},
+            language_name = #{languageName},
+            tts_models = #{ttsModels}
         </trim>
         where id = #{id}
     </update>

+ 0 - 6
ruoyi-admin/src/main/resources/mapper/cc/CcExtNumMapper.xml

@@ -69,10 +69,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where user_code = ''
     </select>
 
-    <update id="updateCcExtNumByUserCode" parameterType="CcExtNum">
-        update cc_ext_num set user_code = #{userCode} where ext_num = #{extNum}
-    </update>
-    <update id="cleanCcExtNumByUserCode" parameterType="String">
-        update cc_ext_num set user_code = '' where user_code = #{userCode}
-    </update>
 </mapper>

+ 0 - 7
ruoyi-admin/src/main/resources/mapper/cc/CcGatewaysMapper.xml

@@ -49,13 +49,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     #{purpose}
                 </foreach>
             </if>
-            <if test="gatewayIds != null and !gatewayIds.isEmpty()">
-                and id in
-                <foreach item="gatewayId" collection="gatewayIds" open="(" separator="," close=")">
-                    #{gatewayId}
-                </foreach>
-            </if>
-
         </where>
     </select>
     

+ 17 - 2
ruoyi-admin/src/main/resources/mapper/cc/CcIvrMapper.xml

@@ -28,6 +28,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="ttsTextWav"    column="tts_text_wav"    />
         <result property="pressKeyInvalidTipsWav"    column="press_key_invalid_tips_wav"    />
         <result property="hangupTipsWav"    column="hangup_tips_wav"    />
+        <result property="ttsLanguageCode"    column="tts_language_code"    />
+        <result property="ttsModels"    column="tts_models"    />
     </resultMap>
 
     <sql id="selectCcIvrVo">
@@ -35,7 +37,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                wait_key_timeout, max_press_key_failures, press_key_invalid_tips,
                enabled, failed_action, digit_range, tts_provider, voice_code,
                ai_transfer_data, max_len, min_len, user_input_var_name,
-               hangup_tips, tts_text_wav, press_key_invalid_tips_wav, hangup_tips_wav from cc_ivr
+               hangup_tips, tts_text_wav, press_key_invalid_tips_wav,
+               hangup_tips_wav, tts_language_code, tts_models from cc_ivr
     </sql>
 
     <select id="selectCcIvrList" parameterType="CcIvr" resultMap="CcIvrResult">
@@ -53,6 +56,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="enabled != null "> and enabled = #{enabled}</if>
             <if test="failedAction != null  and failedAction != ''"> and failed_action = #{failedAction}</if>
             <if test="digitRange != null  and digitRange != ''"> and digit_range = #{digitRange}</if>
+            <if test="ttsLanguageCode != null  and ttsLanguageCode != ''"> and tts_language_code = #{ttsLanguageCode}</if>
+            <if test="ttsModels != null  and ttsModels != ''"> and tts_models = #{ttsModels}</if>
+
         </where>
     </select>
     
@@ -87,7 +93,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ttsTextWav != null and ttsTextWav != ''">tts_text_wav,</if>
             <if test="pressKeyInvalidTipsWav != null and pressKeyInvalidTipsWav != ''">press_key_invalid_tips_wav,</if>
             <if test="hangupTipsWav != null and hangupTipsWav != ''">hangup_tips_wav,</if>
-         </trim>
+            tts_language_code,
+            tts_models,
+
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="rootId != null">#{rootId},</if>
@@ -112,6 +121,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ttsTextWav != null and ttsTextWav != ''">#{ttsTextWav},</if>
             <if test="pressKeyInvalidTipsWav != null and pressKeyInvalidTipsWav != ''">#{pressKeyInvalidTipsWav},</if>
             <if test="hangupTipsWav != null and hangupTipsWav != ''">#{hangupTipsWav},</if>
+            #{ttsLanguageCode},
+            #{ttsModels},
+
          </trim>
     </insert>
 
@@ -140,6 +152,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ttsTextWav != null and ttsTextWav != ''">tts_text_wav = #{ttsTextWav},</if>
             <if test="pressKeyInvalidTipsWav != null and pressKeyInvalidTipsWav != ''">press_key_invalid_tips_wav = #{pressKeyInvalidTipsWav},</if>
             <if test="hangupTipsWav != null and hangupTipsWav != ''">hangup_tips_wav = #{hangupTipsWav},</if>
+            tts_language_code = #{ttsLanguageCode},
+            tts_models = #{ttsModels},
+
         </trim>
         where id = #{id}
     </update>

+ 4 - 4
ruoyi-admin/src/main/resources/mapper/cc/CcOutboundCdrMapper.xml

@@ -31,11 +31,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="caller != null  and caller != ''"> and caller = #{caller}</if>
             <if test="opnum != null  and opnum != ''"> and opnum = #{opnum}</if>
             <if test="callee != null  and callee != ''"> and callee = #{callee}</if>
-            <if test="params.calloutTimeStart != null and params.calloutTimeStart != ''"><!-- 开始时间检索 -->
-                AND start_time &gt;= #{params.calloutTimeStart}
+            <if test="params.startTimeStart != null and params.startTimeStart != ''"><!-- 开始时间检索 -->
+                AND start_time &gt;= #{params.startTimeStart}
             </if>
-            <if test="params.calloutTimeEnd != null and params.calloutTimeEnd != ''"><!-- 结束时间检索 -->
-                AND start_time &lt;= #{params.calloutTimeEnd}
+            <if test="params.startTimeEnd != null and params.startTimeEnd != ''"><!-- 结束时间检索 -->
+                AND start_time &lt;= #{params.startTimeEnd}
             </if>
             <if test="params.answeredTimeStart != null and params.answeredTimeStart != ''"><!-- 开始时间检索 -->
                 AND answered_time &gt;= #{params.answeredTimeStart}

+ 1120 - 0
ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.i18n.js

@@ -0,0 +1,1120 @@
+/**
+ * 基于bootstrapTreeTable/bootstrap-table-treegrid修改
+ * Copyright (c) 2019 ruoyi
+ */
+(function($) {
+    "use strict";
+
+    $.fn.bootstrapTreeTable = function(options, param) {
+        var target = $(this).data('bootstrap.tree.table');
+        target = target ? target : $(this);
+        // 如果是调用方法
+        if (typeof options == 'string') {
+            return $.fn.bootstrapTreeTable.methods[options](target, param);
+        }
+        // 如果是初始化组件
+        options = $.extend({}, $.fn.bootstrapTreeTable.defaults, options || {});
+        target.hasSelectItem = false;    // 是否有radio或checkbox
+        target.data_list = null;         // 用于缓存格式化后的数据-按父分组
+        target.data_obj = null;          // 用于缓存格式化后的数据-按id存对象
+        target.hiddenColumns = [];       // 用于存放被隐藏列的field
+        target.lastAjaxParams;           // 用户最后一次请求的参数
+        target.isFixWidth=false;         // 是否有固定宽度
+        target.totalRows = 0;            // 记录总数
+        target.totalPages = 0;           // 总页数
+        // 初始化
+        var init = function() {
+            // 初始化容器
+            initContainer();
+            // 初始化工具栏
+            initToolbar();
+            // 初始化表头
+            initHeader();
+            // 初始化表体
+            initBody();
+            // 初始化数据服务
+            initServer();
+            // 动态设置表头宽度
+            autoTheadWidth(true);
+            // 缓存target对象
+            target.data('bootstrap.tree.table', target);
+        }
+        // 初始化容器
+        var initContainer = function() {
+            // 在外层包装一下div,样式用的bootstrap-table的
+            var $main_div = $("<div class='bootstrap-tree-table'></div>");
+            var $treetable = $("<div class='treetable-table'></div>");
+            target.before($main_div);
+            $main_div.append($treetable);
+            $treetable.append(target);
+            target.addClass("table");
+            if (options.striped) {
+                target.addClass('table-striped');
+            }
+            if (options.bordered) {
+                target.addClass('table-bordered');
+            }
+            if (options.hover) {
+                target.addClass('table-hover');
+            }
+            if (options.condensed) {
+                target.addClass('table-condensed');
+            }
+            target.html("");
+        }
+        // 初始化工具栏
+        var initToolbar = function() {
+            var $toolbar = $("<div class='treetable-bars'></div>");
+            if (options.toolbar) {
+                $(options.toolbar).addClass('tool-left');
+                $toolbar.append($(options.toolbar));
+            }
+            var $rightToolbar = $('<div class="btn-group tool-right">');
+            $toolbar.append($rightToolbar);
+            target.parent().before($toolbar);
+            // ruoyi 是否显示检索信息
+            var texts = options.localeTexts[options.locale] || options.localeTexts['zh-CN'];
+            if (options.showSearch) {
+                var $searchBtn = $('<button class="btn btn-default btn-outline" type="button" aria-label="search" title="' + texts.search + '"><i class="glyphicon glyphicon-search"></i></button>');
+                $rightToolbar.append($searchBtn);
+                registerSearchBtnClickEvent($searchBtn);
+            }
+            // 是否显示刷新按钮
+            if (options.showRefresh) {
+                var $refreshBtn = $('<button class="btn btn-default btn-outline" type="button" aria-label="refresh" title="' + texts.refresh + '"><i class="glyphicon glyphicon-repeat"></i></button>');
+                $rightToolbar.append($refreshBtn);
+                registerRefreshBtnClickEvent($refreshBtn);
+            }
+            // 是否显示列选项
+            if (options.showColumns) {
+                var $columns_div = $('<div class="btn-group pull-right" title="' + texts.columns + '"><button type="button" aria-label="columns" class="btn btn-default btn-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="glyphicon glyphicon-list"></i> <span class="caret"></span></button></div>');
+                var $columns_ul = $('<ul class="dropdown-menu columns" role="menu"></ul>');
+                $.each(options.columns, function(i, column) {
+                    if (column.field != 'selectItem') {
+                        var _li = null;
+                        if(typeof column.visible == "undefined"||column.visible==true){
+                            _li = $('<li role="menuitem"><label><input type="checkbox" checked="checked" data-field="'+column.field+'" value="'+column.field+'" > '+column.title+'</label></li>');
+                        }else{
+                            _li = $('<li role="menuitem"><label><input type="checkbox" data-field="'+column.field+'" value="'+column.field+'" > '+column.title+'</label></li>');
+                            target.hiddenColumns.push(column.field);
+                        }
+                        $columns_ul.append(_li);
+                    }
+                });
+                $columns_div.append($columns_ul);
+                $rightToolbar.append($columns_div);
+                // 注册列选项事件
+                registerColumnClickEvent();
+            }else{
+                $.each(options.columns, function(i, column) {
+                    if (column.field != 'selectItem') {
+                        if(!(typeof column.visible == "undefined"||column.visible==true)){
+                            target.hiddenColumns.push(column.field);
+                        }
+                    }
+                });
+            }
+        }
+        // 初始化隐藏列
+        var initHiddenColumns = function(){
+            $.each(target.hiddenColumns, function(i, field) {
+                target.find("."+field+"_cls").hide();
+            });
+        }
+        // 初始化表头
+        var initHeader = function() {
+            var $thr = $('<tr></tr>');
+            $.each(options.columns, function(i, column) {
+                var $th = null;
+                // 判断有没有选择列
+                if (i == 0 && column.field == 'selectItem') {
+                    target.hasSelectItem = true;
+                    $th = $('<th style="width:36px"></th>');
+                } else {
+                    $th = $('<th style="' + ((column.width) ? ('width:' + column.width + ((column.widthUnit) ? column.widthUnit : 'px')) : '') + '" class="' + column.field + '_cls"></th>');
+                    if (column.align) {
+                        $th.css("text-align", column.align);
+                    }
+                }
+                if((!target.isFixWidth)&& column.width){
+                    target.isFixWidth = column.width.indexOf("px")>-1?true:false;
+                }
+                $th.html(column.title);
+                $thr.append($th);
+            });
+            var $thead = $('<thead class="treetable-thead"></thead>');
+            $thead.append($thr);
+            target.append($thead);
+        }
+        // 初始化表体
+        var initBody = function() {
+            var $tbody = $('<tbody class="treetable-tbody"></tbody>');
+            target.append($tbody);
+            // 默认高度
+            if (options.height) {
+                $tbody.css("height", options.height);
+            }
+            if (options.pagination) {
+                var $pagination = $('<div class="fixed-table-pagination"></div>');
+                target.append($pagination);
+            }
+        }
+        // 初始化数据服务
+        var initServer = function(parms) {
+            if (options.pagination) {
+                if(parms == undefined || parms == null) {
+                    parms = {};
+                }
+                parms[options.parentCode] = options.rootIdValue;
+            }
+            // 加载数据前先清空
+            target.data_list = {};
+            target.data_obj = {};
+            // 设置请求分页参数
+            if (options.pagination) {
+                var params = {};
+                params.offset = options.pageSize * (options.pageNumber - 1);
+                params.limit = options.pageSize;
+                var curParams = { pageSize: params.limit, pageNum: params.offset / params.limit + 1 };
+                parms = $.extend(curParams, parms);
+            }
+            var $tbody = target.find("tbody");
+            // 添加加载loading
+            var texts = options.localeTexts[options.locale] || options.localeTexts['zh-CN'];
+            var $loading = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + texts.loading + '</div></td></tr>'
+            $tbody.html($loading);
+            if (options.url) {
+                $.ajax({
+                    type: options.type,
+                    url: options.url,
+                    data: $.extend(parms, options.ajaxParams),
+                    dataType: "json",
+                    success: function(data, textStatus, jqXHR) {
+                    	data = calculateObjectValue(options, options.responseHandler, [data], data);
+                        renderTable(data);
+                        calculateObjectValue(options, options.onLoadSuccess, [data], data);
+                    },
+                    error: function(xhr, textStatus) {
+                        var _errorMsg = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + xhr.responseText + '</div></td></tr>'
+                        $tbody.html(_errorMsg);
+                    }
+                });
+            } else {
+                renderTable(options.data);
+            }
+        }
+        // 加载完数据后渲染表格
+        var renderTable = function(data) {
+            var list, totalPage = 0, currPage = 0;
+            if (options.pagination) {
+            	list = data.rows;  // 数据
+                currPage = options.pageNumber; // 当前页
+                totalPage = ~~((data.total - 1) / options.pageSize) + 1 // 总页数
+                target.totalPages  = totalPage;
+                target.totalRows = data.total; // 总记录数
+            } else {
+            	list = data;
+            }
+            data = list;
+            var $tbody = target.find("tbody");
+            // 先清空
+            $tbody.html("");
+            var texts = options.localeTexts[options.locale] || options.localeTexts['zh-CN'];
+            if (!data || data.length <= 0) {
+                var _empty = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + texts.noData + '</div></td></tr>'
+                $tbody.html(_empty);
+                options.pageNumber = 1;
+                initPagination(0, 0);
+                return;
+            }
+            // 缓存并格式化数据
+            formatData(data);
+            // 获取所有根节点
+            var rootNode = target.data_list["_root_"];
+            // 开始绘制
+            if (rootNode) {
+                $.each(rootNode, function(i, item) {
+                    var _child_row_id = "row_id_" + i
+                    recursionNode(item, 1, _child_row_id, "row_root", item[options.code]);
+                });
+            }
+            // 下边的操作主要是为了查询时让一些没有根节点的节点显示
+            $.each(data, function(i, item) {
+                if (!item.isShow) {
+                    var tr = renderRow(item, false, 1, "", "", options.pagination, item[options.code]);
+                    $tbody.append(tr);
+                }
+            });
+            registerExpanderEvent();
+            registerRowClickEvent();
+            initHiddenColumns();
+            // 动态设置表头宽度
+            autoTheadWidth();
+            if (options.pagination) {
+                initPagination(totalPage, currPage);
+            }
+            // 移动端适配
+            var treetableTable = $(target).parent('.treetable-table');
+            var availableHeight = treetableTable.outerWidth();
+            if($.common.isMobile() || availableHeight < 769){
+                var tableStyle = "width: " + availableHeight + "px;overflow: auto;position: relative;";
+                treetableTable.attr('style', tableStyle);
+                var w = 0;
+                $.each(options.columns, function(i, column) {
+                    if (i == 0 && column.field == 'selectItem') {
+                        w += 36;
+                    } else {
+                        w += 200;
+                    }
+                });
+                $(target).attr('style','width:' + w +'px');
+            }
+        }
+        // 初始化分页
+        var initPagination = function (totalPage,currPage) {
+            var $pagination = target.find(".fixed-table-pagination");
+            $pagination.empty();
+            var html = [];
+            var pageFrom = (options.pageNumber - 1) * options.pageSize + 1;
+            var pageTo = options.pageNumber * options.pageSize;
+            if (pageTo > target.totalRows) {
+                pageTo = target.totalRows;
+            }
+            if (pageFrom > pageTo) {
+                pageFrom = pageTo;
+            }
+            html.push('<div class="pull-left pagination-detail">');
+            html.push('<span class="pagination-info">' + formatShowingRows(pageFrom, pageTo, target.totalRows) + '</span>');
+            var pageList = false;
+            $.each(options.pageList, function (i, page) {
+                if(target.totalRows > page){
+                    pageList = true;
+                }
+            })
+            if(pageList){
+                var _page_list = [];
+                _page_list.push('<span class="page-list">');
+                _page_list.push('<span class="btn-group dropup">');
+                _page_list.push('<button type="button" class="btn btn-default btn-outline dropdown-toggle" data-toggle="dropdown">');
+                _page_list.push('<span class="page-size">' + options.pageSize + '</span>');
+                _page_list.push('<span class="caret"></span>');
+                _page_list.push('</button>');
+                _page_list.push('<ul class="dropdown-menu" role="menu">');
+                $.each(options.pageList, function (i, page) {
+                    if(page == options.pageSize){
+                        _page_list.push('<li class="active"><a href="javascript:void(0)">' + page + '</a></li>');
+                    }
+                    else if(page >= target.totalRows && i === 1){
+                        _page_list.push('<li><a href="javascript:void(0)">' + page + '</a></li>');
+                    }
+                    else if(page <= target.totalRows){
+                        _page_list.push('<li><a href="javascript:void(0)">' + page + '</a></li>');
+                    }
+                })
+                _page_list.push('</ul>');
+                _page_list.push('</span>');
+                html.push(formatRecordsPerPage(_page_list.join('')))
+                html.push('</span>');
+            }
+            html.push('</div>');
+
+            if(totalPage > 1){
+                html.push('<div class="pull-right pagination">');
+                html.push('<ul class="pagination pagination-outline">');
+                html.push('<li class="page-pre"><a href="javascript:void(0)">' + options.paginationPreText + '</a></li>');
+                var from, to;
+                if (totalPage < 5) {
+                    from = 1;
+                    to = totalPage;
+                } else {
+                    from = currPage - 2;
+                    to = from + 4;
+                    if (from < 1) {
+                        from = 1;
+                        to = 5;
+                    }
+                    if (to > totalPage) {
+                        to = totalPage;
+                        from = to - 4;
+                    }
+                }
+
+                if (totalPage >= 6) {
+                    if (currPage >= 3) {
+                        html.push('<li class="page-first' + (1 == currPage ? ' active' : '') + '">', '<a href="javascript:void(0)">', 1, '</a>', '</li>');
+                        from++;
+                    }
+                    if (currPage >= 4) {
+                        if (currPage == 4 || totalPage == 6 || totalPage == 7) {
+                            from--;
+                        } else {
+                            html.push('<li class="page-first-separator disabled">', '<a href="javascript:void(0)">...</a>', '</li>');
+                        }
+                        to--;
+                    }
+                }
+
+                if (totalPage >= 7) {
+                    if (currPage >= (totalPage - 2)) {
+                        from--;
+                    }
+                }
+                if (totalPage == 6) {
+                    if (currPage >= (totalPage - 2)) {
+                        to++;
+                    }
+                } else if (totalPage >= 7) {
+                    if (totalPage == 7 || currPage >= (totalPage - 3)) {
+                        to++;
+                    }
+                }
+
+                for (var i = from; i <= to; i++) {
+                    html.push('<li class="page-number' + (i == currPage ? ' active' : '') + '">', '<a href="javascript:void(0)">', i, '</a>', '</li>');
+                }
+
+                if (totalPage >= 8) {
+                    if (currPage <= (totalPage - 4)) {
+                        html.push('<li class="page-last-separator disabled">', '<a href="javascript:void(0)">...</a>', '</li>');
+                    }
+                }
+
+                if (totalPage >= 6) {
+                    if (currPage <= (totalPage - 3)) {
+                        html.push('<li class="page-last' + (totalPage === currPage ? ' active' : '') + '">', '<a href="javascript:void(0)">', totalPage, '</a>', '</li>');
+                    }
+                }
+
+                html.push('<li class="page-next"><a href="javascript:void(0)">' + options.paginationNextText + '</a></li>');
+                html.push('</ul></div>');
+            }
+
+            $pagination.append(html.join(''));
+
+            var $pageList = $pagination.find('.page-list a');
+            var $pre = $pagination.find('.page-pre');
+            var $next = $pagination.find('.page-next');
+            var $number = $pagination.find('.page-number');
+            var $first = $pagination.find('.page-first');
+            var $last = $pagination.find('.page-last');
+            $pre.off('click').on('click', $.proxy(onPagePre, this));
+            $pageList.off('click').on('click', $.proxy(onPageListChange, this));
+            $number.off('click').on('click', $.proxy(onPageNumber, this));
+            $first.off('click').on('click', $.proxy(onPageFirst, this));
+            $last.off('click').on('click', $.proxy(onPageLast, this));
+            $next.off('click').on('click', $.proxy(onPageNext, this));
+        }
+        var onPageListChange = function(event){
+            var $this = $(event.currentTarget);
+            $this.parent().addClass('active').siblings().removeClass('active');
+            var $pagination = target.find(".fixed-table-pagination");
+            options.pageSize = $this.text().toUpperCase() === target.totalRows ? target.totalRows : + $this.text();
+            
+            if(target.totalRows < options.pageSize * options.pageNumber){
+                options.pageNumber = 1;
+            }
+            $pagination.find('.page-size').text(options.pageSize);
+            initServer();
+        }
+        var onPagePre = function(event){
+            if ((options.pageNumber - 1) === 0) {
+                options.pageNumber = target.totalPages;
+            } else {
+                options.pageNumber--;
+            }
+            initServer();
+        }
+        var onPageNumber = function(event){
+            if (options.pageNumber == $(event.currentTarget).text()) {
+                return;
+            }
+            options.pageNumber = $(event.currentTarget).text();
+            initServer();
+        }
+        var onPageFirst = function(event){
+            options.pageNumber = 1;
+            initServer();
+        }
+        var onPageLast = function (event) {
+            options.pageNumber = target.totalPages;
+            initServer();
+        }
+        var onPageNext = function(event){
+            if ((options.pageNumber + 1) > target.totalPages) {
+                options.pageNumber = 1;
+            } else {
+                options.pageNumber++;
+            }
+            initServer();
+        }
+        // 动态设置表头宽度
+        var autoTheadWidth = function(initFlag) {
+            if(options.height>0){
+                var $thead = target.find("thead");
+                var $tbody = target.find("tbody");
+                var borderWidth = parseInt(target.css("border-left-width")) + parseInt(target.css("border-right-width"))
+                
+                $thead.css("width", $tbody.children(":first").width());
+                if(initFlag){
+                    var resizeWaiter = false;
+                    $(window).resize(function() {
+                        if(!resizeWaiter){
+                            resizeWaiter = true;
+                            setTimeout(function(){
+                                if(!target.isFixWidth){
+                                    $tbody.css("width", target.parent().width()-borderWidth);
+                                }
+                                $thead.css("width", $tbody.children(":first").width());
+                                resizeWaiter = false;
+                            }, 300);
+                        }
+                    });
+                }
+            }
+        
+        }
+        // 缓存并格式化数据
+        var formatData = function(data) {
+            var _root = options.rootIdValue ? options.rootIdValue : null;
+            // 父节点属性列表
+            var parentCodes = [];
+            var rootFlag = false;
+            $.each(data, function(index, item) {
+            	if($.inArray(item[options.parentCode], parentCodes) == -1){
+            		parentCodes.push(item[options.parentCode]);
+                }
+            });
+            $.each(data, function(index, item) {
+                // 添加一个默认属性,用来判断当前节点有没有被显示
+                item.isShow = false;
+                // 是否分页
+                if (options.pagination) {
+                    if (item.isTreeLeaf == undefined || item.isTreeLeaf == null) {
+                        item.isTreeLeaf = false;
+                    } else {
+                        item.isTreeLeaf = (item["isTreeLeaf"] == 1 ? true: false) || ((item["isTreeLeaf"] == 'true' || item["isTreeLeaf"] == true) ? true: false);
+                    }
+                }
+                // 顶级节点校验判断,兼容0,'0','',null
+                var _defaultRootFlag = item[options.parentCode] == '0' ||
+                item[options.parentCode] == 0 ||
+                item[options.parentCode] == null ||
+                item[options.parentCode] == '' ||
+                $.inArray(item[options.code], parentCodes) > 0 && !rootFlag;
+                if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootIdValue) : _defaultRootFlag)) {
+                	rootFlag = true;
+                	if (!target.data_list["_root_"]) {
+                        target.data_list["_root_"] = [];
+                    }
+                    if (!target.data_obj["id_" + item[options.code]]) {
+                        target.data_list["_root_"].push(item);
+                    }
+                } else {
+                    if (!target.data_list["_n_" + item[options.parentCode]]) {
+                        target.data_list["_n_" + item[options.parentCode]] = [];
+                    }
+                    if (!target.data_obj["id_" + item[options.code]]) {
+                        target.data_list["_n_" + item[options.parentCode]].push(item);
+                    }
+                }
+                target.data_obj["id_" + item[options.code]] = item;
+            });
+        }
+        // 递归获取子节点并且设置子节点
+        var recursionNode = function(parentNode, lv, row_id, p_id, k) {
+            var $tbody = target.find("tbody");
+            var _ls = target.data_list["_n_" + parentNode[options.code]];
+            var $tr = renderRow(parentNode, _ls ? true : false, lv, row_id, p_id, options.pagination, k);
+            $tbody.append($tr);
+            if (_ls) {
+                $.each(_ls, function(i, item) {
+                    var _child_row_id = row_id + "_" + i
+                    recursionNode(item, (lv + 1), _child_row_id, row_id, item[options.code])
+                });
+            }
+        };
+        // 绘制行
+        var renderRow = function(item, isP, lv, row_id, p_id, _pagination, k) {
+            // 标记已显示
+            item.isShow = true;
+            item.row_id = row_id;
+            item.p_id = p_id;
+            item.lv = lv;
+            var $tr = $('<tr id="' + row_id + '" data-id="' + k + '"pid="' + p_id + '"></tr>');
+            var _icon = options.expanderCollapsedClass;
+            if (options.expandAll) {
+                $tr.css("display", "table");
+                _icon = options.expanderExpandedClass;
+            } else if (lv == 1) {
+                $tr.css("display", "table");
+                _icon = (options.expandFirst) ? options.expanderExpandedClass : options.expanderCollapsedClass;
+            } else if (lv == 2) {
+                if (options.expandFirst) {
+                    $tr.css("display", "table");
+                } else {
+                    $tr.css("display", "none");
+                }
+                _icon = options.expanderCollapsedClass;
+            } else if (_pagination) {
+                if (item.isTreeLeaf) {
+                    _icon = options.expanderCollapsedClass;
+                }
+            } else {
+                $tr.css("display", "none");
+                _icon = options.expanderCollapsedClass;
+            }
+            $.each(options.columns, function(index, column) {
+                // 判断有没有选择列
+                if (column.field == 'selectItem') {
+                    target.hasSelectItem = true;
+                    var $td = $('<td style="text-align:center;width:36px"></td>');
+                    if (column.radio) {
+                        var _ipt = $('<input name="select_item" type="radio" value="' + item[options.code] + '"></input>');
+                        $td.append(_ipt);
+                    }
+                    if (column.checkbox) {
+                        var _ipt = $('<input name="select_item" type="checkbox" value="' + item[options.code] + '"></input>');
+                        $td.append(_ipt);
+                    }
+                    $tr.append($td);
+                } else {
+                    var $td = $('<td name="' + column.field + '" class="' + column.field + '_cls"></td>');
+                    if(column.width){
+                        $td.css("width",column.width + (column.widthUnit ? column.widthUnit : 'px'));
+                    }
+                    if(column.align){
+                        $td.css("text-align",column.align);
+                    }
+                    if(options.expandColumn == index){
+                        $td.css("text-align","left");
+                    }
+                    if(column.valign){
+                        $td.css("vertical-align",column.valign);
+                    }
+                    if(options.showTitle){
+                        $td.addClass("ellipsis");
+                    }
+                    // 增加formatter渲染
+                    if (column.formatter) {
+                        $td.html(column.formatter.call(this, getItemField(item, column.field), item, index));
+                    } else {
+                        if(options.showTitle){
+                            // 只在字段没有formatter时才添加title属性
+                            $td.attr("title",item[column.field]);
+                        }
+                        $td.text(getItemField(item, column.field));
+                    }
+                    if (options.expandColumn == index) {
+                    	if (_pagination) {
+                    	    if (item["isTreeLeaf"]) {
+                    	        $td.prepend('<span class="treetable-expander ' + _icon + '"></span>');
+                    	    } else {
+                    	        $td.prepend('<span class="treetable-expander"></span>')
+                    	    }
+                    	} else {
+	                        if (!isP) {
+	                            $td.prepend('<span class="treetable-expander"></span>')
+	                        } else {
+	                            $td.prepend('<span class="treetable-expander ' + _icon + '"></span>');
+	                        }
+                    	}
+                    	for (var int = 0; int < (lv - options.expandColumn); int++) {
+                            $td.prepend('<span class="treetable-indent"></span>')
+                        }
+                    }
+                    $tr.append($td);
+                }
+            });
+            return $tr;
+        }
+        // 检索信息按钮点击事件
+        var registerSearchBtnClickEvent = function(btn) {
+            $(btn).off('click').on('click', function () {
+                $(".search-collapse").slideToggle();
+            });
+        }
+        // 注册刷新按钮点击事件
+        var registerRefreshBtnClickEvent = function(btn) {
+            $(btn).off('click').on('click', function () {
+                target.refresh();
+            });
+        }
+        // 注册列选项事件
+        var registerColumnClickEvent = function() {
+            $(".bootstrap-tree-table .treetable-bars .columns label input").off('click').on('click', function () {
+                var $this = $(this);
+                if($this.prop('checked')){
+                    target.showColumn($(this).val());
+                }else{
+                    target.hideColumn($(this).val());
+                }
+            });
+        }
+        // 注册行点击选中事件
+        var registerRowClickEvent = function() {
+            target.find("tbody").find("tr").unbind();
+            target.find("tbody").find("tr").click(function() {
+                if (target.hasSelectItem) {
+                    var _ipt = $(this).find("input[name='select_item']");
+                    if (_ipt.attr("type") == "radio") {
+                        _ipt.prop('checked', true);
+                        target.find("tbody").find("tr").removeClass("treetable-selected");
+                        $(this).addClass("treetable-selected");
+                    } else if (_ipt.attr("type") == "checkbox") {
+                    	if (_ipt.prop('checked')) {
+                    		_ipt.prop('checked', true);
+                    		target.find("tbody").find("tr").removeClass("treetable-selected");
+                    		$(this).addClass("treetable-selected");
+                    	} else {
+                    		_ipt.prop('checked', false);
+                    		target.find("tbody").find("tr").removeClass("treetable-selected");
+                    	}
+                    } else {
+                        if (_ipt.prop('checked')) {
+                            _ipt.prop('checked', false);
+                            $(this).removeClass("treetable-selected");
+                        } else {
+                            _ipt.prop('checked', true);
+                            $(this).addClass("treetable-selected");
+                        }
+                    }
+                    var _rowData = target.data_obj["id_" + $(this).data('id')];
+                    calculateObjectValue(options, options.onClickRow, [_rowData], _rowData);
+                }
+            });
+        }
+        // 注册小图标点击事件--展开缩起
+        var registerExpanderEvent = function() {
+            target.find("tbody").find("tr").find(".treetable-expander").unbind();
+            target.find("tbody").find("tr").find(".treetable-expander").click(function() {
+                var _isExpanded = $(this).hasClass(options.expanderExpandedClass);
+                var _isCollapsed = $(this).hasClass(options.expanderCollapsedClass);
+                if (_isExpanded || _isCollapsed) {
+                    var tr = $(this).parent().parent();
+                    var row_id = tr.attr("id");
+                    var row_pid = tr.attr("pid");
+                    var _id = tr.attr("data-id");
+                    var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']");
+                    if (!options.pagination) {
+	                    if (_isExpanded) {
+	                        $(this).removeClass(options.expanderExpandedClass);
+	                        $(this).addClass(options.expanderCollapsedClass);
+	                        if (_ls && _ls.length > 0) {
+	                            $.each(_ls, function(index, item) {
+	                                $(item).css("display", "none");
+	                            });
+	                        }
+	                    } else {
+	                        $(this).removeClass(options.expanderCollapsedClass);
+	                        $(this).addClass(options.expanderExpandedClass);
+	                        if (_ls && _ls.length > 0) {
+	                            $.each(_ls, function(index, item) {
+	                                var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander");
+	                                var _p_display = $("#" + $(item).attr("pid")).css('display');
+	                                if (_p_icon.hasClass(options.expanderExpandedClass) && _p_display == 'table') {
+	                                    $(item).css("display", "table");
+	                                }
+	                            });
+	                        }
+	                    }
+                    } else {
+                        var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']");
+                        if (_ls && _ls.length > 0) {
+                            if (_isExpanded) {
+                                if (row_pid == "row_root") {
+                                    $('table tr[id^="' + row_id + '_"]').css("display", "none");
+                                    $('table tr[id^="' + row_id + '_"]').each(function(i,n) {
+                                        var _isExpanded = $(n).find(".treetable-expander").hasClass(options.expanderExpandedClass);
+                                        if (_isExpanded) {
+                                            $(n).find(".treetable-expander").trigger("click");
+                                        }
+                                    })
+                                } else {
+                                    $.each(_ls, function(index, item) {
+                                        $(item).css("display", "none");
+                                        var _isExpanded = $(item).find(".treetable-expander").hasClass(options.expanderExpandedClass);
+                                        if (_isExpanded) {
+                                            $(item).find(".treetable-expander").trigger("click");
+                                        }
+                                     });
+                            	}
+                            } else {
+                                if (row_pid == "row_root") {
+                                    $('table tr[pid="' + row_id + '"]').css("display", "table");
+                                } else {
+	                                $.each(_ls, function(index, item) {
+	                                    var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander");
+                                        var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass);
+                                        var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass);
+	                                    if (row_id == $(item).attr("pid")) {
+		                                    $(item).css("display", "table");
+	                                    }
+	                                });
+                                }
+                            }
+                        } else {
+                            if (options.pagination) {
+                                var parms = {};
+                                parms[options.parentCode] = _id;
+                                if (options.dataUrl) {
+                                    $.ajax({
+                                        type: options.type,
+                                        url: options.dataUrl,
+                                        data: parms,
+                                        dataType: "json",
+                                        success: function(data, textStatus, jqXHR) {
+                                            $("#" + row_id + "_load").remove();
+                                            var list = data;
+                                            data = list;
+                                            target.appendData(data)
+                                        },
+                                        error: function(xhr, textStatus) {
+                                            var _errorMsg = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + xhr.responseText + '</div></td></tr>'
+                                            $("#" + row_id).after(_errorMsg);
+                                        }
+                                    });
+                                }
+                            }
+                        }
+                        if (_isExpanded) {
+                            $(this).removeClass(options.expanderExpandedClass);
+                            $(this).addClass(options.expanderCollapsedClass);
+                        } else {
+                            $(this).removeClass(options.expanderCollapsedClass);
+                            $(this).addClass(options.expanderExpandedClass);
+                        }
+                    }
+                }
+            });
+        }
+        // 刷新数据
+        target.refresh = function(parms) {
+            if(parms){
+                target.lastAjaxParams=parms;
+            }
+            initServer(target.lastAjaxParams);
+        }
+        // 添加数据刷新表格
+        target.appendData = function(data) {
+            data.reverse()
+            // 下边的操作主要是为了查询时让一些没有根节点的节点显示
+            $.each(data, function(i, item) {
+                if (options.pagination) {
+                    item.__nodes = (item["nodes"] == 1 ? true: false) || ((item["nodes"] == 'true' || item["nodes"] == true) ? true: false);
+                }
+                var _data = target.data_obj["id_" + item[options.code]];
+                var _p_data = target.data_obj["id_" + item[options.parentCode]];
+                var _c_list = target.data_list["_n_" + item[options.parentCode]];
+                var row_id = ""; //行id
+                var p_id = ""; //父行id
+                var _lv = 1; //如果没有父就是1默认显示
+                var tr; //要添加行的对象
+                if (_data && _data.row_id && _data.row_id != "") {
+                    row_id = _data.row_id; // 如果已经存在了,就直接引用原来的
+                }
+                if (_p_data) {
+                    p_id = _p_data.row_id;
+                    if (row_id == "") {
+                        var _tmp = 0
+                        if (_c_list && _c_list.length > 0) {
+                            _tmp = _c_list.length;
+                        }
+                        row_id = _p_data.row_id + "_" + _tmp;
+                    }
+                    _lv = _p_data.lv + 1; //如果有父
+                    // 绘制行
+                    tr = renderRow(item, true, _lv, row_id, p_id, options.pagination, item[options.code]);
+
+                    var _p_icon = $("#" + _p_data.row_id).children().eq(options.expandColumn).find(".treetable-expander");
+                    var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass);
+                    var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass);
+                    // 父节点有没有展开收缩按钮
+                    if (_isExpanded || _isCollapsed) {
+                        // 父节点展开状态显示新加行
+                        if (_isExpanded) {
+                            tr.css("display", "table");
+                        }
+                    } else {
+                        // 父节点没有展开收缩按钮则添加
+                        _p_icon.addClass(options.expanderCollapsedClass);
+                    }
+
+                    if (_data) {
+                        $("#" + _data.row_id).before(tr);
+                        $("#" + _data.row_id).remove();
+                    } else {
+                        // 计算父的同级下一行
+                        var _tmp_ls = _p_data.row_id.split("_");
+                        var _p_next = _p_data.row_id.substring(0, _p_data.row_id.length - (_tmp_ls[_tmp_ls.length - 1] + "").length) + (parseInt(_tmp_ls[_tmp_ls.length - 1]) + 1);
+                        $("#" + _p_data.row_id).after(tr);
+                    }
+                } else {
+                    tr = renderRow(item, false, _lv, row_id, p_id, options.pagination, item[options.code]);
+                    if (_data) {
+                        $("#" + _data.row_id).before(tr);
+                        $("#" + _data.row_id).remove();
+                    } else {
+                        // 画上
+                        var tbody = target.find("tbody");
+                        tbody.append(tr);
+                    }
+                }
+                item.isShow = true;
+                // 缓存并格式化数据
+                formatData([item]);
+            });
+            registerExpanderEvent();
+            registerRowClickEvent();
+            initHiddenColumns();
+        }
+
+        // 展开/折叠指定的行
+        target.toggleRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            $row_expander.trigger("click");
+        }
+        // 展开指定的行
+        target.expandRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            var _isCollapsed = $row_expander.hasClass(target.options.expanderCollapsedClass);
+            if (_isCollapsed) {
+                $row_expander.trigger("click");
+            }
+        }
+        // 折叠 指定的行
+        target.collapseRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            var _isExpanded = $row_expander.hasClass(target.options.expanderExpandedClass);
+            if (_isExpanded) {
+                $row_expander.trigger("click");
+            }
+        }
+        // 展开所有的行
+        target.expandAll=function() {
+            target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){
+                var _isCollapsed = $(n).hasClass(options.expanderCollapsedClass);
+                if (_isCollapsed) {
+                    $(n).trigger("click");
+                }
+            })
+        }
+        // 折叠所有的行
+        target.collapseAll=function() {
+            target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){
+                var _isExpanded = $(n).hasClass(options.expanderExpandedClass);
+                if (_isExpanded) {
+                    $(n).trigger("click");
+                }
+            })
+        }
+        // 显示指定列
+        target.showColumn=function(field,flag) {
+            var _index = $.inArray(field, target.hiddenColumns);
+            if (_index > -1) {
+                target.hiddenColumns.splice(_index, 1);
+            }
+            target.find("."+field+"_cls").show();
+            //是否更新列选项状态
+            if(flag&&options.showColumns){
+                var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']")
+                $input.prop("checked", 'checked');
+            }
+        }
+        // 隐藏指定列
+        target.hideColumn=function(field,flag) {
+            target.hiddenColumns.push(field);
+            target.find("."+field+"_cls").hide();
+            //是否更新列选项状态
+            if(flag&&options.showColumns){
+                var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']")
+                $input.prop("checked", '');
+            }
+        }
+        // ruoyi 解析数据,支持多层级访问
+        var getItemField = function (item, field) {
+            var value = item;
+
+            if (typeof field !== 'string' || item.hasOwnProperty(field)) {
+                return item[field];
+            }
+            var props = field.split('.');
+            for (var p in props) {
+                value = value && value[props[p]];
+            }
+            return value;
+        };
+        // ruoyi 发起对目标(target)函数的调用
+        var calculateObjectValue = function (self, name, args, defaultValue) {
+            var func = name;
+
+            if (typeof name === 'string') {
+                var names = name.split('.');
+
+                if (names.length > 1) {
+                    func = window;
+                    $.each(names, function (i, f) {
+                        func = func[f];
+                    });
+                } else {
+                    func = window[name];
+                }
+            }
+            if (typeof func === 'object') {
+                return func;
+            }
+            if (typeof func === 'function') {
+                return func.apply(self, args);
+            }
+            if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
+                return sprintf.apply(this, [name].concat(args));
+            }
+            return defaultValue;
+        };
+        var  formatRecordsPerPage =  function (pageNumber) {
+            // return '每页显示 ' + pageNumber + ' 条记录';
+            var texts = options.localeTexts[options.locale] || options.localeTexts['zh-CN'];
+            return texts.recordsPerPage.replace('{0}', pageNumber);
+        };
+        var formatShowingRows = function (pageFrom, pageTo, totalRows) {
+        	// return '显示第 ' + pageFrom + ' 到第 ' + pageTo + ' 条记录,总共 ' + totalRows + ' 条记录。';
+            var texts = options.localeTexts[options.locale] || options.localeTexts['zh-CN'];
+            return texts.showingRows
+                .replace('{0}', pageFrom)
+                .replace('{1}', pageTo)
+                .replace('{2}', totalRows);
+        };
+        // 初始化
+        init();
+        return target;
+    };
+
+    // 组件方法封装........
+    $.fn.bootstrapTreeTable.methods = {
+        // 为了兼容bootstrap-table的写法,统一返回数组,这里返回了表格显示列的数据
+        getSelections: function(target, data) {
+            // 所有被选中的记录input
+            var _ipt = target.find("tbody").find("tr").find("input[name='select_item']:checked");
+            var chk_value = [];
+            // 如果是radio
+            if (_ipt.attr("type") == "radio") {
+                var _data = target.data_obj["id_" + _ipt.val()];
+                chk_value.push(_data);
+            } else {
+                _ipt.each(function(_i, _item) {
+                    var _data = target.data_obj["id_" + $(_item).val()];
+                    chk_value.push(_data);
+                });
+            }
+            return chk_value;
+        },
+        // 刷新记录
+        refresh: function(target, parms) {
+            if (parms) {
+                target.refresh(parms);
+            } else {
+                target.refresh();
+            }
+        },
+        // 添加数据到表格
+        appendData: function(target, data) {
+            if (data) {
+                target.appendData(data);
+            }
+        },
+        // 展开/折叠指定的行
+        toggleRow: function(target, id) {
+            target.toggleRow(id);
+        },
+        // 展开指定的行
+        expandRow: function(target, id) {
+            target.expandRow(id);
+        },
+        // 折叠 指定的行
+        collapseRow: function(target, id) {
+            target.collapseRow(id);
+        },
+        // 展开所有的行
+        expandAll: function(target) {
+            target.expandAll();
+        },
+        // 折叠所有的行
+        collapseAll: function(target) {
+            target.collapseAll();
+        },
+        // 显示指定列
+        showColumn: function(target,field) {
+            target.showColumn(field,true);
+        },
+        // 隐藏指定列
+        hideColumn: function(target,field) {
+            target.hideColumn(field,true);
+        }
+        // 组件的其他方法也可以进行类似封装........
+    };
+
+    $.fn.bootstrapTreeTable.defaults = {
+        code: 'code',              // 选取记录返回的值,用于设置父子关系
+        parentCode: 'parentCode',  // 用于设置父子关系
+        rootIdValue: 0,            // 设置根节点id值----可指定根节点,默认为null,"",0,"0"
+        data: null,                // 构造table的数据集合
+        type: "GET",               // 请求数据的ajax类型
+        url: null,                 // 请求数据的ajax的url
+        ajaxParams: {},            // 请求数据的ajax的data属性
+        expandColumn: 1,           // 在哪一列上面显示展开按钮
+        expandAll: false,          // 是否全部展开
+        expandFirst: true,         // 是否默认第一级展开--expandAll为false时生效
+        striped: false,            // 是否各行渐变色
+        bordered: false,           // 是否显示边框
+        hover: true,               // 是否鼠标悬停
+        condensed: false,          // 是否紧缩表格
+        columns: [],               // 列
+        toolbar: null,             // 顶部工具条
+        height: 0,                 // 表格高度
+        pagination: false,         // 是否显示分页
+        dataUrl: null,             // 加载子节点异步请求数据url
+        pageNumber: 1,             // 当前页条数
+        pageSize: 10,              // 每页的记录行数
+        onClickRow: null,          // 单击某行事件
+        pageList: [10, 25, 50],    // 可供选择的每页的行数
+        showTitle: true,           // 是否采用title属性显示字段内容(被formatter格式化的字段不会显示)
+        showSearch: true,          // 是否显示检索信息
+        showColumns: true,         // 是否显示内容列下拉框
+        showRefresh: true,         // 是否显示刷新按钮
+        paginationPreText: '&lsaquo;',
+        paginationNextText: '&rsaquo;',
+        expanderExpandedClass: 'glyphicon glyphicon-chevron-down',   // 展开的按钮的图标
+        expanderCollapsedClass: 'glyphicon glyphicon-chevron-right', // 缩起的按钮的图标
+        responseHandler: function(res) {
+            return false;
+        },
+        onLoadSuccess: function(res) {
+            return false;
+        },
+        // 添加语言配置
+        locale: 'zh-CN',
+        // 添加可配置的文本
+        localeTexts: {
+            'zh-CN': {
+                search: '搜索',
+                refresh: '刷新',
+                columns: '列',
+                loading: '正在努力地加载数据中,请稍候……',
+                noData: '没有找到匹配的记录',
+                recordsPerPage: '每页显示 {0} 条记录',
+                showingRows: '显示第 {0} 到第 {1} 条记录,总共 {2} 条记录。'
+            },
+            'en-US': {
+                search: 'Search',
+                refresh: 'Refresh',
+                columns: 'Columns',
+                loading: 'Loading, please wait...',
+                noData: 'No matching records found',
+                recordsPerPage: '{0} rows per page',
+                showingRows: 'Showing {0} to {1} of {2} rows'
+            }
+        }
+    };
+})(jQuery);

+ 1032 - 1
ruoyi-admin/src/main/resources/static/i18n/messages.properties

@@ -4,6 +4,9 @@ sys.sysName=呼叫管理系统
 ## 弹框提示
 tips.defaultPass=您的密码还是初始密码,请修改密码!
 tips.security.title=安全提示
+tips.role.cust=客户
+tips.role.agent=坐席
+tips.role.kb=知识库查询
 ## 按钮
 btn.ok=确定
 btn.cancel=取消
@@ -56,7 +59,39 @@ user.login.title=登录呼叫管理系统
 user.login.welcome=欢迎使用
 
 # common
-common.adminPage.upload.requiredTips = 上传的文件不能为空
+common.adminPage.upload.requiredTips=上传的文件不能为空
+common.tip.selected.empty=请至少选择一条记录
+common.tip.del.confirm=确认要删除选中的数据吗?
+common.tip.remove.confirm=确认要删除该条数据吗?
+common.tip.clear.confirm=确定清空所有数据吗?
+common.tip.loading=正在处理中,请稍候...
+common.tip.export.confirm="确定导出所有数据吗?"
+common.tip.success=操作成功
+common.tip.fail=操作失败
+common.table.search=搜索
+common.table.col=列
+common.table.refresh=刷新
+common.tip.upload.success=文件导入成功!
+common.tip.upload.fail=文件导入失败,请检查文件格式是否正确!
+common.msg.callRecords.del.confirm=确定要删除该外呼任务和关联的通话记录吗?
+common.msg.callRecords.del.warning=删除后数据将无法恢复,请谨慎操作!
+common.tip.del.success=删除成功!
+common.tip.del.fail=删除失败,请稍后重试!
+menuTab.close_current=关闭当前
+menuTab.close_other=关闭其他
+menuTab.close_left=关闭左侧
+menuTab.close_right=关闭右侧
+menuTab.close_all=全部关闭
+menuTab.full=全屏显示
+menuTab.refresh=刷新页面
+menuTab.open=新窗口打开
+common.msg.copy.success=已复制到剪贴板
+common.msg.copy.fail=复制失败,请手动复制
+common.msg.unknown.media=不支持的媒体格式
+common.msg.file.notexists=文件不存在!
+common.time.hour=时
+common.time.minute=分
+common.time.second=秒
 
 # 电话工具条
 phonebar.placeholder.phonenum=请输入电话号码
@@ -106,6 +141,12 @@ phonebar.msg.free=空闲
 phonebar.msg.busy=忙碌
 phonebar.msg.customer_channel_hold=通话已保持
 phonebar.msg.customer_channel_unhold=通话已接回
+phonebar.msg.inner_consultation_start=咨询开始.
+phonebar.msg.inner_consultation_stop=咨询结束.
+phonebar.msg.transfer_call_success=电话转接成功.
+phonebar.msg.conferenceEnd=多方通话结束
+phonebar.msg.conferenceStart=多方通话进行中
+phonebar.msg.transferToConferenceSuccess=已接入多方会议
 phonebar.label.loginTime=签入时间:
 phonebar.label.agentStatus=状态:
 phonebar.label.queueStat=当前排队人数:
@@ -149,6 +190,17 @@ params.manage.form.paramType=参数类型:
 params.manage.form.title.add=新增callcenter参数配置
 params.manage.form.title.edit=修改callcenter参数配置
 
+# firewalld管理页面
+firewalld.manage.query.protocol=协议类型
+firewalld.manage.query.portStart=起始端口
+firewalld.manage.table.portEnd=结束端口
+firewalld.manage.table.enableFromSource=仅允许来源IP
+firewalld.manage.table.fromSource=来源IP
+firewalld.manage.table.opra=操作
+firewalld.manage.table.modalName=防火墙规则配置
+firewalld.manage.form.title.add=新增防火墙规则
+firewalld.manage.form.title.edit=修改防火墙规则
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=基础配置
 switchconf.label.more=更多
@@ -175,6 +227,10 @@ switchconf.cert.label=证书内容:
 switchconf.log.fs.label=freeswitch日志:
 switchconf.log.cc.label=callcenter日志:
 switchconf.log.error.label=错误日志:
+switchconf.asr.aws.header=亚马逊ASR参数配置
+switchconf.tts.aws.header=亚马逊tts参数配置
+switchconf.asr.deepgram.header=Deepgram ASR参数配置
+switchconf.tts.deepgram.header=Deepgram TTS参数配置
 
 # 业务组
 bizgroup.query.bizGroupName=业务组名称:
@@ -918,6 +974,7 @@ ivr.table.ttsText=话术内容
 ivr.table.action=处理方式
 ivr.table.opra=操作
 ivr.form.pressKeyInvalidTips.default=输入错误,请重新输入
+ivr.apply.confirmMessage=不要再外呼作业及呼入高峰期间,执行应用设置。否则会导致电话异常。
 
 callTask.form.taskType3=IVR外呼
 callTask.form.ivrId=IVR方案
@@ -931,6 +988,40 @@ ivr.form.hangupTips=挂机提示:
 ivr.form.aiInboundId=AI客服:
 ivr.form.aiInboundId.empty=请选择AI客服
 ivr.form.action.ai=转接到AI客服
+ivr.form.placeholder.ttsText=输入TTS文本或上传音频文件
+ivr.form.btn.title.tts=TTS合成
+ivr.form.btn.tts=合成
+ivr.form.btn.title.upload=上传WAV音频文件
+ivr.form.btn.upload=上传
+ivr.form.tips.helpblock=支持直接输入文本或上传WAV格式音频文件。输入//可调出变量列表
+ivr.form.title.ttsAudioPreview=音频预览
+ivr.form.placeholder.hangupTips=输入挂机提示文本或上传音频文件
+ivr.form.placeholder.pressKeyInvalidTips=输入挂机提示文本或上传音频文件
+ivr.form.tips.digitRange=请输入有效的正则表达式,用于验证用户输入格式
+ivr.validator.msg.checkDigit=该按键值在同一层级下已存在,请使用其他按键
+ivr.validator.msg.minLenCheck=最小长度不能大于最大长度
+ivr.validator.msg.maxLenCheck=最大长度不能小于最小长度
+ivr.validator.msg.validRegex=请输入有效的正则表达式格式
+ivr.edit.title=修改IVR配置
+ivr.add.title=新增IVR配置
+ivr.variableSelector.title=选择变量
+ivr.variableSelector.tip=点击插入
+ivr.modal.upload.title=上传音频文件
+ivr.modal.upload.label=选择WAV文件:
+ivr.modal.upload.helpblock=仅支持WAV格式,最大50MB
+ivr.modal.upload.progress=上传进度:
+ivr.modal.upload.confirm=确认上传
+ivr.msg.selectFile=请先选择文件!
+ivr.msg.wavOnly=请选择 WAV 格式的音频文件!
+ivr.msg.fileSizeLimit=文件大小不能超过 50MB!
+ivr.msg.uploadSuccess=上传成功!
+ivr.msg.uploadFail=上传失败:
+ivr.msg.networkError=网络错误:
+ivr.msg.ttsTextRequired=请输入需要合成的文本内容!
+ivr.msg.ttsSynthesizing=正在合成音频...
+ivr.msg.ttsSuccess=TTS合成成功!
+ivr.msg.ttsDataError=TTS合成返回数据格式错误!
+ivr.msg.ttsFail=TTS合成失败
 
 # 反向注册功能支持
 gateways.table.label.register2=反向注册模式
@@ -1061,4 +1152,944 @@ kbcontent.table.opra=操作
 kbcontent.form.title=标题:
 kbcontent.form.content=内容:
 
+# 用户管理
+system.user.list.boxTitle=组织机构
+system.user.list.btn.Dept=管理部门
+system.user.list.btn.Expand=展开
+system.user.list.btn.Collapse=折叠
+system.user.list.btn.Refresh=刷新部门
+system.user.list.query.loginName=登录账号:
+system.user.list.query.phonenumber=手机号码:
+system.user.list.query.status=用户状态:
+system.user.list.query.createName=创建时间:
+system.user.modalName=用户
+system.user.table.userId=用户
+system.user.table.loginName=登录账号
+system.user.table.userName=用户姓名
+system.user.table.deptName=部门
+system.user.table.email=邮箱
+system.user.table.phonenumber=手机
+system.user.table.userstatus=用户状态
+system.user.table.createTime=创建时间
+system.user.table.opra=操作
+system.user.table.btn.more=更多操作
+system.user.table.btn.resetPass=重置密码
+system.user.table.btn.authRole=分配角色
+system.user.form.head.base=基本信息
+system.user.form.loginName=登录账号:
+system.user.form.placeholder.loginName=请输入登录账号
+system.user.form.password=登录密码:
+system.user.form.placeholder.password=请输入登录密码
+system.user.form.tips.password=登录密码,鼠标按下显示密码
+system.user.form.userName=用户姓名:
+system.user.form.placeholder.userName=请输入用户姓名
+system.user.form.deptName=归属部门:
+system.user.form.placeholder.deptName=请选择归属部门
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=请输入手机号码
+system.user.form.email=邮箱:
+system.user.form.placeholder.email=请输入邮箱
+system.user.form.sex=用户性别:
+system.user.form.status=用户状态:
+system.user.form.post=岗位:
+system.user.form.role=角色:
+system.user.form.head.other=其他信息
+system.user.form.remark=备注:
+system.user.form.head.authRole=分配角色
+system.user.form.loginDate=最后登录时间
+system.user.form.loginIp=最后登录IP:
+system.user.form.updateTime=更新时间:
+system.user.form.updateBy=更新者:
+system.user.form.createTime=创建时间:
+system.user.form.createBy=创建者:
+system.user.authrole.table.roleId=角色编号
+system.user.authrole.table.roleSort=排序
+system.user.authrole.table.roleName=角色名称
+system.user.authrole.table.roleKey=权限字符
+system.user.authrole.table.createTime=创建时间
+system.user.resetpwd.password=输入密码:
+system.user.resetpwd.placeholder.password=请输入重置密码
+system.user.resetpwd.tips.password=重置密码,鼠标按下显示密码
+system.user.tips.disable=确认要停用用户吗?
+system.user.tips.enable=确认要启用用户吗?
+
+# 角色管理页面
+system.role.list.query.roleName=角色名称:
+system.role.list.query.roleKey=权限字符:
+system.role.list.query.status=角色状态:
+system.role.list.query.createTime=创建时间:
+system.role.modalName=角色
+system.role.table.roleId=角色编号
+system.role.table.roleName=角色名称
+system.role.table.roleKey=权限字符
+system.role.table.dataScope=数据权限
+system.role.table.roleSort=显示顺序
+system.role.table.roleStatus=角色状态
+system.role.table.createTime=创建时间
+system.role.table.opra=操作
+system.role.dataScope.all=全部数据权限
+system.role.dataScope.custom=自定义数据权限
+system.role.dataScope.dept=本部门数据权限
+system.role.dataScope.deptAndChild=本部门及以下数据权限
+system.role.dataScope.self=仅本人数据权限
+system.role.table.btn.dataScope=数据权限
+system.role.table.btn.authUser=分配用户
+system.role.table.btn.more=更多操作
+system.role.modal.dataScope=分配数据权限
+system.role.modal.authUser=分配用户
+system.role.confirm.disable=确认要停用角色吗?
+system.role.confirm.enable=确认要启用角色吗?
+
+# 角色管理-新增/修改页面
+system.role.add.title=新增角色
+system.role.edit.title=修改角色
+system.role.add.roleName=角色名称:
+system.role.add.roleKey=权限字符:
+system.role.add.roleKey.help=控制器中定义的权限字符,如:@RequiresRoles("")
+system.role.add.roleSort=显示顺序:
+system.role.add.status=状态:
+system.role.add.remark=备注:
+system.role.add.menuPermission=菜单权限:
+system.role.add.expandCollapse=展开/折叠
+system.role.add.selectAll=全选/全不选
+system.role.add.parentChildLink=父子联动
+system.role.add.validate.roleNameExists=角色名称已经存在
+system.role.add.validate.roleKeyExists=角色权限已经存在
+
+# 角色管理-分配用户页面
+system.role.authUser.title=角色分配用户
+system.role.authUser.query.loginName=登录名称:
+system.role.authUser.query.phonenumber=手机号码:
+system.role.authUser.btn.addUser=添加用户
+system.role.authUser.btn.cancelAuthBatch=批量取消授权
+system.role.authUser.btn.cancelAuth=取消授权
+system.role.authUser.table.userId=用户ID
+system.role.authUser.table.loginName=登录名称
+system.role.authUser.table.userName=用户名称
+system.role.authUser.table.email=邮箱
+system.role.authUser.table.phonenumber=手机
+system.role.authUser.table.status=用户状态
+system.role.authUser.table.createTime=创建时间
+system.role.authUser.table.opra=操作
+system.role.authUser.modal.selectUser=选择用户
+system.role.authUser.msg.selectOne=请至少选择一条记录
+system.role.authUser.confirm.cancelBatch=确认要删除选中的{0}条数据吗?
+system.role.authUser.confirm.cancelAuth=确认要取消该用户角色吗?
+
+# 角色管理-数据权限页面
+system.role.dataScope.title=角色数据权限
+system.role.dataScope.dataScope=数据范围:
+system.role.dataScope.help=特殊情况下,设置为"自定数据权限"
+system.role.dataScope.dataPermission=数据权限:
+
+# 角色管理-选择用户页面
+system.role.selectUser.title=分配角色选择用户
+
+# 部门管理-列表页面
+system.dept.list.title=部门列表
+system.dept.list.query.deptName=部门名称:
+system.dept.list.query.status=部门状态:
+system.dept.list.btn.expandCollapse=展开/折叠
+system.dept.modalName=部门
+system.dept.table.deptName=部门名称
+system.dept.table.orderNum=排序
+system.dept.table.status=状态
+system.dept.table.createTime=创建时间
+system.dept.table.opra=操作
+
+# 部门管理-表单(add和edit复用)
+system.dept.add.title=新增部门
+system.dept.edit.title=修改部门
+system.dept.form.parentDept=上级部门:
+system.dept.form.deptName=部门名称:
+system.dept.form.orderNum=显示排序:
+system.dept.form.leader=负责人:
+system.dept.form.phone=联系电话:
+system.dept.form.email=邮箱:
+system.dept.form.status=部门状态:
+
+# 部门管理-验证和提示
+system.dept.validate.deptNameExists=部门已经存在
+system.dept.msg.selectParentFirst=请先添加用户所属的部门!
+system.dept.msg.parentNotAllow=父部门不能选择
+system.dept.modal.selectDept=部门选择
+
+# 部门管理-树选择页面
+system.dept.tree.title=部门树选择
+system.dept.tree.keyword=关键字:
+system.dept.tree.btn.search= 搜索 
+system.dept.tree.btn.showSearch=显示搜索
+system.dept.tree.btn.hideSearch=隐藏搜索
+system.dept.tree.expand=展开
+system.dept.tree.collapse=折叠
+
+# 参数管理-列表页面
+system.config.list.title=参数列表
+system.config.list.query.configName=参数名称:
+system.config.list.query.configKey=参数键名:
+system.config.list.query.configType=系统内置:
+system.config.list.query.createTime=创建时间:
+system.config.list.btn.refreshCache=刷新缓存
+system.config.modalName=参数
+system.config.table.configId=参数主键
+system.config.table.configName=参数名称
+system.config.table.configKey=参数键名
+system.config.table.configValue=参数键值
+system.config.table.configType=系统内置
+system.config.table.remark=备注
+system.config.table.createTime=创建时间
+system.config.table.opra=操作
+
+# 参数管理-表单(add和edit复用)
+system.config.add.title=新增参数
+system.config.edit.title=修改参数
+system.config.form.configName=参数名称:
+system.config.form.configKey=参数键名:
+system.config.form.configValue=参数键值:
+system.config.form.configType=系统内置:
+system.config.form.remark=备注:
+
+# 参数管理-验证
+system.config.validate.configKeyExists=参数键名已经存在
+
+# 菜单管理-列表页面
+system.menu.list.title=菜单列表
+system.menu.list.query.menuName=菜单名称:
+system.menu.list.query.visible=菜单状态:
+system.menu.list.btn.expandCollapse=展开/折叠
+system.menu.modalName=菜单
+system.menu.table.menuName=菜单名称
+system.menu.table.orderNum=排序
+system.menu.table.url=请求地址
+system.menu.table.menuType=类型
+system.menu.table.visible=可见
+system.menu.table.perms=权限标识
+system.menu.table.opra=操作
+system.menu.type.catalog=目录
+system.menu.type.menu=菜单
+system.menu.type.button=按钮
+
+# 菜单管理-表单(add和edit复用)
+system.menu.add.title=新增菜单
+system.menu.edit.title=修改菜单
+system.menu.form.parentMenu=上级菜单:
+system.menu.form.menuType=菜单类型:
+system.menu.form.menuName=菜单名称:
+system.menu.form.menuCode=菜单编号:
+system.menu.form.url=请求地址:
+system.menu.form.url.help=访问的请求地址,如:`/system/user`,如外网地址需内链访问则以`http(s)://`开头
+system.menu.form.target=打开方式:
+system.menu.target.tab=页签
+system.menu.target.blank=新窗口
+system.menu.form.perms=权限标识:
+system.menu.form.perms.help=控制器中定义的权限标识,如:@RequiresPermissions("")
+system.menu.form.orderNum=显示排序:
+system.menu.form.orderNum.help=数字越小越靠前
+system.menu.form.icon=图标:
+system.menu.form.icon.help=单击选择需要使用的FontAwesome图标
+system.menu.form.icon.placeholder=选择图标
+system.menu.form.visible=菜单状态:
+system.menu.form.visible.help=选择隐藏则菜单将不会出现在侧边栏,也没有权限被访问
+system.menu.form.isRefresh=是否刷新:
+system.menu.form.isRefresh.help=打开菜单选项卡是否刷新页面
+
+# 菜单管理-验证和提示
+system.menu.validate.menuNameExists=菜单已经存在
+system.menu.modal.selectMenu=菜单选择
+system.menu.msg.mainMenuNotSelect=主菜单不能选择
+
+# 菜单管理-树选择页面
+system.menu.tree.title=菜单树选择
+system.menu.tree.keyword=关键字:
+system.menu.tree.btn.search= 搜索 
+system.menu.tree.btn.showSearch=显示搜索
+system.menu.tree.btn.hideSearch=隐藏搜索
+system.menu.tree.expand=展开
+system.menu.tree.collapse=折叠
+
+# 菜单管理-图标页面
+system.menu.icon.title=Font Awesome图标列表
+
+# 通用选项
+options.yes=是
+options.no=否
+
+# 用户个人信息页面
+system.profile.title=用户个人信息
+
+# Logo区域
+system.profile.logo.title=系统logo
+system.profile.logo.edit=修改logo
+system.profile.logo.modal.title=修改自定义logo
+
+# 个人资料区域
+system.profile.info.title=个人资料
+system.profile.info.loginName=登录名称:
+system.profile.info.phone=手机号码:
+system.profile.info.dept=所属部门:
+system.profile.info.email=邮箱地址:
+system.profile.info.createTime=创建时间:
+
+# 基本资料区域
+system.profile.basic.title=基本资料
+system.profile.tab.basic=基本资料
+system.profile.tab.password=修改密码
+
+# 表单字段
+system.profile.form.userName=用户名称:
+system.profile.form.userName.placeholder=请输入用户名称
+system.profile.form.phone=手机号码:
+system.profile.form.phone.placeholder=请输入手机号码
+system.profile.form.email=邮箱:
+system.profile.form.email.placeholder=请输入邮箱
+system.profile.form.sex=性别:
+system.profile.sex.male=男
+system.profile.sex.female=女
+
+# 头像
+system.profile.avatar.edit=修改头像
+system.profile.avatar.modal.title=修改用户头像
+
+# 修改密码
+system.profile.password.old=旧密码:
+system.profile.password.old.placeholder=请输入旧密码
+system.profile.password.new=新密码:
+system.profile.password.new.placeholder=请输入新密码
+system.profile.password.confirm=确认密码:
+system.profile.password.confirm.placeholder=请确认密码
+
+# 密码规则提示
+system.profile.password.rule.number=密码只能为0-9数字
+system.profile.password.rule.letter=密码只能为a-z和A-Z字母
+system.profile.password.rule.mix=密码必须包含(字母,数字)
+system.profile.password.rule.special=密码必须包含(字母,数字,特殊字符!@#$%^&*()-=_+)
+
+# 验证消息
+system.profile.validate.userName.required=请输入用户名称
+system.profile.validate.email.required=请输入邮箱
+system.profile.validate.email.exists=Email已经存在
+system.profile.validate.phone.required=请输入手机号码
+system.profile.validate.phone.exists=手机号码已经存在
+system.profile.password.validate.old.required=请输入原密码
+system.profile.password.validate.old.error=原密码错误
+system.profile.password.validate.new.required=请输入新密码
+system.profile.password.validate.new.minlength=密码不能小于6个字符
+system.profile.password.validate.new.maxlength=密码不能大于20个字符
+system.profile.password.validate.confirm.required=请再次输入新密码
+system.profile.password.validate.confirm.equalTo=两次密码输入不一致
+
+# 上传和裁剪
+system.profile.upload.image=上传图像
+system.profile.upload.image.type.error=请选择一个图片文件。
+system.profile.cropper.loading=裁剪框加载中,请稍候...
+
+# 通用按钮
+btn.confirm=确定
+
+# 岗位管理-列表页面
+system.post.list.title=岗位列表
+system.post.list.query.postCode=岗位编码:
+system.post.list.query.postName=岗位名称:
+system.post.list.query.status=岗位状态:
+system.post.modalName=岗位
+system.post.table.postId=岗位编号
+system.post.table.postCode=岗位编码
+system.post.table.postName=岗位名称
+system.post.table.postSort=显示顺序
+system.post.table.status=状态
+system.post.table.createTime=创建时间
+system.post.table.opra=操作
+
+# 岗位管理-表单(add和edit复用)
+system.post.add.title=新增岗位
+system.post.edit.title=修改岗位
+system.post.form.postName=岗位名称:
+system.post.form.postCode=岗位编码:
+system.post.form.postSort=显示顺序:
+system.post.form.status=岗位状态:
+system.post.form.remark=备注:
+
+# 岗位管理-验证
+system.post.validate.postCodeExists=岗位编码已经存在
+system.post.validate.postNameExists=岗位名称已经存在
+
+# 字典类型管理-列表页面
+system.dict.type.list.title=字典类型列表
+system.dict.type.list.query.dictName=字典名称:
+system.dict.type.list.query.dictType=字典类型:
+system.dict.type.list.query.status=字典状态:
+system.dict.type.list.query.createTime=创建时间:
+system.dict.type.list.btn.refreshCache=刷新缓存
+system.dict.type.modalName=类型
+system.dict.type.table.dictId=字典主键
+system.dict.type.table.dictName=字典名称
+system.dict.type.table.dictType=字典类型
+system.dict.type.table.status=状态
+system.dict.type.table.remark=备注
+system.dict.type.table.createTime=创建时间
+system.dict.type.table.opra=操作
+system.dict.type.btn.list=列表
+system.dict.type.modal.dictData=字典数据
+
+# 字典类型管理-表单(add和edit复用)
+system.dict.type.add.title=新增字典类型
+system.dict.type.edit.title=修改字典类型
+system.dict.type.form.dictName=字典名称:
+system.dict.type.form.dictType=字典类型:
+system.dict.type.form.dictType.help=数据存储中的Key值,如:sys_user_sex
+system.dict.type.form.status=状态:
+system.dict.type.form.remark=备注:
+
+# 字典类型管理-验证
+system.dict.type.validate.dictTypeExists=该字典类型已经存在
+
+# 字典树选择页面
+system.dict.tree.title=字典树选择
+system.dict.tree.keyword=关键字:
+system.dict.tree.btn.search= 搜索 
+system.dict.tree.btn.showSearch=显示搜索
+system.dict.tree.btn.hideSearch=隐藏搜索
+
+# 字典数据管理-列表页面
+system.dict.data.list.title=字典数据列表
+system.dict.data.list.query.dictType=字典名称:
+system.dict.data.list.query.dictLabel=字典标签:
+system.dict.data.list.query.status=数据状态:
+system.dict.data.modalName=数据
+system.dict.data.table.dictCode=字典编码
+system.dict.data.table.dictLabel=字典标签
+system.dict.data.table.dictValue=字典键值
+system.dict.data.table.dictSort=字典排序
+system.dict.data.table.status=状态
+system.dict.data.table.remark=备注
+system.dict.data.table.createTime=创建时间
+system.dict.data.table.opra=操作
+
+# 字典数据管理-表单(add和edit复用)
+system.dict.data.add.title=新增字典数据
+system.dict.data.edit.title=修改字典数据
+system.dict.data.form.dictLabel=字典标签:
+system.dict.data.form.dictValue=字典键值:
+system.dict.data.form.dictType=字典类型:
+system.dict.data.form.cssClass=样式属性:
+system.dict.data.form.dictSort=字典排序:
+system.dict.data.form.listClass=回显样式:
+system.dict.data.form.listClass.help=table表格字典列显示样式属性
+system.dict.data.form.isDefault=系统默认:
+system.dict.data.form.status=状态:
+system.dict.data.form.remark=备注:
+
+# 回显样式选项
+system.dict.data.listClass.default=默认
+system.dict.data.listClass.primary=主要
+system.dict.data.listClass.success=成功
+system.dict.data.listClass.info=信息
+system.dict.data.listClass.warning=警告
+system.dict.data.listClass.danger=危险
+
+# 通用选项
+options.pleaseSelect=---请选择---
+
+# 缓存监控页面
+system.cache.title=缓存监控
+system.cache.list.title=缓存列表
+system.cache.keys.title=键名列表
+system.cache.value.title=缓存内容
+system.cache.table.cacheName=缓存名称
+system.cache.table.cacheKey=缓存键名
+system.cache.table.operation=操作
+system.cache.btn.clear=清空
+system.cache.btn.clearAll=清理全部
+system.cache.label.cacheName=缓存名称:
+system.cache.label.cacheKey=缓存键名:
+system.cache.label.cacheValue=缓存内容:
+system.cache.msg.refreshListSuccess=刷新缓存列表成功
+system.cache.msg.refreshKeysSuccess=刷新键名列表成功
+system.cache.msg.clearCacheNameSuccess=清理缓存[{0}]成功
+system.cache.msg.clearCacheKeySuccess=清理缓存[{0}]成功
+system.cache.msg.clearAllSuccess=清理全部缓存成功
+
+# 登录日志页面
+system.logininfor.list.title=登录日志列表
+system.logininfor.list.query.ipaddr=登录地址:
+system.logininfor.list.query.loginName=登录名称:
+system.logininfor.list.query.status=登录状态:
+system.logininfor.list.query.loginTime=登录时间:
+system.logininfor.modalName=登录日志
+system.logininfor.table.infoId=访问编号
+system.logininfor.table.loginName=登录名称
+system.logininfor.table.ipaddr=登录地址
+system.logininfor.table.loginLocation=登录地点
+system.logininfor.table.browser=浏览器
+system.logininfor.table.os=操作系统
+system.logininfor.table.status=登录状态
+system.logininfor.table.msg=操作信息
+system.logininfor.table.loginTime=登录时间
+
+# 在线用户页面
+system.online.list.title=在线用户列表
+system.online.list.query.ipaddr=登录地址:
+system.online.list.query.loginName=登录名称:
+system.online.modalName=在线用户
+system.online.table.serialNumber=序号
+system.online.table.sessionId=会话编号
+system.online.table.loginName=登录名称
+system.online.table.deptName=部门名称
+system.online.table.ipaddr=主机
+system.online.table.loginLocation=登录地点
+system.online.table.browser=浏览器
+system.online.table.os=操作系统
+system.online.table.status=会话状态
+system.online.table.startTimestamp=登录时间
+system.online.table.lastAccessTime=最后访问时间
+system.online.table.operation=操作
+system.online.status.online=在线
+system.online.status.offline=离线
+system.online.msg.confirmForceLogout=确定要强制选中用户下线吗?
+system.online.msg.selectUser=请选择要强退的用户
+system.online.msg.confirmBatchForceLogout=确认要强退选中的{0}条数据吗?
+
+# 通用按钮(补充)
+btn.clean=清空
+btn.unlock=解锁
+btn.forceLogout=强退
+
+# 操作日志详细页面
+system.operlog.detail.title=操作日志详细
+system.operlog.detail.module=操作模块:
+system.operlog.detail.loginInfo=登录信息:
+system.operlog.detail.requestUrl=请求地址:
+system.operlog.detail.costTime=耗时
+system.operlog.detail.millisecond=毫秒
+system.operlog.detail.method=操作方法:
+system.operlog.detail.operParam=请求参数:
+system.operlog.detail.jsonResult=返回参数:
+system.operlog.detail.status=状态:
+system.operlog.detail.errorMsg=异常信息:
+system.operlog.status.normal=正常
+system.operlog.status.abnormal=异常
+
+# 操作日志列表页面
+system.operlog.list.title=操作日志列表
+system.operlog.list.query.operIp=操作地址:
+system.operlog.list.query.title=系统模块:
+system.operlog.list.query.operName=操作人员:
+system.operlog.list.query.businessType=操作类型:
+system.operlog.list.query.status=操作状态:
+system.operlog.list.query.operTime=操作时间:
+system.operlog.modalName=操作日志
+system.operlog.table.operId=日志编号
+system.operlog.table.title=系统模块
+system.operlog.table.businessType=操作类型
+system.operlog.table.operName=操作人员
+system.operlog.table.deptName=部门名称
+system.operlog.table.operIp=操作地址
+system.operlog.table.operLocation=操作地点
+system.operlog.table.status=操作状态
+system.operlog.table.operTime=操作时间
+system.operlog.table.costTime=消耗时间
+system.operlog.table.operation=操作
+system.operlog.status.success=成功
+system.operlog.status.fail=失败
+system.operlog.format.costTime=%s毫秒
+
+# 服务监控页面(netdata/system_io)
+system.netdata.title=服务监控
+system.netdata.chart.system.cpu=系统整体 CPU
+system.netdata.chart.system.ram=系统整体内存
+system.netdata.chart.system.io=系统磁盘 I/O
+system.netdata.chart.net.enp4s0=网卡流量
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 内存
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui 内存
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH 内存
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL 内存
+system.netdata.loading=加载中...
+system.netdata.status.offline=离线
+system.netdata.status.online=在线
+system.netdata.status.error=连接失败
+system.netdata.time.5min=5分钟
+system.netdata.time.15min=15分钟
+system.netdata.time.1hour=1小时
+system.netdata.time.24hour=24小时
+system.netdata.time.3day=3天
+system.netdata.btn.refresh=刷新
+system.netdata.stat.current=当前值
+system.netdata.stat.avg=平均值
+system.netdata.stat.max=最大值
+system.netdata.stat.min=最小值
+system.netdata.yaxis.cpu=CPU 使用率 (%)
+system.netdata.yaxis.mem=内存使用 (MiB)
+system.netdata.yaxis.io=磁盘 I/O (KiB/s)
+system.netdata.yaxis.net=网络流量 (kilobits/s)
+system.netdata.error.noData=无数据返回
+system.netdata.error.prefix=错误:
+
+# 服务器监控页面
+system.server.title=服务器监控
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=核心数
+system.server.cpu.used=用户使用率
+system.server.cpu.sys=系统使用率
+system.server.cpu.free=当前空闲率
+system.server.mem.title=内存
+system.server.mem.memory=内存
+system.server.mem.jvm=JVM
+system.server.mem.total=总内存
+system.server.mem.used=已用内存
+system.server.mem.free=剩余内存
+system.server.mem.usage=使用率
+system.server.sys.title=服务器信息
+system.server.sys.computerName=服务器名称
+system.server.sys.osName=操作系统
+system.server.sys.computerIp=服务器IP
+system.server.sys.osArch=系统架构
+system.server.sys.userDir=项目路径
+system.server.jvm.title=Java虚拟机信息
+system.server.jvm.name=Java名称
+system.server.jvm.version=Java版本
+system.server.jvm.startTime=启动时间
+system.server.jvm.runTime=运行时长
+system.server.jvm.home=安装路径
+system.server.jvm.inputArgs=运行参数
+system.server.disk.title=磁盘状态
+system.server.disk.dirName=盘符路径
+system.server.disk.sysTypeName=文件系统
+system.server.disk.typeName=盘符类型
+system.server.disk.total=总大小
+system.server.disk.free=可用大小
+system.server.disk.used=已用大小
+system.server.disk.usage=已用百分比
+system.server.table.property=属性
+system.server.table.value=值
+system.server.unit.count=个
+
+# 通知公告新增页面
+system.notice.add.title=新增通知公告
+system.notice.placeholder.noticeContent=请输入公告内容
+system.notice.msg.uploadFail=图片上传失败。
+
+# 通知公告修改页面
+system.notice.edit.title=修改通知公告
+
+# 通知公告列表页面
+system.notice.list.title=通知公告列表
+system.notice.list.query.noticeTitle=公告标题:
+system.notice.list.query.createBy=操作人员:
+system.notice.list.query.noticeType=公告类型:
+system.notice.modalName=公告
+system.notice.table.noticeId=序号
+system.notice.table.noticeTitle=公告标题
+system.notice.table.noticeType=公告类型
+system.notice.table.status=状态
+system.notice.table.createBy=创建者
+system.notice.table.createTime=创建时间
+system.notice.table.operation=操作
+
+# 通知公告表单标签
+system.notice.label.noticeTitle=公告标题:
+system.notice.label.noticeType=公告类型:
+system.notice.label.noticeContent=公告内容:
+system.notice.label.status=公告状态:
+
+# 通知公告查看页面
+system.notice.view.title=公告详细
+system.notice.view.sendTime=发送时间
+system.notice.view.sender=发件人
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=ASR语言
+callTask.form.asrLanguageCode.empty=请选择ASR语言
+callTask.form.ttsLanguageCode=TTS语言
+callTask.form.ttsLanguageCode.empty=请选择TTS语言
+inboundllm.form.asrLanguageCode=ASR语言
+inboundllm.form.asrLanguageCode.empty=请选择ASR语言
+inboundllm.form.ttsLanguageCode=TTS语言
+inboundllm.form.ttsLanguageCode.empty=请选择TTS语言
+ivr.form.asrLanguageCode=ASR语言
+ivr.form.asrLanguageCode.empty=请选择ASR语言
+ivr.form.ttsLanguageCode=TTS语言
+ivr.form.ttsLanguageCode.empty=请选择TTS语言
+
+# sys_config数据
+_sys.config.sys.index.skinName=主框架页-默认皮肤样式名称
+_sys.config.sys.user.initPassword=用户管理-账号初始密码
+_sys.config.sys.index.sideTheme=主框架页-侧边栏主题
+_sys.config.sys.account.registerUser=账号自助-是否开启用户注册功能
+_sys.config.sys.account.chrtype=用户管理-密码字符范围
+_sys.config.sys.account.initPasswordModify=用户管理-初始密码修改策略
+_sys.config.sys.account.passwordValidateDays=用户管理-账号密码更新周期
+_sys.config.sys.index.menuStyle=主框架页-菜单导航显示风格
+_sys.config.sys.index.footer=主框架页-是否开启页脚
+_sys.config.sys.index.tagsView=主框架页-是否开启页签
+_sys.config.sys.login.blackIPList=用户登录-黑名单列表
+_sys.config.sys.version=系统版本号
+_sys.config.sys.logo=系统默认logo
+_sys.config.config_asr_provider_aliyun=阿里云
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=电信
+_sys.config.config_asr_provider_aws=亚马逊
+_sys.config.config_tts_provider_aliyun=阿里云
+_sys.config.config_tts_provider_doubao=豆包
+_sys.config.config_tts_provider_chinatelecom=电信
+_sys.config.config_tts_provider_aws=亚马逊
+_sys.config.config_tts_language_aliyun_tts=阿里云tts语言设置
+_sys.config.config_tts_language_doubao_vcl_tts=豆包tts语言设置
+_sys.config.config_tts_language_aws_tts=亚马逊tts语言设置
+_sys.config.config_asr_language_aliyun=阿里云asr语言设置
+_sys.config.config_asr_language_aws=亚马逊asr语言设置
+
+# 字典类型
+_sys.dict.type.sys_user_sex=用户性别
+_sys.dict.type.sys_show_hide=菜单状态
+_sys.dict.type.sys_normal_disable=系统开关
+_sys.dict.type.sys_job_status=任务状态
+_sys.dict.type.sys_job_group=任务分组
+_sys.dict.type.sys_yes_no=系统是否
+_sys.dict.type.sys_notice_type=通知类型
+_sys.dict.type.sys_notice_status=通知状态
+_sys.dict.type.sys_oper_type=操作类型
+_sys.dict.type.sys_common_status=系统状态
+
+# 字典值
+_sys.dict.data.label.sys_user_sex.0=男
+_sys.dict.data.label.sys_user_sex.1=女
+_sys.dict.data.label.sys_user_sex.2=未知
+_sys.dict.data.label.sys_show_hide.0=显示
+_sys.dict.data.label.sys_show_hide.1=隐藏
+_sys.dict.data.label.sys_normal_disable.0=正常
+_sys.dict.data.label.sys_normal_disable.1=停用
+_sys.dict.data.label.sys_job_status.0=正常
+_sys.dict.data.label.sys_job_status.1=暂停
+_sys.dict.data.label.sys_job_group.DEFAULT=默认
+_sys.dict.data.label.sys_job_group.SYSTEM=系统
+_sys.dict.data.label.sys_yes_no.Y=是
+_sys.dict.data.label.sys_yes_no.N=否
+_sys.dict.data.label.sys_notice_type.1=通知
+_sys.dict.data.label.sys_notice_type.2=公告
+_sys.dict.data.label.sys_notice_status.0=正常
+_sys.dict.data.label.sys_notice_status.1=关闭
+_sys.dict.data.label.sys_oper_type.0=其他
+_sys.dict.data.label.sys_oper_type.1=新增
+_sys.dict.data.label.sys_oper_type.2=修改
+_sys.dict.data.label.sys_oper_type.3=删除
+_sys.dict.data.label.sys_oper_type.4=授权
+_sys.dict.data.label.sys_oper_type.5=导出
+_sys.dict.data.label.sys_oper_type.6=导入
+_sys.dict.data.label.sys_oper_type.7=强退
+_sys.dict.data.label.sys_oper_type.8=生成代码
+_sys.dict.data.label.sys_oper_type.9=清空数据
+_sys.dict.data.label.sys_common_status.0=成功
+_sys.dict.data.label.sys_common_status.1=失败
+
+# 系统参数
+_cc.params.phone_encrypted_key=电话工具条-外呼电话的解密key
+_cc.params.recording_path=录音保存路径
+_cc.params.enable_cc_record_stereo=是否开启立体音
+_cc.params.post_cdr_url=话单推送接口
+_cc.params.recordings_extension=录音类型
+_cc.params.conference_video_templates=可用的会议布局
+_cc.params.conference_gateway_addr=电话视频会议网关地址
+_cc.params.conference_recording_path=视频会议录音/录像保存路径
+_cc.params.conference_gateway_caller=电话视频会议主叫
+_cc.params.video_level_id_list=视频清晰度列表
+_cc.params.conference_outboud_profile=电话视频会议外呼的profile
+_cc.params.conference_video_layouts=电话视频会议的布局列表
+_cc.params.wait-for-second-vad-during-interrupt=是否开启二次打断设置(首次讲话不立即响应,等待客户第二次说话)
+_cc.params.outbound-call-extra-params-for-profile-internal2=外呼时携带的额外变量参数(用户控制呼叫行为及设置特性等)
+_cc.params.call_monitor_enabled=是否启用通话监听
+_cc.params.fs_log_file_path=FreeSWITCH日志路径
+_cc.params.cc_log_file_path=call-center日志路径
+_cc.params.fs_call_asr_enabled=是否启动双向asr语音识别并推送结果
+_cc.params.fs_call_asr_engine=双向asr语音识别,使用哪个asr引擎(chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=是否启用呼入电话排队监控
+_cc.params.fs_docker_container_name=Freeswitch的docker容器名称
+_cc.params.fs_conf_directory=Freeswitch配置文件路径
+_cc.params.robot-asr-type=语音识别类型; mrcp、websocket
+_cc.params.fs-asr-mrcp-param=mrcp 语音识别请求参数
+_cc.params.robot-max-no-speak-time=机器人通话中,最大客户静默时长; 客户超时不讲话通话会被自动掐断
+_cc.params.max-call-concurrency=呼入通话最大并发数限制
+_cc.params.asr-pause-enabled=asr按需识别,仅在机器话术播报完毕后才启动
+_cc.params.max-wait-time-after-vad-start=检测到客户说话开始后,等待说话结束的最大时长;秒
+_cc.params.inbound-transfer-agent-timeout=呼入电话转接坐席超时时间; 秒
+_cc.params.hide-inbound-number=转接到坐席时,隐藏呼入号码
+_cc.params.inbound-play-opnum=转接到坐席时,是否播报工号
+_cc.params.event-socket-ip=event socket ip address
+_cc.params.event-socket-port=event socket port
+_cc.params.event-socket-pass=event socket password
+_cc.params.event-socket-conn-pool-size=event socket connection pool size
+_cc.params.max-agent-number=最大坐席人数
+_cc.params.ws-server-auth-token-secret=解密并验证token的Key
+_cc.params.ws-server-port=(websocket server) 电话工具条端口
+_cc.params.vad-intelligent-wait=是否开启vad智能等待
+_cc.params.vad-intelligent-wait-ms=vad智能等待的毫秒数
+_cc.params.call-center-server-port=呼叫中心WebAPI的 server-port (只读)
+_cc.params.fs-deploy-type=FreeSWITCH 部署方式; docker/native
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH native部署方式的启动脚本路径
+_cc.params.fs-root-directory=FreeSWITCH 程序根目录
+_cc.params.call-center-server-ip-addr=服务器对外暴露的IP地址
+_cc.params.call-center-websocket-port=呼叫中心电话工具条端口
+_cc.params.tts_content_variables=话术中涉及到的变量
+_cc.params.call-center-api-token=语音通知的访问Token
+_cc.params.outbound-max-line-number=全局参数; 可用的最大外呼并发数
+_cc.params.outbound-enable-prediction-algorithm=群呼转人工坐席的场景下,是否开启预测外呼算法
+_cc.params.aliyun-tts-account-json=阿里云tts账号参数json
+_cc.params.empty-number-detection-enabled=空号识别功能是否开启
+_cc.params.empty-number-detection-config=空号识别定义
+_cc.params.default_interrupt_ignore_keywords=打断忽略关键字列表默认值
+_cc.params.llm-max-try=连接大模型的最大尝试次数
+_cc.params.llm-conn-timeout=连接大模型的超时时间; 毫秒
+_cc.params.llm-max-try-fail-tips=连接大模型的失败提示语
+_cc.params.api-client-white-ips=允许访问接口的ip白名单
+_cc.params.price_aliyun_asr=阿里云asr单价(每小时)
+_cc.params.price_aliyun_tts=阿里云tts单价(每千次)
+_cc.params.price_aliyun_tts_flow=阿里云声音克隆单价(每万字符)
+_cc.params.price_llm_token=大模型单价(每千token)
+_cc.params.doubao-tts-account-json=豆包tts账号参数json
+_cc.params.system_license_info=system license info
+_cc.params.system_hw_fingerprint_cmd=system_hw_fingerprint_cmd
+_cc.params.fs-inbound-acl-enabled=是否启用freeswitch的呼入白名单防护
+_cc.params.freeswitch_root=freeswitch根目录
+_cc.params.callTask_allowDel_days=间隔多少天允许删除任务及通话记录
+_cc.params.firewalld-config-path=firewalld 防火墙配置文件路径
+_cc.params.firewalld-restart-cmd=重启firewalld防火墙的命令
+_cc.params.firewalld-enabled=是否启用firewalld防护墙
+_cc.params.fs-inbound-allow-ip-list=呼入ip白名单(用于外部线路呼入)
+_cc.params.fs-register-allow-ip-list=分机注册的白名单 
+_cc.params.fs-register-acl-enabled=是否启用freeswitch的分机注册防护
+_cc.params.max-wait-time-customer-speaking=客户未说话静默时长
+
+# 新增功能
+_menu.firewalld=防火墙管理
+_menu.awsAsrConf=亚马逊ASR配置
+_menu.awsTtsConf=亚马逊TTS配置
+_menu.deepgramAsrConf=Deepgram ASR配置
+_menu.deepgramTtsConf=Deepgram TTS配置
+
+# 防火墙管理
+firewallRule.add.title=新增防火墙规则配置
+firewallRule.edit.title=修改防火墙规则配置
+firewallRule.notice.title=温馨提示
+firewallRule.notice.freeswitch=无需添加 Freeswitch 相关端口
+firewallRule.notice.ssh=无需添加 SSH 相关端口
+firewallRule.form.protocol=协议
+firewallRule.form.portStart=起始端口
+firewallRule.form.portEnd=结束端口
+firewallRule.form.enableFromSource=启用来源限制
+firewallRule.form.fromSource=来源地址
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=是
+firewallRule.boolean.no=否
+firewallRule.placeholder.portStart=请输入起始端口号(1-65535)
+firewallRule.placeholder.portEnd=请输入结束端口号(1-65535)
+firewallRule.placeholder.fromSource=请输入来源IP地址或网段
+firewallRule.validation.integer=请输入整数
+firewallRule.validation.portRange=起始端口必须小于等于结束端口
+firewallRule.validation.portStart.required=请输入起始端口
+firewallRule.validation.portEnd.required=请输入结束端口
+firewallRule.validation.port.range=端口范围必须在 1-65535 之间
+
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=预占
+_phoneBar.agentStatus.text_6=多方会议
+_phoneBar.agentStatus.text_5=话后整理
+_phoneBar.agentStatus.text_4=通话中
+_phoneBar.agentStatus.text_33=培训
+_phoneBar.agentStatus.text_32=会议
+_phoneBar.agentStatus.text_31=小休
+_phoneBar.agentStatus.text_3=置忙
+_phoneBar.agentStatus.text_2=置闲
+_phoneBar.agentStatus.text_1=已签入
+phonebar.msg.error_login_token=电话工具条:无法获取 loginToken!
+phonebar.msg.error_wss_domain=ERROR! 启用了wss之后,必须使用域名访问websocketServer! 
+phonebar.msg.websocket_not_supported=您的浏览器不支持websocket,您无法使用本页面的功能!
+phonebar.msg.hold_call_hangup=保持的通话已挂机.
+phonebar.msg.call_waiting=客户电话等待中.
+phonebar.msg.call_wait_retrieved=等待的电话已接回.
+phonebar.msg.consultation_started=咨询已开始.
+phonebar.msg.consultation_ended=咨询已结束.
+phonebar.msg.waiting_call_hangup=等待的客户已挂机.
+phonebar.msg.ipcc_disconnected=ipccserver 连接断开.
+phonebar.msg.select_group=请选择业务组!
+phonebar.msg.select_agent=请选择要咨询的坐席成员!
+phonebar.msg.select_free_agent=请选择空闲的坐席成员!
+phonebar.msg.cannot_consult_self=不能咨询自己,请选择其他坐席成员!
+phonebar.msg.select_transfer_group=请选择转接的业务组!
+phonebar.msg.select_transfer_agent=请选择转接的坐席成员!
+phonebar.msg.cannot_transfer_self=不能转给自己,请选择其他坐席成员!
+phonebar.msg.please_login=请先上线.
+phonebar.msg.no_call=当前没有通话.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=cant not send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=请输入外呼号码!
+phonebar.msg.invalid_phone=请输入正确格式的外呼号码!
+phonebar.msg.agent_locked=坐席已锁定
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false , 已禁用默认ui工具条按钮.
+phonebar.msg.logout_not_allowed=当前不允许签出!
+phonebar.msg.confirm_close=关闭网页将导致您无法接听电话,确定要关闭吗 ?
+phonebar.msg.esc_hangup=按下了ESC键, 即将发送挂机指令.
+phonebar.msg.confirm_transfer_conference=是否把当前通话转换为会议 ?
+phonebar.msg.enter_member_name=请填写参会者姓名!
+phonebar.msg.enter_member_phone=请填写参会者手机号!
+phonebar.msg.member_exists=会议成员已经存在,请不要重复添加!
+phonebar.msg.start_conference=正常发起多方通话
+phonebar.msg.provide_call_id=请提供待监听电话的 callId !
+phonebar.status.free=空闲
+phonebar.status.about_to_call=即将呼叫
+phonebar.status.in_call=通话中
+page.title=我的工作台
+phonebar.conference.label.type=会议类型
+phonebar.conference.option.video=视频
+phonebar.conference.option.audio=音频
+phonebar.conference.btn.start=启动会议
+phonebar.conference.btn.end=结束会议
+phonebar.conference.btn.join=加入会议
+phonebar.conference.btn.remove=移除
+phonebar.conference.btn.reinvite=重呼
+phonebar.conference.placeholder.name=姓名
+phonebar.conference.placeholder.phone=手机号
+phonebar.conference.alt.mute=禁言该成员
+phonebar.conference.alt.vmute=关闭该成员的视频
+phonebar.conference.title.remove=踢除会议成员
+phonebar.conference.title.reinvite=重新呼叫
+phonebar.transfer.label.group=业务组
+phonebar.transfer.label.member=坐席成员
+phonebar.transfer.option.select=请选择
+phonebar.transfer.placeholder.phone=电话号码
+phonebar.transfer.title.externalPhone=可以把当前通话转接到外线号码上。如果该文本框留空,则忽略处理。
+phonebar.transfer.btn.transfer=转接电话
+phonebar.transfer.title.transfer=把当前电话转接给他/她处理。
+phonebar.transfer.btn.retrieve=接回客户
+phonebar.transfer.title.retrieve=在咨询失败的情况下使用该按钮,接回处于等待中的电话。
+phonebar.transfer.btn.transferCall=转接客户
+phonebar.transfer.title.transferCall=在咨询成功的情况下使用该按钮,把电话转接给专家坐席。
+phonebar.transfer.btn.consult=拨号咨询
+phonebar.transfer.title.consult=拨号咨询
+phonebar.transfer.status.login=刚签入
+phonebar.transfer.status.free=空闲
+phonebar.transfer.status.busy=忙碌
+phonebar.transfer.status.calling=通话中
+phonebar.transfer.status.after=事后处理
+phonebar.popup.inbound=呼入弹屏
+phonebar.popup.outbound=外呼弹屏
+phonebar.chat.dialog.end=对话已结束。
+phonebar.chat.role.customer=客户
+phonebar.chat.role.agent=我
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS模型
+inboundllm.form.ttsModels.empty=请选择语音合成模型
+inboundllm.form.asrModels=ASR模型
+inboundllm.form.asrModels.empty=请选择语音识别模型
+callTask.form.ttsModels=TTS模型
+callTask.form.ttsModels.empty=请选择语音合成模型
+callTask.form.asrModels=ASR模型
+callTask.form.asrModels.empty=请选择语音识别模型
+ivr.form.ttsModels=TTS模型
+ivr.form.ttsModels.empty=请选择语音合成模型

+ 1032 - 2
ruoyi-admin/src/main/resources/static/i18n/messages_en.properties

@@ -4,6 +4,9 @@ sys.sysName=Call Management System
 ## 弹框提示
 tips.defaultPass=Your password is still the initial password. Please change it!
 tips.security.title=Security Tips
+tips.role.cust=Cust
+tips.role.agent=Agent
+tips.role.kb=Kb
 ## 按钮
 btn.ok=OK
 btn.cancel=Cancel
@@ -57,6 +60,38 @@ user.login.welcome=Welcome
 
 # common
 common.adminPage.upload.requiredTips = The uploaded file cannot be empty.
+common.tip.selected.empty=Please select at least one record
+common.tip.del.confirm=Are you sure you want to delete the selected data?
+common.tip.remove.confirm=Are you sure you want to delete this record?
+common.tip.clear.confirm=Are you sure you want to clear all data?
+common.tip.loading=Processing, please wait...
+common.tip.export.confirm="Are you sure you want to export all data?"
+common.tip.success=Operation successful
+common.tip.fail=Operation failed
+common.table.search=search
+common.table.col=col
+common.table.refresh=refresh
+common.tip.upload.success=File imported successfully!
+common.tip.upload.fail=File import failed. Please check if the file format is correct!
+common.msg.callRecords.del.confirm=Are you sure you want to delete this outbound task and its associated call records?
+common.msg.callRecords.del.warning=Deleted data cannot be recovered. Please proceed with caution!
+common.tip.del.success=Deleted successfully!
+common.tip.del.fail=Deletion failed. Please try again later!
+menuTab.close_current=Close Current
+menuTab.close_other=Close Others
+menuTab.close_left=Close Left
+menuTab.close_right=Close Right
+menuTab.close_all=Close All
+menuTab.full=Full Screen
+menuTab.refresh=Refresh
+menuTab.open=Open in New Window
+common.msg.copy.success=Copied to clipboard
+common.msg.copy.fail=Copy failed, please copy manually
+common.msg.unknown.media=Unsupported media format
+common.msg.file.notexists=File does not exist!
+common.time.hour=h
+common.time.minute=m
+common.time.second=s
 
 # 电话工具条
 phonebar.placeholder.phonenum=Please enter your phone number.
@@ -106,6 +141,12 @@ phonebar.msg.free=Free
 phonebar.msg.busy=Busy
 phonebar.msg.customer_channel_hold=Customer Channel Hold
 phonebar.msg.customer_channel_unhold=Customer Channel Hold Ended
+phonebar.msg.inner_consultation_start=Inner Consultation Start
+phonebar.msg.inner_consultation_stop=Inner Consultation Stop
+phonebar.msg.transfer_call_success=Transfer Call Success
+phonebar.msg.conferenceEnd=Conference End
+phonebar.msg.conferenceStart=Conference Start
+phonebar.msg.transferToConferenceSuccess=Transfer To Conference Success
 phonebar.label.loginTime=Extension Login Time:
 phonebar.label.agentStatus=Agent Status:
 phonebar.label.queueStat=Queue Agents:
@@ -149,6 +190,17 @@ params.manage.form.paramType=Parameter Type:
 params.manage.form.title.add=Add Callcenter Parameter Configuration
 params.manage.form.title.edit=Edit Callcenter Parameter Configuration
 
+# firewalld管理页面
+firewalld.manage.query.protocol=Protocol Type
+firewalld.manage.query.portStart=Start Port
+firewalld.manage.table.portEnd=End Port
+firewalld.manage.table.enableFromSource=Allow from Source IP Only
+firewalld.manage.table.fromSource=Source IP
+firewalld.manage.table.opra=Operation
+firewalld.manage.table.modalName=Firewall Rule Configuration
+firewalld.manage.form.title.add=Add Firewall Rule
+firewalld.manage.form.title.edit=Edit Firewall Rule
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=Basic Configurations
 switchconf.label.more=More
@@ -175,6 +227,10 @@ switchconf.cert.label=Certificate Content:
 switchconf.log.fs.label=Freeswitch Logs:
 switchconf.log.cc.label=Callcenter Logs:
 switchconf.log.error.label=Error Logs:
+switchconf.asr.aws.header=AWS ASR
+switchconf.tts.aws.header=AWS TTS
+switchconf.asr.deepgram.header=Deepgram ASR
+switchconf.tts.deepgram.header=Deepgram TTS
 
 # 业务组
 bizgroup.query.bizGroupName=Business Group Name:
@@ -561,8 +617,8 @@ llmAcount.form.name=Name:
 llmAcount.form.providerClassName=Provider Class Name:
 llmAcount.form.concurrentNum=Max Concurrency:
 llmAcount.form.interruptFlag=Interrupt Type:
-llmAcount.form.interruptFlag0=Never Interrupt
-llmAcount.form.interruptFlag1=KeyWords Interrupt
+llmAcount.form.interruptFlag0=Never Interrupt
+llmAcount.form.interruptFlag1=KeyWords Interrupt
 llmAcount.form.interruptFlag2=Always Interrupt
 llmAcount.form.transferManualDigit=Transfer Manual Digit:
 llmAcount.form.interruptKeywords=Interrupt Keywords:
@@ -918,6 +974,7 @@ ivr.table.ttsText=IVR Content
 ivr.table.action=IVR Action
 ivr.table.opra=Operation
 ivr.form.pressKeyInvalidTips.default=Invalid input, please try again. 
+ivr.apply.confirmMessage=Do not execute application settings during outbound calling operations or peak inbound call hours. This may cause call anomalies.
 
 callTask.form.taskType3=IVR
 callTask.form.ivrId=IVR
@@ -931,6 +988,40 @@ ivr.form.hangupTips=Hangup Tips:
 ivr.form.aiInboundId=AI Robot:
 ivr.form.aiInboundId.empty=Please Select an AI Robot
 ivr.form.action.ai=Transfer To AI Robot
+ivr.form.placeholder.ttsText=Enter TTS text or upload audio file
+ivr.form.btn.title.tts=TTS Synthesis
+ivr.form.btn.tts=Synthesize
+ivr.form.btn.title.upload=Upload WAV audio file
+ivr.form.btn.upload=Upload
+ivr.form.tips.helpblock=Supports direct text input or uploading WAV format audio files. Type // to bring up the variable list
+ivr.form.title.ttsAudioPreview=Audio Preview
+ivr.form.placeholder.hangupTips=Enter hangup prompt text or upload audio file
+ivr.form.placeholder.pressKeyInvalidTips=Enter invalid key prompt text or upload audio file
+ivr.form.tips.digitRange=Please enter a valid regular expression to validate user input format
+ivr.validator.msg.checkDigit=This key value already exists at the same level, please use a different key
+ivr.validator.msg.minLenCheck=Minimum length cannot be greater than maximum length
+ivr.validator.msg.maxLenCheck=Maximum length cannot be less than minimum length
+ivr.validator.msg.validRegex=Please enter a valid regular expression format
+ivr.edit.title=Edit IVR Configuration
+ivr.add.title=Add IVR Configuration
+ivr.variableSelector.title=Select Variable
+ivr.variableSelector.tip=Click to insert
+ivr.modal.upload.title=Upload Audio File
+ivr.modal.upload.label=Select WAV file:
+ivr.modal.upload.helpblock=WAV format only, max 50MB
+ivr.modal.upload.progress=Upload progress:
+ivr.modal.upload.confirm=Confirm Upload
+ivr.msg.selectFile=Please select a file first!
+ivr.msg.wavOnly=Please select a WAV format audio file!
+ivr.msg.fileSizeLimit=File size cannot exceed 50MB!
+ivr.msg.uploadSuccess=Upload successful!
+ivr.msg.uploadFail=Upload failed:
+ivr.msg.networkError=Network error:
+ivr.msg.ttsTextRequired=Please enter the text content to be synthesized!
+ivr.msg.ttsSynthesizing=Synthesizing audio...
+ivr.msg.ttsSuccess=TTS synthesis successful!
+ivr.msg.ttsDataError=TTS synthesis returned data format error!
+ivr.msg.ttsFail=TTS synthesis failed
 
 # 反向注册功能支持
 gateways.table.label.register2=Reverse Registration Mode
@@ -1061,5 +1152,944 @@ kbcontent.table.opra=Operation
 kbcontent.form.title=Title:
 kbcontent.form.content=Content:
 
+# 用户管理
+system.user.list.boxTitle=Dept
+system.user.list.btn.Dept=Manage Dept
+system.user.list.btn.Expand=Expand
+system.user.list.btn.Collapse=Collapse
+system.user.list.btn.Refresh=Refresh
+system.user.list.query.loginName=LoginName:
+system.user.list.query.phonenumber=Phonenumber:
+system.user.list.query.status=Status:
+system.user.list.query.createName=CreateName:
+system.user.modalName=User
+system.user.table.userId=User
+system.user.table.loginName=LoginName
+system.user.table.userName=UserName
+system.user.table.deptName=DeptName
+system.user.table.email=Email
+system.user.table.phonenumber=Phonenumber
+system.user.table.userstatus=Status
+system.user.table.createTime=CreateName
+system.user.table.opra=Operation
+system.user.table.btn.more=More
+system.user.table.btn.resetPass=ResetPass
+system.user.table.btn.authRole=AuthRole
+system.user.form.head.base=Base Info
+system.user.form.loginName=LoginName:
+system.user.form.placeholder.loginName=Please Input LoginName
+system.user.form.password=Password:
+system.user.form.placeholder.password=Please Input Password
+system.user.form.tips.password=Push Mouse Show Password
+system.user.form.userName=UserName:
+system.user.form.placeholder.userName=Please Input UserName
+system.user.form.deptName=DeptName:
+system.user.form.placeholder.deptName=Please Select DeptName
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=Please Input Phonenumber
+system.user.form.email=Email:
+system.user.form.placeholder.email=Please Input Email
+system.user.form.sex=Sex:
+system.user.form.status=Status:
+system.user.form.post=Post:
+system.user.form.role=Role:
+system.user.form.head.other=Other Info
+system.user.form.remark=Remark:
+system.user.form.head.authRole=AuthRole
+system.user.form.loginDate=Last Login Date
+system.user.form.loginIp=Last Login Ip:
+system.user.form.updateTime=UpdateTime:
+system.user.form.updateBy=UpdateBy:
+system.user.form.createTime=CreateTime:
+system.user.form.createBy=CreateBy:
+system.user.authrole.table.roleId=RoleId
+system.user.authrole.table.roleSort=RoleSort
+system.user.authrole.table.roleName=RoleName
+system.user.authrole.table.roleKey=RoleKey
+system.user.authrole.table.createTime=CreateTime
+system.user.resetpwd.password=Password:
+system.user.resetpwd.placeholder.password=Please Input Password
+system.user.resetpwd.tips.password=Push Mouse Show Password
+system.user.tips.disable=Are you sure you want to disable this user?
+system.user.tips.enable=Are you sure you want to enable this user?
+
+# Role Management Page
+system.role.list.query.roleName=Role Name:
+system.role.list.query.roleKey=Permission Key:
+system.role.list.query.status=Role Status:
+system.role.list.query.createTime=Create Time:
+system.role.modalName=Role
+system.role.table.roleId=Role ID
+system.role.table.roleName=Role Name
+system.role.table.roleKey=Permission Key
+system.role.table.dataScope=Data Scope
+system.role.table.roleSort=Display Order
+system.role.table.roleStatus=Role Status
+system.role.table.createTime=Create Time
+system.role.table.opra=Operation
+system.role.dataScope.all=All Data
+system.role.dataScope.custom=Custom Data
+system.role.dataScope.dept=Dept Data
+system.role.dataScope.deptAndChild=Dept and Child Data
+system.role.dataScope.self=Self Data Only
+system.role.table.btn.dataScope=Data Scope
+system.role.table.btn.authUser=Assign User
+system.role.table.btn.more=More
+system.role.modal.dataScope=Assign Data Scope
+system.role.modal.authUser=Assign User
+system.role.confirm.disable=Are you sure to disable this role?
+system.role.confirm.enable=Are you sure to enable this role?
+
+# Role Management - Add/Edit Page
+system.role.add.title=Add Role
+system.role.edit.title=Edit Role
+system.role.add.roleName=Role Name:
+system.role.add.roleKey=Permission Key:
+system.role.add.roleKey.help=Permission key defined in controller, e.g., @RequiresRoles("")
+system.role.add.roleSort=Display Order:
+system.role.add.status=Status:
+system.role.add.remark=Remark:
+system.role.add.menuPermission=Menu Permission:
+system.role.add.expandCollapse=Expand/Collapse
+system.role.add.selectAll=Select All/None
+system.role.add.parentChildLink=Parent-Child Link
+system.role.add.validate.roleNameExists=Role name already exists
+system.role.add.validate.roleKeyExists=Role permission already exists
+
+# Role Management - Auth User Page
+system.role.authUser.title=Assign Users to Role
+system.role.authUser.query.loginName=Login Name:
+system.role.authUser.query.phonenumber=Phone Number:
+system.role.authUser.btn.addUser=Add User
+system.role.authUser.btn.cancelAuthBatch=Batch Cancel Authorization
+system.role.authUser.btn.cancelAuth=Cancel Authorization
+system.role.authUser.table.userId=User ID
+system.role.authUser.table.loginName=Login Name
+system.role.authUser.table.userName=User Name
+system.role.authUser.table.email=Email
+system.role.authUser.table.phonenumber=Phone
+system.role.authUser.table.status=User Status
+system.role.authUser.table.createTime=Create Time
+system.role.authUser.table.opra=Operation
+system.role.authUser.modal.selectUser=Select User
+system.role.authUser.msg.selectOne=Please select at least one record
+system.role.authUser.confirm.cancelBatch=Are you sure to cancel authorization for {0} selected records?
+system.role.authUser.confirm.cancelAuth=Are you sure to cancel this user's role authorization?
+
+# Role Management - Data Scope Page
+system.role.dataScope.title=Role Data Scope
+system.role.dataScope.dataScope=Data Scope:
+system.role.dataScope.help=In special cases, set to "Custom Data Scope"
+system.role.dataScope.dataPermission=Data Permission:
+
+# Role Management - Select User Page
+system.role.selectUser.title=Select User for Role Assignment
+
+# Dept Management - List Page
+system.dept.list.title=Dept List
+system.dept.list.query.deptName=Dept Name:
+system.dept.list.query.status=Dept Status:
+system.dept.list.btn.expandCollapse=Expand/Collapse
+system.dept.modalName=Dept
+system.dept.table.deptName=Dept Name
+system.dept.table.orderNum=Order
+system.dept.table.status=Status
+system.dept.table.createTime=Create Time
+system.dept.table.opra=Operation
+
+# Dept Management - Form (shared by add and edit)
+system.dept.add.title=Add Dept
+system.dept.edit.title=Edit Dept
+system.dept.form.parentDept=Parent Dept:
+system.dept.form.deptName=Dept Name:
+system.dept.form.orderNum=Display Order:
+system.dept.form.leader=Leader:
+system.dept.form.phone=Phone:
+system.dept.form.email=Email:
+system.dept.form.status=Dept Status:
+
+# Dept Management - Validation and Messages
+system.dept.validate.deptNameExists=Dept already exists
+system.dept.msg.selectParentFirst=Please add the parent dept first!
+system.dept.msg.parentNotAllow=Parent dept not allowed
+system.dept.modal.selectDept=Select Dept
+
+# Dept Management - Tree Select Page
+system.dept.tree.title=Dept Tree Select
+system.dept.tree.keyword=Keyword:
+system.dept.tree.btn.search= Search 
+system.dept.tree.btn.showSearch=Show Search
+system.dept.tree.btn.hideSearch=Hide Search
+system.dept.tree.expand=Expand
+system.dept.tree.collapse=Collapse
+
+# Config Management - List Page
+system.config.list.title=Config List
+system.config.list.query.configName=Config Name:
+system.config.list.query.configKey=Config Key:
+system.config.list.query.configType=Built-in:
+system.config.list.query.createTime=Create Time:
+system.config.list.btn.refreshCache=Refresh Cache
+system.config.modalName=Config
+system.config.table.configId=Config ID
+system.config.table.configName=Config Name
+system.config.table.configKey=Config Key
+system.config.table.configValue=Config Value
+system.config.table.configType=Built-in
+system.config.table.remark=Remark
+system.config.table.createTime=Create Time
+system.config.table.opra=Operation
+
+# Config Management - Form (shared by add and edit)
+system.config.add.title=Add Config
+system.config.edit.title=Edit Config
+system.config.form.configName=Config Name:
+system.config.form.configKey=Config Key:
+system.config.form.configValue=Config Value:
+system.config.form.configType=Built-in:
+system.config.form.remark=Remark:
+
+# Config Management - Validation
+system.config.validate.configKeyExists=Config key already exists
+
+# Menu Management - List Page
+system.menu.list.title=Menu List
+system.menu.list.query.menuName=Menu Name:
+system.menu.list.query.visible=Menu Status:
+system.menu.list.btn.expandCollapse=Expand/Collapse
+system.menu.modalName=Menu
+system.menu.table.menuName=Menu Name
+system.menu.table.orderNum=Order
+system.menu.table.url=URL
+system.menu.table.menuType=Type
+system.menu.table.visible=Visible
+system.menu.table.perms=Permission
+system.menu.table.opra=Operation
+system.menu.type.catalog=Catalog
+system.menu.type.menu=Menu
+system.menu.type.button=Button
+
+# Menu Management - Form (shared by add and edit)
+system.menu.add.title=Add Menu
+system.menu.edit.title=Edit Menu
+system.menu.form.parentMenu=Parent Menu:
+system.menu.form.menuType=Menu Type:
+system.menu.form.menuName=Menu Name:
+system.menu.form.menuCode=Menu Code:
+system.menu.form.url=URL:
+system.menu.form.url.help=Request URL, e.g., `/system/user`, use `http(s)://` prefix for external links
+system.menu.form.target=Target:
+system.menu.target.tab=Tab
+system.menu.target.blank=New Window
+system.menu.form.perms=Permission:
+system.menu.form.perms.help=Permission identifier in controller, e.g., @RequiresPermissions("")
+system.menu.form.orderNum=Order:
+system.menu.form.orderNum.help=Smaller number comes first
+system.menu.form.icon=Icon:
+system.menu.form.icon.help=Click to select FontAwesome icon
+system.menu.form.icon.placeholder=Select Icon
+system.menu.form.visible=Visible:
+system.menu.form.visible.help=Hidden menus won't appear in sidebar and cannot be accessed
+system.menu.form.isRefresh=Refresh:
+system.menu.form.isRefresh.help=Whether to refresh page when opening menu tab
+
+# Menu Management - Validation and Messages
+system.menu.validate.menuNameExists=Menu already exists
+system.menu.modal.selectMenu=Select Menu
+system.menu.msg.mainMenuNotSelect=Main menu cannot be selected
+
+# Menu Management - Tree Select Page
+system.menu.tree.title=Menu Tree Select
+system.menu.tree.keyword=Keyword:
+system.menu.tree.btn.search= Search 
+system.menu.tree.btn.showSearch=Show Search
+system.menu.tree.btn.hideSearch=Hide Search
+system.menu.tree.expand=Expand
+system.menu.tree.collapse=Collapse
+
+# Menu Management - Icon Page
+system.menu.icon.title=Font Awesome Icon List
+
+# Common Options
+options.yes=Yes
+options.no=No
+
+# User Profile Page
+system.profile.title=User Profile
+
+# Logo Section
+system.profile.logo.title=System Logo
+system.profile.logo.edit=Edit Logo
+system.profile.logo.modal.title=Edit Custom Logo
+
+# Personal Info Section
+system.profile.info.title=Personal Info
+system.profile.info.loginName=Login Name:
+system.profile.info.phone=Phone:
+system.profile.info.dept=Department:
+system.profile.info.email=Email:
+system.profile.info.createTime=Create Time:
+
+# Basic Info Section
+system.profile.basic.title=Basic Info
+system.profile.tab.basic=Basic Info
+system.profile.tab.password=Change Password
+
+# Form Fields
+system.profile.form.userName=User Name:
+system.profile.form.userName.placeholder=Enter user name
+system.profile.form.phone=Phone:
+system.profile.form.phone.placeholder=Enter phone number
+system.profile.form.email=Email:
+system.profile.form.email.placeholder=Enter email
+system.profile.form.sex=Gender:
+system.profile.sex.male=Male
+system.profile.sex.female=Female
+
+# Avatar
+system.profile.avatar.edit=Edit Avatar
+system.profile.avatar.modal.title=Edit User Avatar
+
+# Change Password
+system.profile.password.old=Old Password:
+system.profile.password.old.placeholder=Enter old password
+system.profile.password.new=New Password:
+system.profile.password.new.placeholder=Enter new password
+system.profile.password.confirm=Confirm Password:
+system.profile.password.confirm.placeholder=Confirm new password
+
+# Password Rule Tips
+system.profile.password.rule.number=Password can only be 0-9 digits
+system.profile.password.rule.letter=Password can only be a-z and A-Z letters
+system.profile.password.rule.mix=Password must contain (letters, numbers)
+system.profile.password.rule.special=Password must contain (letters, numbers, special characters !@#$%^&*()-=_+)
+
+# Validation Messages
+system.profile.validate.userName.required=Please enter user name
+system.profile.validate.email.required=Please enter email
+system.profile.validate.email.exists=Email already exists
+system.profile.validate.phone.required=Please enter phone number
+system.profile.validate.phone.exists=Phone number already exists
+system.profile.password.validate.old.required=Please enter old password
+system.profile.password.validate.old.error=Old password is incorrect
+system.profile.password.validate.new.required=Please enter new password
+system.profile.password.validate.new.minlength=Password cannot be less than 6 characters
+system.profile.password.validate.new.maxlength=Password cannot be more than 20 characters
+system.profile.password.validate.confirm.required=Please enter new password again
+system.profile.password.validate.confirm.equalTo=Two password entries are inconsistent
+
+# Upload and Crop
+system.profile.upload.image=Upload Image
+system.profile.upload.image.type.error=Please select an image file.
+system.profile.cropper.loading=Cropper loading, please wait...
+
+# Common Buttons
+btn.confirm=Confirm
+
+# Post Management - List Page
+system.post.list.title=Post List
+system.post.list.query.postCode=Post Code:
+system.post.list.query.postName=Post Name:
+system.post.list.query.status=Post Status:
+system.post.modalName=Post
+system.post.table.postId=Post ID
+system.post.table.postCode=Post Code
+system.post.table.postName=Post Name
+system.post.table.postSort=Display Order
+system.post.table.status=Status
+system.post.table.createTime=Create Time
+system.post.table.opra=Operation
+
+# Post Management - Form (shared by add and edit)
+system.post.add.title=Add Post
+system.post.edit.title=Edit Post
+system.post.form.postName=Post Name:
+system.post.form.postCode=Post Code:
+system.post.form.postSort=Display Order:
+system.post.form.status=Post Status:
+system.post.form.remark=Remark:
+
+# Post Management - Validation
+system.post.validate.postCodeExists=Post code already exists
+system.post.validate.postNameExists=Post name already exists
+
+# Dict Type Management - List Page
+system.dict.type.list.title=Dict Type List
+system.dict.type.list.query.dictName=Dict Name:
+system.dict.type.list.query.dictType=Dict Type:
+system.dict.type.list.query.status=Dict Status:
+system.dict.type.list.query.createTime=Create Time:
+system.dict.type.list.btn.refreshCache=Refresh Cache
+system.dict.type.modalName=Type
+system.dict.type.table.dictId=Dict ID
+system.dict.type.table.dictName=Dict Name
+system.dict.type.table.dictType=Dict Type
+system.dict.type.table.status=Status
+system.dict.type.table.remark=Remark
+system.dict.type.table.createTime=Create Time
+system.dict.type.table.opra=Operation
+system.dict.type.btn.list=List
+system.dict.type.modal.dictData=Dict Data
+
+# Dict Type Management - Form (shared by add and edit)
+system.dict.type.add.title=Add Dict Type
+system.dict.type.edit.title=Edit Dict Type
+system.dict.type.form.dictName=Dict Name:
+system.dict.type.form.dictType=Dict Type:
+system.dict.type.form.dictType.help=Key value in data storage, e.g., sys_user_sex
+system.dict.type.form.status=Status:
+system.dict.type.form.remark=Remark:
+
+# Dict Type Management - Validation
+system.dict.type.validate.dictTypeExists=This dict type already exists
+
+# Dict Tree Select Page
+system.dict.tree.title=Dict Tree Select
+system.dict.tree.keyword=Keyword:
+system.dict.tree.btn.search= Search 
+system.dict.tree.btn.showSearch=Show Search
+system.dict.tree.btn.hideSearch=Hide Search
+
+# Dict Data Management - List Page
+system.dict.data.list.title=Dict Data List
+system.dict.data.list.query.dictType=Dict Name:
+system.dict.data.list.query.dictLabel=Dict Label:
+system.dict.data.list.query.status=Data Status:
+system.dict.data.modalName=Data
+system.dict.data.table.dictCode=Dict Code
+system.dict.data.table.dictLabel=Dict Label
+system.dict.data.table.dictValue=Dict Value
+system.dict.data.table.dictSort=Dict Sort
+system.dict.data.table.status=Status
+system.dict.data.table.remark=Remark
+system.dict.data.table.createTime=Create Time
+system.dict.data.table.opra=Operation
+
+# Dict Data Management - Form (shared by add and edit)
+system.dict.data.add.title=Add Dict Data
+system.dict.data.edit.title=Edit Dict Data
+system.dict.data.form.dictLabel=Dict Label:
+system.dict.data.form.dictValue=Dict Value:
+system.dict.data.form.dictType=Dict Type:
+system.dict.data.form.cssClass=CSS Class:
+system.dict.data.form.dictSort=Dict Sort:
+system.dict.data.form.listClass=List Class:
+system.dict.data.form.listClass.help=Table column display style attribute for dictionary
+system.dict.data.form.isDefault=Is Default:
+system.dict.data.form.status=Status:
+system.dict.data.form.remark=Remark:
+
+# List Class Options
+system.dict.data.listClass.default=Default
+system.dict.data.listClass.primary=Primary
+system.dict.data.listClass.success=Success
+system.dict.data.listClass.info=Info
+system.dict.data.listClass.warning=Warning
+system.dict.data.listClass.danger=Danger
+
+# Common Options
+options.pleaseSelect=---Please Select---
+
+# Cache Monitor Page
+system.cache.title=Cache Monitor
+system.cache.list.title=Cache List
+system.cache.keys.title=Key List
+system.cache.value.title=Cache Content
+system.cache.table.cacheName=Cache Name
+system.cache.table.cacheKey=Cache Key
+system.cache.table.operation=Operation
+system.cache.btn.clear=Clear
+system.cache.btn.clearAll=Clear All
+system.cache.label.cacheName=Cache Name:
+system.cache.label.cacheKey=Cache Key:
+system.cache.label.cacheValue=Cache Content:
+system.cache.msg.refreshListSuccess=Cache list refreshed successfully
+system.cache.msg.refreshKeysSuccess=Key list refreshed successfully
+system.cache.msg.clearCacheNameSuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearCacheKeySuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearAllSuccess=All caches cleared successfully
+
+# Login Log Page
+system.logininfor.list.title=Login Log List
+system.logininfor.list.query.ipaddr=Login Address:
+system.logininfor.list.query.loginName=Login Name:
+system.logininfor.list.query.status=Login Status:
+system.logininfor.list.query.loginTime=Login Time:
+system.logininfor.modalName=Login Log
+system.logininfor.table.infoId=Access ID
+system.logininfor.table.loginName=Login Name
+system.logininfor.table.ipaddr=Login Address
+system.logininfor.table.loginLocation=Login Location
+system.logininfor.table.browser=Browser
+system.logininfor.table.os=Operating System
+system.logininfor.table.status=Login Status
+system.logininfor.table.msg=Operation Info
+system.logininfor.table.loginTime=Login Time
+
+# Online User Page
+system.online.list.title=Online User List
+system.online.list.query.ipaddr=Login Address:
+system.online.list.query.loginName=Login Name:
+system.online.modalName=Online User
+system.online.table.serialNumber=No.
+system.online.table.sessionId=Session ID
+system.online.table.loginName=Login Name
+system.online.table.deptName=Department
+system.online.table.ipaddr=Host
+system.online.table.loginLocation=Login Location
+system.online.table.browser=Browser
+system.online.table.os=Operating System
+system.online.table.status=Session Status
+system.online.table.startTimestamp=Login Time
+system.online.table.lastAccessTime=Last Access Time
+system.online.table.operation=Operation
+system.online.status.online=Online
+system.online.status.offline=Offline
+system.online.msg.confirmForceLogout=Are you sure you want to force the selected user to log out?
+system.online.msg.selectUser=Please select users to force logout
+system.online.msg.confirmBatchForceLogout=Are you sure you want to force logout {0} selected users?
+
+# Common Buttons (additional)
+btn.clean=Clean
+btn.unlock=Unlock
+btn.forceLogout=Force Logout
+
+# Operation Log Detail Page
+system.operlog.detail.title=Operation Log Detail
+system.operlog.detail.module=Operation Module:
+system.operlog.detail.loginInfo=Login Info:
+system.operlog.detail.requestUrl=Request URL:
+system.operlog.detail.costTime=Cost Time
+system.operlog.detail.millisecond=ms
+system.operlog.detail.method=Operation Method:
+system.operlog.detail.operParam=Request Parameters:
+system.operlog.detail.jsonResult=Response Parameters:
+system.operlog.detail.status=Status:
+system.operlog.detail.errorMsg=Error Message:
+system.operlog.status.normal=Normal
+system.operlog.status.abnormal=Abnormal
+
+# Operation Log List Page
+system.operlog.list.title=Operation Log List
+system.operlog.list.query.operIp=Operation Address:
+system.operlog.list.query.title=System Module:
+system.operlog.list.query.operName=Operator:
+system.operlog.list.query.businessType=Operation Type:
+system.operlog.list.query.status=Operation Status:
+system.operlog.list.query.operTime=Operation Time:
+system.operlog.modalName=Operation Log
+system.operlog.table.operId=Log ID
+system.operlog.table.title=System Module
+system.operlog.table.businessType=Operation Type
+system.operlog.table.operName=Operator
+system.operlog.table.deptName=Department
+system.operlog.table.operIp=Operation Address
+system.operlog.table.operLocation=Operation Location
+system.operlog.table.status=Operation Status
+system.operlog.table.operTime=Operation Time
+system.operlog.table.costTime=Cost Time
+system.operlog.table.operation=Operation
+system.operlog.status.success=Success
+system.operlog.status.fail=Fail
+system.operlog.format.costTime=%sms
+
+# Service Monitor Page (netdata/system_io)
+system.netdata.title=Service Monitor
+system.netdata.chart.system.cpu=System CPU
+system.netdata.chart.system.ram=System RAM
+system.netdata.chart.system.io=System Disk I/O
+system.netdata.chart.net.enp4s0=Network Traffic
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 Memory
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui Memory
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH Memory
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL Memory
+system.netdata.loading=Loading...
+system.netdata.status.offline=Offline
+system.netdata.status.online=Online
+system.netdata.status.error=Connection Failed
+system.netdata.time.5min=5 Minutes
+system.netdata.time.15min=15 Minutes
+system.netdata.time.1hour=1 Hour
+system.netdata.time.24hour=24 Hours
+system.netdata.time.3day=3 Days
+system.netdata.btn.refresh=Refresh
+system.netdata.stat.current=Current
+system.netdata.stat.avg=Average
+system.netdata.stat.max=Max
+system.netdata.stat.min=Min
+system.netdata.yaxis.cpu=CPU Usage (%)
+system.netdata.yaxis.mem=Memory Usage (MiB)
+system.netdata.yaxis.io=Disk I/O (KiB/s)
+system.netdata.yaxis.net=Network Traffic (kilobits/s)
+system.netdata.error.noData=No data returned
+system.netdata.error.prefix=Error:
+
+# Server Monitor Page
+system.server.title=Server Monitor
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=Cores
+system.server.cpu.used=User Usage
+system.server.cpu.sys=System Usage
+system.server.cpu.free=Current Free
+system.server.mem.title=Memory
+system.server.mem.memory=Memory
+system.server.mem.jvm=JVM
+system.server.mem.total=Total
+system.server.mem.used=Used
+system.server.mem.free=Free
+system.server.mem.usage=Usage
+system.server.sys.title=Server Info
+system.server.sys.computerName=Server Name
+system.server.sys.osName=Operating System
+system.server.sys.computerIp=Server IP
+system.server.sys.osArch=System Architecture
+system.server.sys.userDir=Project Directory
+system.server.jvm.title=JVM Info
+system.server.jvm.name=Java Name
+system.server.jvm.version=Java Version
+system.server.jvm.startTime=Start Time
+system.server.jvm.runTime=Run Time
+system.server.jvm.home=Installation Path
+system.server.jvm.inputArgs=Runtime Arguments
+system.server.disk.title=Disk Status
+system.server.disk.dirName=Drive Path
+system.server.disk.sysTypeName=File System
+system.server.disk.typeName=Drive Type
+system.server.disk.total=Total Size
+system.server.disk.free=Free Size
+system.server.disk.used=Used Size
+system.server.disk.usage=Usage Percentage
+system.server.table.property=Property
+system.server.table.value=Value
+system.server.unit.count=cnt
+
+# Notice Add Page
+system.notice.add.title=Add Notice
+system.notice.placeholder.noticeContent=Please enter notice content
+system.notice.msg.uploadFail=Image upload failed.
+
+# Notice Edit Page
+system.notice.edit.title=Edit Notice
+
+# Notice List Page
+system.notice.list.title=Notice List
+system.notice.list.query.noticeTitle=Notice Title:
+system.notice.list.query.createBy=Creator:
+system.notice.list.query.noticeType=Notice Type:
+system.notice.modalName=Notice
+system.notice.table.noticeId=No.
+system.notice.table.noticeTitle=Notice Title
+system.notice.table.noticeType=Notice Type
+system.notice.table.status=Status
+system.notice.table.createBy=Creator
+system.notice.table.createTime=Create Time
+system.notice.table.operation=Operation
+
+# Notice Form Labels
+system.notice.label.noticeTitle=Notice Title:
+system.notice.label.noticeType=Notice Type:
+system.notice.label.noticeContent=Notice Content:
+system.notice.label.status=Notice Status:
+
+# Notice View Page
+system.notice.view.title=Notice Detail
+system.notice.view.sendTime=Send Time
+system.notice.view.sender=Sender
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=Language For ASR
+callTask.form.asrLanguageCode.empty=Please Select Language For ASR
+callTask.form.ttsLanguageCode=Language For TTS
+callTask.form.ttsLanguageCode.empty=Please Select Language For TTS
+inboundllm.form.asrLanguageCode=Language For ASR
+inboundllm.form.asrLanguageCode.empty=Please Select Language For ASR
+inboundllm.form.ttsLanguageCode=Language For TTS
+inboundllm.form.ttsLanguageCode.empty=Please Select Language For TTS
+ivr.form.asrLanguageCode=Language For ASR
+ivr.form.asrLanguageCode.empty=Please Select Language For ASR
+ivr.form.ttsLanguageCode=Language For TTS
+ivr.form.ttsLanguageCode.empty=Please Select Language For TTS
+
+# sys_config data
+_sys.config.sys.index.skinName=Default Skin Style for Main Framework Page
+_sys.config.sys.user.initPassword=User Management - Initial Account Password
+_sys.config.sys.index.sideTheme=Sidebar Theme for Main Framework Page
+_sys.config.sys.account.registerUser=Account Self-Service - Enable User Registration
+_sys.config.sys.account.chrtype=User Management - Password Character Range
+_sys.config.sys.account.initPasswordModify=User Management - Initial Password Change Policy
+_sys.config.sys.account.passwordValidateDays=User Management - Account Password Update Cycle
+_sys.config.sys.index.menuStyle=Main Framework Page - Menu Navigation Display Style
+_sys.config.sys.index.footer=Main Framework Page - Enable Footer
+_sys.config.sys.index.tagsView=Main Framework Page - Enable Tabs
+_sys.config.sys.login.blackIPList=User Login - Blacklist
+_sys.config.sys.version=System Version
+_sys.config.sys.logo=System Default Logo
+_sys.config.config_asr_provider_aliyun=Alibaba Cloud
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=China Telecom
+_sys.config.config_asr_provider_aws=Amazon
+_sys.config.config_tts_provider_aliyun=Alibaba Cloud
+_sys.config.config_tts_provider_doubao=Doubao
+_sys.config.config_tts_provider_chinatelecom=China Telecom
+_sys.config.config_tts_provider_aws=Amazon
+_sys.config.config_tts_language_aliyun_tts=Alibaba Cloud TTS Language Settings
+_sys.config.config_tts_language_doubao_vcl_tts=Doubao TTS Language Settings
+_sys.config.config_tts_language_aws_tts=Amazon TTS Language Settings
+_sys.config.config_asr_language_aliyun=Alibaba Cloud ASR Language Settings
+_sys.config.config_asr_language_aws=Amazon ASR Language Settings
+
+# Dictionary Types
+_sys.dict.type.sys_user_sex=User Gender
+_sys.dict.type.sys_show_hide=Menu Status
+_sys.dict.type.sys_normal_disable=System Switch
+_sys.dict.type.sys_job_status=Job Status
+_sys.dict.type.sys_job_group=Job Group
+_sys.dict.type.sys_yes_no=System Yes/No
+_sys.dict.type.sys_notice_type=Notice Type
+_sys.dict.type.sys_notice_status=Notice Status
+_sys.dict.type.sys_oper_type=Operation Type
+_sys.dict.type.sys_common_status=System Status
+
+# Dictionary Values
+_sys.dict.data.label.sys_user_sex.0=Male
+_sys.dict.data.label.sys_user_sex.1=Female
+_sys.dict.data.label.sys_user_sex.2=Unknown
+_sys.dict.data.label.sys_show_hide.0=Show
+_sys.dict.data.label.sys_show_hide.1=Hide
+_sys.dict.data.label.sys_normal_disable.0=Normal
+_sys.dict.data.label.sys_normal_disable.1=Disabled
+_sys.dict.data.label.sys_job_status.0=Normal
+_sys.dict.data.label.sys_job_status.1=Paused
+_sys.dict.data.label.sys_job_group.DEFAULT=Default
+_sys.dict.data.label.sys_job_group.SYSTEM=System
+_sys.dict.data.label.sys_yes_no.Y=Yes
+_sys.dict.data.label.sys_yes_no.N=No
+_sys.dict.data.label.sys_notice_type.1=Notification
+_sys.dict.data.label.sys_notice_type.2=Announcement
+_sys.dict.data.label.sys_notice_status.0=Normal
+_sys.dict.data.label.sys_notice_status.1=Closed
+_sys.dict.data.label.sys_oper_type.0=Other
+_sys.dict.data.label.sys_oper_type.1=Add
+_sys.dict.data.label.sys_oper_type.2=Edit
+_sys.dict.data.label.sys_oper_type.3=Delete
+_sys.dict.data.label.sys_oper_type.4=Authorize
+_sys.dict.data.label.sys_oper_type.5=Export
+_sys.dict.data.label.sys_oper_type.6=Import
+_sys.dict.data.label.sys_oper_type.7=Force Quit
+_sys.dict.data.label.sys_oper_type.8=Generate Code
+_sys.dict.data.label.sys_oper_type.9=Clear Data
+_sys.dict.data.label.sys_common_status.0=Success
+_sys.dict.data.label.sys_common_status.1=Failed
+
+# System Parameters
+_cc.params.phone_encrypted_key=Phone Toolbar - Encryption Key for Outbound Calls
+_cc.params.recording_path=Recording Save Path
+_cc.params.enable_cc_record_stereo=Enable Stereo Recording
+_cc.params.post_cdr_url=CDR Push Interface URL
+_cc.params.recordings_extension=Recording File Format
+_cc.params.conference_video_templates=Available Conference Layouts
+_cc.params.conference_gateway_addr=Video Conference Gateway Address
+_cc.params.conference_recording_path=Video Conference Recording Save Path
+_cc.params.conference_gateway_caller=Video Conference Caller Number
+_cc.params.video_level_id_list=Video Quality Level List
+_cc.params.conference_outboud_profile=Video Conference Outbound Profile
+_cc.params.conference_video_layouts=Video Conference Layout List
+_cc.params.wait-for-second-vad-during-interrupt=Enable Secondary Interruption Setting (Wait for Second Speech)
+_cc.params.outbound-call-extra-params-for-profile-internal2=Outbound Call Extra Parameters (Call Behavior Control)
+_cc.params.call_monitor_enabled=Enable Call Monitoring
+_cc.params.fs_log_file_path=FreeSWITCH Log File Path
+_cc.params.cc_log_file_path=Call Center Log File Path
+_cc.params.fs_call_asr_enabled=Enable Bidirectional ASR Recognition and Result Push
+_cc.params.fs_call_asr_engine=Bidirectional ASR Engine (chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=Enable Inbound Call Queue Monitoring
+_cc.params.fs_docker_container_name=FreeSWITCH Docker Container Name
+_cc.params.fs_conf_directory=FreeSWITCH Configuration Directory
+_cc.params.robot-asr-type=ASR Recognition Type (mrcp/websocket)
+_cc.params.fs-asr-mrcp-param=MRCP ASR Request Parameters
+_cc.params.robot-max-no-speak-time=Max Customer Silence Duration in Robot Call (Auto Hangup Timeout)
+_cc.params.max-call-concurrency=Max Inbound Call Concurrency Limit
+_cc.params.asr-pause-enabled=ASR On-Demand Recognition (Enable After Robot Speech)
+_cc.params.max-wait-time-after-vad-start=Max Wait Time After Speech Detection Start (seconds)
+_cc.params.inbound-transfer-agent-timeout=Inbound Call Transfer to Agent Timeout (seconds)
+_cc.params.hide-inbound-number=Hide Inbound Number When Transferring to Agent
+_cc.params.inbound-play-opnum=Play Agent Number When Transferring Inbound Call
+_cc.params.event-socket-ip=Event Socket IP Address
+_cc.params.event-socket-port=Event Socket Port
+_cc.params.event-socket-pass=Event Socket Password
+_cc.params.event-socket-conn-pool-size=Event Socket Connection Pool Size
+_cc.params.max-agent-number=Max Agent Number
+_cc.params.ws-server-auth-token-secret=Token Secret for Decryption and Verification
+_cc.params.ws-server-port=WebSocket Server Phone Toolbar Port
+_cc.params.vad-intelligent-wait=Enable VAD Intelligent Wait
+_cc.params.vad-intelligent-wait-ms=VAD Intelligent Wait Duration (milliseconds)
+_cc.params.call-center-server-port=Call Center WebAPI Server Port (Read-Only)
+_cc.params.fs-deploy-type=FreeSWITCH Deployment Type (docker/native)
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH Native Deployment Startup Script Path
+_cc.params.fs-root-directory=FreeSWITCH Program Root Directory
+_cc.params.call-center-server-ip-addr=Server External IP Address
+_cc.params.call-center-websocket-port=Call Center WebSocket Port
+_cc.params.tts_content_variables=TTS Content Variables in Scripts
+_cc.params.call-center-api-token=Voice Notification Access Token
+_cc.params.outbound-max-line-number=Global Parameter - Max Outbound Concurrency
+_cc.params.outbound-enable-prediction-algorithm=Enable Predictive Dialing Algorithm for Batch Calls
+_cc.params.aliyun-tts-account-json=Aliyun TTS Account Parameters JSON
+_cc.params.empty-number-detection-enabled=Enable Empty Number Detection
+_cc.params.empty-number-detection-config=Empty Number Detection Configuration
+_cc.params.default_interrupt_ignore_keywords=Default Interruption Ignore Keywords List
+_cc.params.llm-max-try=Max Retry Times for LLM Connection
+_cc.params.llm-conn-timeout=LLM Connection Timeout (milliseconds)
+_cc.params.llm-max-try-fail-tips=LLM Connection Failure Prompt Message
+_cc.params.api-client-white-ips=API Client IP Whitelist
+_cc.params.price_aliyun_asr=Aliyun ASR Price (per hour)
+_cc.params.price_aliyun_tts=Aliyun TTS Price (per 1k requests)
+_cc.params.price_aliyun_tts_flow=Aliyun Voice Cloning Price (per 10k characters)
+_cc.params.price_llm_token=LLM Price (per 1k tokens)
+_cc.params.doubao-tts-account-json=Doubao TTS Account Parameters JSON
+_cc.params.system_license_info=System License Information
+_cc.params.system_hw_fingerprint_cmd=System Hardware Fingerprint Command
+_cc.params.fs-inbound-acl-enabled=Enable FreeSWITCH Inbound ACL Protection
+_cc.params.freeswitch_root=FreeSWITCH Root Directory
+_cc.params.callTask_allowDel_days=Days Interval Allowed to Delete Call Tasks and Records
+_cc.params.firewalld-config-path=Firewalld Configuration File Path
+_cc.params.firewalld-restart-cmd=Firewalld Restart Command
+_cc.params.firewalld-enabled=Enable Firewalld Firewall
+_cc.params.fs-inbound-allow-ip-list=Inbound IP Whitelist (for External Trunk Inbound)
+_cc.params.fs-register-allow-ip-list=Extension Registration IP Whitelist
+_cc.params.fs-register-acl-enabled=Enable FreeSWITCH Extension Registration ACL Protection
+_cc.params.max-wait-time-customer-speaking=Max Time For Wait Customer Speaking
+
+# 新增功能
+_menu.firewalld=Firewalld Configuration
+_menu.awsAsrConf=AWS ASR Configuration
+_menu.awsTtsConf=AWS TTS Configuration
+_menu.deepgramAsrConf=Deepgram ASR Configuration
+_menu.deepgramTtsConf=Deepgram TTS Configuration
+
+# Firewalld Configuration
+firewallRule.add.title=Add Firewall Rule Configuration
+firewallRule.edit.title=Edit Firewall Rule Configuration
+firewallRule.notice.title=Notice
+firewallRule.notice.freeswitch=No need to add Freeswitch related ports
+firewallRule.notice.ssh=No need to add SSH related ports
+firewallRule.form.protocol=Protocol
+firewallRule.form.portStart=Start Port
+firewallRule.form.portEnd=End Port
+firewallRule.form.enableFromSource=Enable Source Restriction
+firewallRule.form.fromSource=Source Address
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=Yes
+firewallRule.boolean.no=No
+firewallRule.placeholder.portStart=Enter start port (1-65535)
+firewallRule.placeholder.portEnd=Enter end port (1-65535)
+firewallRule.placeholder.fromSource=Enter source IP address or subnet
+firewallRule.validation.integer=Please enter an integer
+firewallRule.validation.portRange=Start port must be less than or equal to end port
+firewallRule.validation.portStart.required=Please enter start port
+firewallRule.validation.portEnd.required=Please enter end port
+firewallRule.validation.port.range=Port range must be between 1-65535
 
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=Locked
+_phoneBar.agentStatus.text_6=Conference
+_phoneBar.agentStatus.text_5=FillForm
+_phoneBar.agentStatus.text_4=InCall
+_phoneBar.agentStatus.text_33=Training
+_phoneBar.agentStatus.text_32=Meeting
+_phoneBar.agentStatus.text_31=Rest
+_phoneBar.agentStatus.text_3=Busy
+_phoneBar.agentStatus.text_2=Free
+_phoneBar.agentStatus.text_1=JustLogin
+phonebar.msg.error_login_token=Phone Bar: Unable to get loginToken!
+phonebar.msg.error_wss_domain=ERROR! When WSS is enabled, domain name must be used to access websocketServer! 
+phonebar.msg.websocket_not_supported=Your browser does not support WebSocket, you cannot use the features of this page!
+phonebar.msg.hold_call_hangup=The held call has been hung up.
+phonebar.msg.call_waiting=Customer call is waiting.
+phonebar.msg.call_wait_retrieved=The waiting call has been retrieved.
+phonebar.msg.consultation_started=Consultation has started.
+phonebar.msg.consultation_ended=Consultation has ended.
+phonebar.msg.waiting_call_hangup=The waiting customer has hung up.
+phonebar.msg.ipcc_disconnected=ipccserver connection disconnected.
+phonebar.msg.select_group=Please select a business group!
+phonebar.msg.select_agent=Please select an agent to consult!
+phonebar.msg.select_free_agent=Please select an available agent!
+phonebar.msg.cannot_consult_self=Cannot consult yourself, please select another agent!
+phonebar.msg.select_transfer_group=Please select the transfer business group!
+phonebar.msg.select_transfer_agent=Please select the transfer agent!
+phonebar.msg.cannot_transfer_self=Cannot transfer to yourself, please select another agent!
+phonebar.msg.please_login=Please login first.
+phonebar.msg.no_call=No active call currently.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=Cannot send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=Please enter the phone number!
+phonebar.msg.invalid_phone=Please enter a valid phone number format!
+phonebar.msg.agent_locked=Agent is locked
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false, default UI toolbar buttons disabled.
+phonebar.msg.logout_not_allowed=Logout is not allowed currently!
+phonebar.msg.confirm_close=Closing the page will prevent you from answering calls, are you sure you want to close?
+phonebar.msg.esc_hangup=ESC key pressed, sending hangup command.
+phonebar.msg.confirm_transfer_conference=Do you want to transfer the current call to conference?
+phonebar.msg.enter_member_name=Please enter the participant name!
+phonebar.msg.enter_member_phone=Please enter the participant phone number!
+phonebar.msg.member_exists=Conference member already exists, please do not add again!
+phonebar.msg.start_conference=Starting multi-party call
+phonebar.msg.provide_call_id=Please provide the callId of the call to monitor!
+phonebar.status.free=Free
+phonebar.status.about_to_call=About to call
+phonebar.status.in_call=In call
+page.title=My Workbench
+phonebar.conference.label.type=Conference Type
+phonebar.conference.option.video=Video
+phonebarbar.conference.option.audio=Audio
+phonebar.conference.btn.start=Start Conference
+phonebar.conference.btn.end=End Conference
+phonebar.conference.btn.join=Join
+phonebar.conference.btn.remove=Remove
+phonebar.conference.btn.reinvite=Recall
+phonebar.conference.placeholder.name=Name
+phonebar.conference.placeholder.phone=Phone
+phonebar.conference.alt.mute=Mute this member
+phonebar.conference.alt.vmute=Turn off this member's video
+phonebar.conference.title.remove=Remove conference member
+phonebar.conference.title.reinvite=Recall member
+phonebar.transfer.label.group=Business Group
+phonebar.transfer.label.member=Agent Members
+phonebar.transfer.option.select=Please Select
+phonebar.transfer.placeholder.phone=Phone Number
+phonebar.transfer.title.externalPhone=Transfer current call to external number. Leave empty to ignore.
+phonebar.transfer.btn.transfer=Transfer Call
+phonebar.transfer.title.transfer=Transfer current call to selected agent.
+phonebar.transfer.btn.retrieve=Retrieve Customer
+phonebar.transfer.title.retrieve=Use this button to retrieve customer when consultation fails.
+phonebar.transfer.btn.transferCall=Transfer Customer
+phonebar.transfer.title.transferCall=Use this button to transfer call to expert agent when consultation succeeds.
+phonebar.transfer.btn.consult=Dial Consult
+phonebar.transfer.title.consult=Dial Consult
+phonebar.transfer.status.login=Just Logged In
+phonebar.transfer.status.free=Free
+phonebar.transfer.status.busy=Busy
+phonebar.transfer.status.calling=In Call
+phonebar.transfer.status.after=After Call Work
+phonebar.popup.inbound=Inbound Popup
+phonebar.popup.outbound=Outbound Popup
+phonebar.chat.dialog.end=Dialog ended.
+phonebar.chat.role.customer=Customer
+phonebar.chat.role.agent=Me
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS Models
+inboundllm.form.ttsModels.empty=Please Select TTS Models
+inboundllm.form.asrModels=ASR Models
+inboundllm.form.asrModels.empty=Please Select ASR Models
+callTask.form.ttsModels=TTS Models
+callTask.form.ttsModels.empty=Please Select TTS Models
+callTask.form.asrModels=ASR Models
+callTask.form.asrModels.empty=Please Select ASR Models
+ivr.form.ttsModels=TTS Models
+ivr.form.ttsModels.empty=Please Select TTS Models

+ 1032 - 2
ruoyi-admin/src/main/resources/static/i18n/messages_en_US.properties

@@ -4,6 +4,9 @@ sys.sysName=Call Management System
 ## 弹框提示
 tips.defaultPass=Your password is still the initial password. Please change it!
 tips.security.title=Security Tips
+tips.role.cust=Cust
+tips.role.agent=Agent
+tips.role.kb=Kb
 ## 按钮
 btn.ok=OK
 btn.cancel=Cancel
@@ -57,6 +60,38 @@ user.login.welcome=Welcome
 
 # common
 common.adminPage.upload.requiredTips = The uploaded file cannot be empty.
+common.tip.selected.empty=Please select at least one record
+common.tip.del.confirm=Are you sure you want to delete the selected data?
+common.tip.remove.confirm=Are you sure you want to delete this record?
+common.tip.clear.confirm=Are you sure you want to clear all data?
+common.tip.loading=Processing, please wait...
+common.tip.export.confirm="Are you sure you want to export all data?"
+common.tip.success=Operation successful
+common.tip.fail=Operation failed
+common.table.search=search
+common.table.col=col
+common.table.refresh=refresh
+common.tip.upload.success=File imported successfully!
+common.tip.upload.fail=File import failed. Please check if the file format is correct!
+common.msg.callRecords.del.confirm=Are you sure you want to delete this outbound task and its associated call records?
+common.msg.callRecords.del.warning=Deleted data cannot be recovered. Please proceed with caution!
+common.tip.del.success=Deleted successfully!
+common.tip.del.fail=Deletion failed. Please try again later!
+menuTab.close_current=Close Current
+menuTab.close_other=Close Others
+menuTab.close_left=Close Left
+menuTab.close_right=Close Right
+menuTab.close_all=Close All
+menuTab.full=Full Screen
+menuTab.refresh=Refresh
+menuTab.open=Open in New Window
+common.msg.copy.success=Copied to clipboard
+common.msg.copy.fail=Copy failed, please copy manually
+common.msg.unknown.media=Unsupported media format
+common.msg.file.notexists=File does not exist!
+common.time.hour=h
+common.time.minute=m
+common.time.second=s
 
 # 电话工具条
 phonebar.placeholder.phonenum=Please enter your phone number.
@@ -106,6 +141,12 @@ phonebar.msg.free=Free
 phonebar.msg.busy=Busy
 phonebar.msg.customer_channel_hold=Customer Channel Hold
 phonebar.msg.customer_channel_unhold=Customer Channel Hold Ended
+phonebar.msg.inner_consultation_start=Inner Consultation Start
+phonebar.msg.inner_consultation_stop=Inner Consultation Stop
+phonebar.msg.transfer_call_success=Transfer Call Success
+phonebar.msg.conferenceEnd=Conference End
+phonebar.msg.conferenceStart=Conference Start
+phonebar.msg.transferToConferenceSuccess=Transfer To Conference Success
 phonebar.label.loginTime=Extension Login Time:
 phonebar.label.agentStatus=Agent Status:
 phonebar.label.queueStat=Queue Agents:
@@ -149,6 +190,17 @@ params.manage.form.paramType=Parameter Type:
 params.manage.form.title.add=Add Callcenter Parameter Configuration
 params.manage.form.title.edit=Edit Callcenter Parameter Configuration
 
+# firewalld管理页面
+firewalld.manage.query.protocol=Protocol Type
+firewalld.manage.query.portStart=Start Port
+firewalld.manage.table.portEnd=End Port
+firewalld.manage.table.enableFromSource=Allow from Source IP Only
+firewalld.manage.table.fromSource=Source IP
+firewalld.manage.table.opra=Operation
+firewalld.manage.table.modalName=Firewall Rule Configuration
+firewalld.manage.form.title.add=Add Firewall Rule
+firewalld.manage.form.title.edit=Edit Firewall Rule
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=Basic Configurations
 switchconf.label.more=More
@@ -175,6 +227,10 @@ switchconf.cert.label=Certificate Content:
 switchconf.log.fs.label=Freeswitch Logs:
 switchconf.log.cc.label=Callcenter Logs:
 switchconf.log.error.label=Error Logs:
+switchconf.asr.aws.header=AWS ASR
+switchconf.tts.aws.header=AWS TTS
+switchconf.asr.deepgram.header=Deepgram ASR
+switchconf.tts.deepgram.header=Deepgram TTS
 
 # 业务组
 bizgroup.query.bizGroupName=Business Group Name:
@@ -561,8 +617,8 @@ llmAcount.form.name=Name:
 llmAcount.form.providerClassName=Provider Class Name:
 llmAcount.form.concurrentNum=Max Concurrency:
 llmAcount.form.interruptFlag=Interrupt Type:
-llmAcount.form.interruptFlag0=Never Interrupt
-llmAcount.form.interruptFlag1=KeyWords Interrupt
+llmAcount.form.interruptFlag0=Never Interrupt
+llmAcount.form.interruptFlag1=KeyWords Interrupt
 llmAcount.form.interruptFlag2=Always Interrupt
 llmAcount.form.transferManualDigit=Transfer Manual Digit:
 llmAcount.form.interruptKeywords=Interrupt Keywords:
@@ -918,6 +974,7 @@ ivr.table.ttsText=IVR Content
 ivr.table.action=IVR Action
 ivr.table.opra=Operation
 ivr.form.pressKeyInvalidTips.default=Invalid input, please try again. 
+ivr.apply.confirmMessage=Do not execute application settings during outbound calling operations or peak inbound call hours. This may cause call anomalies.
 
 callTask.form.taskType3=IVR
 callTask.form.ivrId=IVR
@@ -931,6 +988,40 @@ ivr.form.hangupTips=Hangup Tips:
 ivr.form.aiInboundId=AI Robot:
 ivr.form.aiInboundId.empty=Please Select an AI Robot
 ivr.form.action.ai=Transfer To AI Robot
+ivr.form.placeholder.ttsText=Enter TTS text or upload audio file
+ivr.form.btn.title.tts=TTS Synthesis
+ivr.form.btn.tts=Synthesize
+ivr.form.btn.title.upload=Upload WAV audio file
+ivr.form.btn.upload=Upload
+ivr.form.tips.helpblock=Supports direct text input or uploading WAV format audio files. Type // to bring up the variable list
+ivr.form.title.ttsAudioPreview=Audio Preview
+ivr.form.placeholder.hangupTips=Enter hangup prompt text or upload audio file
+ivr.form.placeholder.pressKeyInvalidTips=Enter invalid key prompt text or upload audio file
+ivr.form.tips.digitRange=Please enter a valid regular expression to validate user input format
+ivr.validator.msg.checkDigit=This key value already exists at the same level, please use a different key
+ivr.validator.msg.minLenCheck=Minimum length cannot be greater than maximum length
+ivr.validator.msg.maxLenCheck=Maximum length cannot be less than minimum length
+ivr.validator.msg.validRegex=Please enter a valid regular expression format
+ivr.edit.title=Edit IVR Configuration
+ivr.add.title=Add IVR Configuration
+ivr.variableSelector.title=Select Variable
+ivr.variableSelector.tip=Click to insert
+ivr.modal.upload.title=Upload Audio File
+ivr.modal.upload.label=Select WAV file:
+ivr.modal.upload.helpblock=WAV format only, max 50MB
+ivr.modal.upload.progress=Upload progress:
+ivr.modal.upload.confirm=Confirm Upload
+ivr.msg.selectFile=Please select a file first!
+ivr.msg.wavOnly=Please select a WAV format audio file!
+ivr.msg.fileSizeLimit=File size cannot exceed 50MB!
+ivr.msg.uploadSuccess=Upload successful!
+ivr.msg.uploadFail=Upload failed:
+ivr.msg.networkError=Network error:
+ivr.msg.ttsTextRequired=Please enter the text content to be synthesized!
+ivr.msg.ttsSynthesizing=Synthesizing audio...
+ivr.msg.ttsSuccess=TTS synthesis successful!
+ivr.msg.ttsDataError=TTS synthesis returned data format error!
+ivr.msg.ttsFail=TTS synthesis failed
 
 # 反向注册功能支持
 gateways.table.label.register2=Reverse Registration Mode
@@ -1061,5 +1152,944 @@ kbcontent.table.opra=Operation
 kbcontent.form.title=Title:
 kbcontent.form.content=Content:
 
+# 用户管理
+system.user.list.boxTitle=Dept
+system.user.list.btn.Dept=Manage Dept
+system.user.list.btn.Expand=Expand
+system.user.list.btn.Collapse=Collapse
+system.user.list.btn.Refresh=Refresh
+system.user.list.query.loginName=LoginName:
+system.user.list.query.phonenumber=Phonenumber:
+system.user.list.query.status=Status:
+system.user.list.query.createName=CreateName:
+system.user.modalName=User
+system.user.table.userId=User
+system.user.table.loginName=LoginName
+system.user.table.userName=UserName
+system.user.table.deptName=DeptName
+system.user.table.email=Email
+system.user.table.phonenumber=Phonenumber
+system.user.table.userstatus=Status
+system.user.table.createTime=CreateName
+system.user.table.opra=Operation
+system.user.table.btn.more=More
+system.user.table.btn.resetPass=ResetPass
+system.user.table.btn.authRole=AuthRole
+system.user.form.head.base=Base Info
+system.user.form.loginName=LoginName:
+system.user.form.placeholder.loginName=Please Input LoginName
+system.user.form.password=Password:
+system.user.form.placeholder.password=Please Input Password
+system.user.form.tips.password=Push Mouse Show Password
+system.user.form.userName=UserName:
+system.user.form.placeholder.userName=Please Input UserName
+system.user.form.deptName=DeptName:
+system.user.form.placeholder.deptName=Please Select DeptName
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=Please Input Phonenumber
+system.user.form.email=Email:
+system.user.form.placeholder.email=Please Input Email
+system.user.form.sex=Sex:
+system.user.form.status=Status:
+system.user.form.post=Post:
+system.user.form.role=Role:
+system.user.form.head.other=Other Info
+system.user.form.remark=Remark:
+system.user.form.head.authRole=AuthRole
+system.user.form.loginDate=Last Login Date
+system.user.form.loginIp=Last Login Ip:
+system.user.form.updateTime=UpdateTime:
+system.user.form.updateBy=UpdateBy:
+system.user.form.createTime=CreateTime:
+system.user.form.createBy=CreateBy:
+system.user.authrole.table.roleId=RoleId
+system.user.authrole.table.roleSort=RoleSort
+system.user.authrole.table.roleName=RoleName
+system.user.authrole.table.roleKey=RoleKey
+system.user.authrole.table.createTime=CreateTime
+system.user.resetpwd.password=Password:
+system.user.resetpwd.placeholder.password=Please Input Password
+system.user.resetpwd.tips.password=Push Mouse Show Password
+system.user.tips.disable=Are you sure you want to disable this user?
+system.user.tips.enable=Are you sure you want to enable this user?
+
+# Role Management Page
+system.role.list.query.roleName=Role Name:
+system.role.list.query.roleKey=Permission Key:
+system.role.list.query.status=Role Status:
+system.role.list.query.createTime=Create Time:
+system.role.modalName=Role
+system.role.table.roleId=Role ID
+system.role.table.roleName=Role Name
+system.role.table.roleKey=Permission Key
+system.role.table.dataScope=Data Scope
+system.role.table.roleSort=Display Order
+system.role.table.roleStatus=Role Status
+system.role.table.createTime=Create Time
+system.role.table.opra=Operation
+system.role.dataScope.all=All Data
+system.role.dataScope.custom=Custom Data
+system.role.dataScope.dept=Dept Data
+system.role.dataScope.deptAndChild=Dept and Child Data
+system.role.dataScope.self=Self Data Only
+system.role.table.btn.dataScope=Data Scope
+system.role.table.btn.authUser=Assign User
+system.role.table.btn.more=More
+system.role.modal.dataScope=Assign Data Scope
+system.role.modal.authUser=Assign User
+system.role.confirm.disable=Are you sure to disable this role?
+system.role.confirm.enable=Are you sure to enable this role?
+
+# Role Management - Add/Edit Page
+system.role.add.title=Add Role
+system.role.edit.title=Edit Role
+system.role.add.roleName=Role Name:
+system.role.add.roleKey=Permission Key:
+system.role.add.roleKey.help=Permission key defined in controller, e.g., @RequiresRoles("")
+system.role.add.roleSort=Display Order:
+system.role.add.status=Status:
+system.role.add.remark=Remark:
+system.role.add.menuPermission=Menu Permission:
+system.role.add.expandCollapse=Expand/Collapse
+system.role.add.selectAll=Select All/None
+system.role.add.parentChildLink=Parent-Child Link
+system.role.add.validate.roleNameExists=Role name already exists
+system.role.add.validate.roleKeyExists=Role permission already exists
+
+# Role Management - Auth User Page
+system.role.authUser.title=Assign Users to Role
+system.role.authUser.query.loginName=Login Name:
+system.role.authUser.query.phonenumber=Phone Number:
+system.role.authUser.btn.addUser=Add User
+system.role.authUser.btn.cancelAuthBatch=Batch Cancel Authorization
+system.role.authUser.btn.cancelAuth=Cancel Authorization
+system.role.authUser.table.userId=User ID
+system.role.authUser.table.loginName=Login Name
+system.role.authUser.table.userName=User Name
+system.role.authUser.table.email=Email
+system.role.authUser.table.phonenumber=Phone
+system.role.authUser.table.status=User Status
+system.role.authUser.table.createTime=Create Time
+system.role.authUser.table.opra=Operation
+system.role.authUser.modal.selectUser=Select User
+system.role.authUser.msg.selectOne=Please select at least one record
+system.role.authUser.confirm.cancelBatch=Are you sure to cancel authorization for {0} selected records?
+system.role.authUser.confirm.cancelAuth=Are you sure to cancel this user's role authorization?
+
+# Role Management - Data Scope Page
+system.role.dataScope.title=Role Data Scope
+system.role.dataScope.dataScope=Data Scope:
+system.role.dataScope.help=In special cases, set to "Custom Data Scope"
+system.role.dataScope.dataPermission=Data Permission:
+
+# Role Management - Select User Page
+system.role.selectUser.title=Select User for Role Assignment
+
+# Dept Management - List Page
+system.dept.list.title=Dept List
+system.dept.list.query.deptName=Dept Name:
+system.dept.list.query.status=Dept Status:
+system.dept.list.btn.expandCollapse=Expand/Collapse
+system.dept.modalName=Dept
+system.dept.table.deptName=Dept Name
+system.dept.table.orderNum=Order
+system.dept.table.status=Status
+system.dept.table.createTime=Create Time
+system.dept.table.opra=Operation
+
+# Dept Management - Form (shared by add and edit)
+system.dept.add.title=Add Dept
+system.dept.edit.title=Edit Dept
+system.dept.form.parentDept=Parent Dept:
+system.dept.form.deptName=Dept Name:
+system.dept.form.orderNum=Display Order:
+system.dept.form.leader=Leader:
+system.dept.form.phone=Phone:
+system.dept.form.email=Email:
+system.dept.form.status=Dept Status:
+
+# Dept Management - Validation and Messages
+system.dept.validate.deptNameExists=Dept already exists
+system.dept.msg.selectParentFirst=Please add the parent dept first!
+system.dept.msg.parentNotAllow=Parent dept not allowed
+system.dept.modal.selectDept=Select Dept
+
+# Dept Management - Tree Select Page
+system.dept.tree.title=Dept Tree Select
+system.dept.tree.keyword=Keyword:
+system.dept.tree.btn.search= Search 
+system.dept.tree.btn.showSearch=Show Search
+system.dept.tree.btn.hideSearch=Hide Search
+system.dept.tree.expand=Expand
+system.dept.tree.collapse=Collapse
+
+# Config Management - List Page
+system.config.list.title=Config List
+system.config.list.query.configName=Config Name:
+system.config.list.query.configKey=Config Key:
+system.config.list.query.configType=Built-in:
+system.config.list.query.createTime=Create Time:
+system.config.list.btn.refreshCache=Refresh Cache
+system.config.modalName=Config
+system.config.table.configId=Config ID
+system.config.table.configName=Config Name
+system.config.table.configKey=Config Key
+system.config.table.configValue=Config Value
+system.config.table.configType=Built-in
+system.config.table.remark=Remark
+system.config.table.createTime=Create Time
+system.config.table.opra=Operation
+
+# Config Management - Form (shared by add and edit)
+system.config.add.title=Add Config
+system.config.edit.title=Edit Config
+system.config.form.configName=Config Name:
+system.config.form.configKey=Config Key:
+system.config.form.configValue=Config Value:
+system.config.form.configType=Built-in:
+system.config.form.remark=Remark:
+
+# Config Management - Validation
+system.config.validate.configKeyExists=Config key already exists
+
+# Menu Management - List Page
+system.menu.list.title=Menu List
+system.menu.list.query.menuName=Menu Name:
+system.menu.list.query.visible=Menu Status:
+system.menu.list.btn.expandCollapse=Expand/Collapse
+system.menu.modalName=Menu
+system.menu.table.menuName=Menu Name
+system.menu.table.orderNum=Order
+system.menu.table.url=URL
+system.menu.table.menuType=Type
+system.menu.table.visible=Visible
+system.menu.table.perms=Permission
+system.menu.table.opra=Operation
+system.menu.type.catalog=Catalog
+system.menu.type.menu=Menu
+system.menu.type.button=Button
+
+# Menu Management - Form (shared by add and edit)
+system.menu.add.title=Add Menu
+system.menu.edit.title=Edit Menu
+system.menu.form.parentMenu=Parent Menu:
+system.menu.form.menuType=Menu Type:
+system.menu.form.menuName=Menu Name:
+system.menu.form.menuCode=Menu Code:
+system.menu.form.url=URL:
+system.menu.form.url.help=Request URL, e.g., `/system/user`, use `http(s)://` prefix for external links
+system.menu.form.target=Target:
+system.menu.target.tab=Tab
+system.menu.target.blank=New Window
+system.menu.form.perms=Permission:
+system.menu.form.perms.help=Permission identifier in controller, e.g., @RequiresPermissions("")
+system.menu.form.orderNum=Order:
+system.menu.form.orderNum.help=Smaller number comes first
+system.menu.form.icon=Icon:
+system.menu.form.icon.help=Click to select FontAwesome icon
+system.menu.form.icon.placeholder=Select Icon
+system.menu.form.visible=Visible:
+system.menu.form.visible.help=Hidden menus won't appear in sidebar and cannot be accessed
+system.menu.form.isRefresh=Refresh:
+system.menu.form.isRefresh.help=Whether to refresh page when opening menu tab
+
+# Menu Management - Validation and Messages
+system.menu.validate.menuNameExists=Menu already exists
+system.menu.modal.selectMenu=Select Menu
+system.menu.msg.mainMenuNotSelect=Main menu cannot be selected
+
+# Menu Management - Tree Select Page
+system.menu.tree.title=Menu Tree Select
+system.menu.tree.keyword=Keyword:
+system.menu.tree.btn.search= Search 
+system.menu.tree.btn.showSearch=Show Search
+system.menu.tree.btn.hideSearch=Hide Search
+system.menu.tree.expand=Expand
+system.menu.tree.collapse=Collapse
+
+# Menu Management - Icon Page
+system.menu.icon.title=Font Awesome Icon List
+
+# Common Options
+options.yes=Yes
+options.no=No
+
+# User Profile Page
+system.profile.title=User Profile
+
+# Logo Section
+system.profile.logo.title=System Logo
+system.profile.logo.edit=Edit Logo
+system.profile.logo.modal.title=Edit Custom Logo
+
+# Personal Info Section
+system.profile.info.title=Personal Info
+system.profile.info.loginName=Login Name:
+system.profile.info.phone=Phone:
+system.profile.info.dept=Department:
+system.profile.info.email=Email:
+system.profile.info.createTime=Create Time:
+
+# Basic Info Section
+system.profile.basic.title=Basic Info
+system.profile.tab.basic=Basic Info
+system.profile.tab.password=Change Password
+
+# Form Fields
+system.profile.form.userName=User Name:
+system.profile.form.userName.placeholder=Enter user name
+system.profile.form.phone=Phone:
+system.profile.form.phone.placeholder=Enter phone number
+system.profile.form.email=Email:
+system.profile.form.email.placeholder=Enter email
+system.profile.form.sex=Gender:
+system.profile.sex.male=Male
+system.profile.sex.female=Female
+
+# Avatar
+system.profile.avatar.edit=Edit Avatar
+system.profile.avatar.modal.title=Edit User Avatar
+
+# Change Password
+system.profile.password.old=Old Password:
+system.profile.password.old.placeholder=Enter old password
+system.profile.password.new=New Password:
+system.profile.password.new.placeholder=Enter new password
+system.profile.password.confirm=Confirm Password:
+system.profile.password.confirm.placeholder=Confirm new password
+
+# Password Rule Tips
+system.profile.password.rule.number=Password can only be 0-9 digits
+system.profile.password.rule.letter=Password can only be a-z and A-Z letters
+system.profile.password.rule.mix=Password must contain (letters, numbers)
+system.profile.password.rule.special=Password must contain (letters, numbers, special characters !@#$%^&*()-=_+)
+
+# Validation Messages
+system.profile.validate.userName.required=Please enter user name
+system.profile.validate.email.required=Please enter email
+system.profile.validate.email.exists=Email already exists
+system.profile.validate.phone.required=Please enter phone number
+system.profile.validate.phone.exists=Phone number already exists
+system.profile.password.validate.old.required=Please enter old password
+system.profile.password.validate.old.error=Old password is incorrect
+system.profile.password.validate.new.required=Please enter new password
+system.profile.password.validate.new.minlength=Password cannot be less than 6 characters
+system.profile.password.validate.new.maxlength=Password cannot be more than 20 characters
+system.profile.password.validate.confirm.required=Please enter new password again
+system.profile.password.validate.confirm.equalTo=Two password entries are inconsistent
+
+# Upload and Crop
+system.profile.upload.image=Upload Image
+system.profile.upload.image.type.error=Please select an image file.
+system.profile.cropper.loading=Cropper loading, please wait...
+
+# Common Buttons
+btn.confirm=Confirm
+
+# Post Management - List Page
+system.post.list.title=Post List
+system.post.list.query.postCode=Post Code:
+system.post.list.query.postName=Post Name:
+system.post.list.query.status=Post Status:
+system.post.modalName=Post
+system.post.table.postId=Post ID
+system.post.table.postCode=Post Code
+system.post.table.postName=Post Name
+system.post.table.postSort=Display Order
+system.post.table.status=Status
+system.post.table.createTime=Create Time
+system.post.table.opra=Operation
+
+# Post Management - Form (shared by add and edit)
+system.post.add.title=Add Post
+system.post.edit.title=Edit Post
+system.post.form.postName=Post Name:
+system.post.form.postCode=Post Code:
+system.post.form.postSort=Display Order:
+system.post.form.status=Post Status:
+system.post.form.remark=Remark:
+
+# Post Management - Validation
+system.post.validate.postCodeExists=Post code already exists
+system.post.validate.postNameExists=Post name already exists
+
+# Dict Type Management - List Page
+system.dict.type.list.title=Dict Type List
+system.dict.type.list.query.dictName=Dict Name:
+system.dict.type.list.query.dictType=Dict Type:
+system.dict.type.list.query.status=Dict Status:
+system.dict.type.list.query.createTime=Create Time:
+system.dict.type.list.btn.refreshCache=Refresh Cache
+system.dict.type.modalName=Type
+system.dict.type.table.dictId=Dict ID
+system.dict.type.table.dictName=Dict Name
+system.dict.type.table.dictType=Dict Type
+system.dict.type.table.status=Status
+system.dict.type.table.remark=Remark
+system.dict.type.table.createTime=Create Time
+system.dict.type.table.opra=Operation
+system.dict.type.btn.list=List
+system.dict.type.modal.dictData=Dict Data
+
+# Dict Type Management - Form (shared by add and edit)
+system.dict.type.add.title=Add Dict Type
+system.dict.type.edit.title=Edit Dict Type
+system.dict.type.form.dictName=Dict Name:
+system.dict.type.form.dictType=Dict Type:
+system.dict.type.form.dictType.help=Key value in data storage, e.g., sys_user_sex
+system.dict.type.form.status=Status:
+system.dict.type.form.remark=Remark:
+
+# Dict Type Management - Validation
+system.dict.type.validate.dictTypeExists=This dict type already exists
+
+# Dict Tree Select Page
+system.dict.tree.title=Dict Tree Select
+system.dict.tree.keyword=Keyword:
+system.dict.tree.btn.search= Search 
+system.dict.tree.btn.showSearch=Show Search
+system.dict.tree.btn.hideSearch=Hide Search
+
+# Dict Data Management - List Page
+system.dict.data.list.title=Dict Data List
+system.dict.data.list.query.dictType=Dict Name:
+system.dict.data.list.query.dictLabel=Dict Label:
+system.dict.data.list.query.status=Data Status:
+system.dict.data.modalName=Data
+system.dict.data.table.dictCode=Dict Code
+system.dict.data.table.dictLabel=Dict Label
+system.dict.data.table.dictValue=Dict Value
+system.dict.data.table.dictSort=Dict Sort
+system.dict.data.table.status=Status
+system.dict.data.table.remark=Remark
+system.dict.data.table.createTime=Create Time
+system.dict.data.table.opra=Operation
+
+# Dict Data Management - Form (shared by add and edit)
+system.dict.data.add.title=Add Dict Data
+system.dict.data.edit.title=Edit Dict Data
+system.dict.data.form.dictLabel=Dict Label:
+system.dict.data.form.dictValue=Dict Value:
+system.dict.data.form.dictType=Dict Type:
+system.dict.data.form.cssClass=CSS Class:
+system.dict.data.form.dictSort=Dict Sort:
+system.dict.data.form.listClass=List Class:
+system.dict.data.form.listClass.help=Table column display style attribute for dictionary
+system.dict.data.form.isDefault=Is Default:
+system.dict.data.form.status=Status:
+system.dict.data.form.remark=Remark:
+
+# List Class Options
+system.dict.data.listClass.default=Default
+system.dict.data.listClass.primary=Primary
+system.dict.data.listClass.success=Success
+system.dict.data.listClass.info=Info
+system.dict.data.listClass.warning=Warning
+system.dict.data.listClass.danger=Danger
+
+# Common Options
+options.pleaseSelect=---Please Select---
+
+# Cache Monitor Page
+system.cache.title=Cache Monitor
+system.cache.list.title=Cache List
+system.cache.keys.title=Key List
+system.cache.value.title=Cache Content
+system.cache.table.cacheName=Cache Name
+system.cache.table.cacheKey=Cache Key
+system.cache.table.operation=Operation
+system.cache.btn.clear=Clear
+system.cache.btn.clearAll=Clear All
+system.cache.label.cacheName=Cache Name:
+system.cache.label.cacheKey=Cache Key:
+system.cache.label.cacheValue=Cache Content:
+system.cache.msg.refreshListSuccess=Cache list refreshed successfully
+system.cache.msg.refreshKeysSuccess=Key list refreshed successfully
+system.cache.msg.clearCacheNameSuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearCacheKeySuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearAllSuccess=All caches cleared successfully
+
+# Login Log Page
+system.logininfor.list.title=Login Log List
+system.logininfor.list.query.ipaddr=Login Address:
+system.logininfor.list.query.loginName=Login Name:
+system.logininfor.list.query.status=Login Status:
+system.logininfor.list.query.loginTime=Login Time:
+system.logininfor.modalName=Login Log
+system.logininfor.table.infoId=Access ID
+system.logininfor.table.loginName=Login Name
+system.logininfor.table.ipaddr=Login Address
+system.logininfor.table.loginLocation=Login Location
+system.logininfor.table.browser=Browser
+system.logininfor.table.os=Operating System
+system.logininfor.table.status=Login Status
+system.logininfor.table.msg=Operation Info
+system.logininfor.table.loginTime=Login Time
+
+# Online User Page
+system.online.list.title=Online User List
+system.online.list.query.ipaddr=Login Address:
+system.online.list.query.loginName=Login Name:
+system.online.modalName=Online User
+system.online.table.serialNumber=No.
+system.online.table.sessionId=Session ID
+system.online.table.loginName=Login Name
+system.online.table.deptName=Department
+system.online.table.ipaddr=Host
+system.online.table.loginLocation=Login Location
+system.online.table.browser=Browser
+system.online.table.os=Operating System
+system.online.table.status=Session Status
+system.online.table.startTimestamp=Login Time
+system.online.table.lastAccessTime=Last Access Time
+system.online.table.operation=Operation
+system.online.status.online=Online
+system.online.status.offline=Offline
+system.online.msg.confirmForceLogout=Are you sure you want to force the selected user to log out?
+system.online.msg.selectUser=Please select users to force logout
+system.online.msg.confirmBatchForceLogout=Are you sure you want to force logout {0} selected users?
+
+# Common Buttons (additional)
+btn.clean=Clean
+btn.unlock=Unlock
+btn.forceLogout=Force Logout
+
+# Operation Log Detail Page
+system.operlog.detail.title=Operation Log Detail
+system.operlog.detail.module=Operation Module:
+system.operlog.detail.loginInfo=Login Info:
+system.operlog.detail.requestUrl=Request URL:
+system.operlog.detail.costTime=Cost Time
+system.operlog.detail.millisecond=ms
+system.operlog.detail.method=Operation Method:
+system.operlog.detail.operParam=Request Parameters:
+system.operlog.detail.jsonResult=Response Parameters:
+system.operlog.detail.status=Status:
+system.operlog.detail.errorMsg=Error Message:
+system.operlog.status.normal=Normal
+system.operlog.status.abnormal=Abnormal
+
+# Operation Log List Page
+system.operlog.list.title=Operation Log List
+system.operlog.list.query.operIp=Operation Address:
+system.operlog.list.query.title=System Module:
+system.operlog.list.query.operName=Operator:
+system.operlog.list.query.businessType=Operation Type:
+system.operlog.list.query.status=Operation Status:
+system.operlog.list.query.operTime=Operation Time:
+system.operlog.modalName=Operation Log
+system.operlog.table.operId=Log ID
+system.operlog.table.title=System Module
+system.operlog.table.businessType=Operation Type
+system.operlog.table.operName=Operator
+system.operlog.table.deptName=Department
+system.operlog.table.operIp=Operation Address
+system.operlog.table.operLocation=Operation Location
+system.operlog.table.status=Operation Status
+system.operlog.table.operTime=Operation Time
+system.operlog.table.costTime=Cost Time
+system.operlog.table.operation=Operation
+system.operlog.status.success=Success
+system.operlog.status.fail=Fail
+system.operlog.format.costTime=%sms
+
+# Service Monitor Page (netdata/system_io)
+system.netdata.title=Service Monitor
+system.netdata.chart.system.cpu=System CPU
+system.netdata.chart.system.ram=System RAM
+system.netdata.chart.system.io=System Disk I/O
+system.netdata.chart.net.enp4s0=Network Traffic
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 Memory
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui Memory
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH Memory
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL Memory
+system.netdata.loading=Loading...
+system.netdata.status.offline=Offline
+system.netdata.status.online=Online
+system.netdata.status.error=Connection Failed
+system.netdata.time.5min=5 Minutes
+system.netdata.time.15min=15 Minutes
+system.netdata.time.1hour=1 Hour
+system.netdata.time.24hour=24 Hours
+system.netdata.time.3day=3 Days
+system.netdata.btn.refresh=Refresh
+system.netdata.stat.current=Current
+system.netdata.stat.avg=Average
+system.netdata.stat.max=Max
+system.netdata.stat.min=Min
+system.netdata.yaxis.cpu=CPU Usage (%)
+system.netdata.yaxis.mem=Memory Usage (MiB)
+system.netdata.yaxis.io=Disk I/O (KiB/s)
+system.netdata.yaxis.net=Network Traffic (kilobits/s)
+system.netdata.error.noData=No data returned
+system.netdata.error.prefix=Error:
+
+# Server Monitor Page
+system.server.title=Server Monitor
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=Cores
+system.server.cpu.used=User Usage
+system.server.cpu.sys=System Usage
+system.server.cpu.free=Current Free
+system.server.mem.title=Memory
+system.server.mem.memory=Memory
+system.server.mem.jvm=JVM
+system.server.mem.total=Total
+system.server.mem.used=Used
+system.server.mem.free=Free
+system.server.mem.usage=Usage
+system.server.sys.title=Server Info
+system.server.sys.computerName=Server Name
+system.server.sys.osName=Operating System
+system.server.sys.computerIp=Server IP
+system.server.sys.osArch=System Architecture
+system.server.sys.userDir=Project Directory
+system.server.jvm.title=JVM Info
+system.server.jvm.name=Java Name
+system.server.jvm.version=Java Version
+system.server.jvm.startTime=Start Time
+system.server.jvm.runTime=Run Time
+system.server.jvm.home=Installation Path
+system.server.jvm.inputArgs=Runtime Arguments
+system.server.disk.title=Disk Status
+system.server.disk.dirName=Drive Path
+system.server.disk.sysTypeName=File System
+system.server.disk.typeName=Drive Type
+system.server.disk.total=Total Size
+system.server.disk.free=Free Size
+system.server.disk.used=Used Size
+system.server.disk.usage=Usage Percentage
+system.server.table.property=Property
+system.server.table.value=Value
+system.server.unit.count=cnt
+
+# Notice Add Page
+system.notice.add.title=Add Notice
+system.notice.placeholder.noticeContent=Please enter notice content
+system.notice.msg.uploadFail=Image upload failed.
+
+# Notice Edit Page
+system.notice.edit.title=Edit Notice
+
+# Notice List Page
+system.notice.list.title=Notice List
+system.notice.list.query.noticeTitle=Notice Title:
+system.notice.list.query.createBy=Creator:
+system.notice.list.query.noticeType=Notice Type:
+system.notice.modalName=Notice
+system.notice.table.noticeId=No.
+system.notice.table.noticeTitle=Notice Title
+system.notice.table.noticeType=Notice Type
+system.notice.table.status=Status
+system.notice.table.createBy=Creator
+system.notice.table.createTime=Create Time
+system.notice.table.operation=Operation
+
+# Notice Form Labels
+system.notice.label.noticeTitle=Notice Title:
+system.notice.label.noticeType=Notice Type:
+system.notice.label.noticeContent=Notice Content:
+system.notice.label.status=Notice Status:
+
+# Notice View Page
+system.notice.view.title=Notice Detail
+system.notice.view.sendTime=Send Time
+system.notice.view.sender=Sender
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=Language For ASR
+callTask.form.asrLanguageCode.empty=Please Select Language For ASR
+callTask.form.ttsLanguageCode=Language For TTS
+callTask.form.ttsLanguageCode.empty=Please Select Language For TTS
+inboundllm.form.asrLanguageCode=Language For ASR
+inboundllm.form.asrLanguageCode.empty=Please Select Language For ASR
+inboundllm.form.ttsLanguageCode=Language For TTS
+inboundllm.form.ttsLanguageCode.empty=Please Select Language For TTS
+ivr.form.asrLanguageCode=Language For ASR
+ivr.form.asrLanguageCode.empty=Please Select Language For ASR
+ivr.form.ttsLanguageCode=Language For TTS
+ivr.form.ttsLanguageCode.empty=Please Select Language For TTS
+
+# sys_config data
+_sys.config.sys.index.skinName=Default Skin Style for Main Framework Page
+_sys.config.sys.user.initPassword=User Management - Initial Account Password
+_sys.config.sys.index.sideTheme=Sidebar Theme for Main Framework Page
+_sys.config.sys.account.registerUser=Account Self-Service - Enable User Registration
+_sys.config.sys.account.chrtype=User Management - Password Character Range
+_sys.config.sys.account.initPasswordModify=User Management - Initial Password Change Policy
+_sys.config.sys.account.passwordValidateDays=User Management - Account Password Update Cycle
+_sys.config.sys.index.menuStyle=Main Framework Page - Menu Navigation Display Style
+_sys.config.sys.index.footer=Main Framework Page - Enable Footer
+_sys.config.sys.index.tagsView=Main Framework Page - Enable Tabs
+_sys.config.sys.login.blackIPList=User Login - Blacklist
+_sys.config.sys.version=System Version
+_sys.config.sys.logo=System Default Logo
+_sys.config.config_asr_provider_aliyun=Alibaba Cloud
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=China Telecom
+_sys.config.config_asr_provider_aws=Amazon
+_sys.config.config_tts_provider_aliyun=Alibaba Cloud
+_sys.config.config_tts_provider_doubao=Doubao
+_sys.config.config_tts_provider_chinatelecom=China Telecom
+_sys.config.config_tts_provider_aws=Amazon
+_sys.config.config_tts_language_aliyun_tts=Alibaba Cloud TTS Language Settings
+_sys.config.config_tts_language_doubao_vcl_tts=Doubao TTS Language Settings
+_sys.config.config_tts_language_aws_tts=Amazon TTS Language Settings
+_sys.config.config_asr_language_aliyun=Alibaba Cloud ASR Language Settings
+_sys.config.config_asr_language_aws=Amazon ASR Language Settings
+
+# Dictionary Types
+_sys.dict.type.sys_user_sex=User Gender
+_sys.dict.type.sys_show_hide=Menu Status
+_sys.dict.type.sys_normal_disable=System Switch
+_sys.dict.type.sys_job_status=Job Status
+_sys.dict.type.sys_job_group=Job Group
+_sys.dict.type.sys_yes_no=System Yes/No
+_sys.dict.type.sys_notice_type=Notice Type
+_sys.dict.type.sys_notice_status=Notice Status
+_sys.dict.type.sys_oper_type=Operation Type
+_sys.dict.type.sys_common_status=System Status
+
+# Dictionary Values
+_sys.dict.data.label.sys_user_sex.0=Male
+_sys.dict.data.label.sys_user_sex.1=Female
+_sys.dict.data.label.sys_user_sex.2=Unknown
+_sys.dict.data.label.sys_show_hide.0=Show
+_sys.dict.data.label.sys_show_hide.1=Hide
+_sys.dict.data.label.sys_normal_disable.0=Normal
+_sys.dict.data.label.sys_normal_disable.1=Disabled
+_sys.dict.data.label.sys_job_status.0=Normal
+_sys.dict.data.label.sys_job_status.1=Paused
+_sys.dict.data.label.sys_job_group.DEFAULT=Default
+_sys.dict.data.label.sys_job_group.SYSTEM=System
+_sys.dict.data.label.sys_yes_no.Y=Yes
+_sys.dict.data.label.sys_yes_no.N=No
+_sys.dict.data.label.sys_notice_type.1=Notification
+_sys.dict.data.label.sys_notice_type.2=Announcement
+_sys.dict.data.label.sys_notice_status.0=Normal
+_sys.dict.data.label.sys_notice_status.1=Closed
+_sys.dict.data.label.sys_oper_type.0=Other
+_sys.dict.data.label.sys_oper_type.1=Add
+_sys.dict.data.label.sys_oper_type.2=Edit
+_sys.dict.data.label.sys_oper_type.3=Delete
+_sys.dict.data.label.sys_oper_type.4=Authorize
+_sys.dict.data.label.sys_oper_type.5=Export
+_sys.dict.data.label.sys_oper_type.6=Import
+_sys.dict.data.label.sys_oper_type.7=Force Quit
+_sys.dict.data.label.sys_oper_type.8=Generate Code
+_sys.dict.data.label.sys_oper_type.9=Clear Data
+_sys.dict.data.label.sys_common_status.0=Success
+_sys.dict.data.label.sys_common_status.1=Failed
+
+# System Parameters
+_cc.params.phone_encrypted_key=Phone Toolbar - Encryption Key for Outbound Calls
+_cc.params.recording_path=Recording Save Path
+_cc.params.enable_cc_record_stereo=Enable Stereo Recording
+_cc.params.post_cdr_url=CDR Push Interface URL
+_cc.params.recordings_extension=Recording File Format
+_cc.params.conference_video_templates=Available Conference Layouts
+_cc.params.conference_gateway_addr=Video Conference Gateway Address
+_cc.params.conference_recording_path=Video Conference Recording Save Path
+_cc.params.conference_gateway_caller=Video Conference Caller Number
+_cc.params.video_level_id_list=Video Quality Level List
+_cc.params.conference_outboud_profile=Video Conference Outbound Profile
+_cc.params.conference_video_layouts=Video Conference Layout List
+_cc.params.wait-for-second-vad-during-interrupt=Enable Secondary Interruption Setting (Wait for Second Speech)
+_cc.params.outbound-call-extra-params-for-profile-internal2=Outbound Call Extra Parameters (Call Behavior Control)
+_cc.params.call_monitor_enabled=Enable Call Monitoring
+_cc.params.fs_log_file_path=FreeSWITCH Log File Path
+_cc.params.cc_log_file_path=Call Center Log File Path
+_cc.params.fs_call_asr_enabled=Enable Bidirectional ASR Recognition and Result Push
+_cc.params.fs_call_asr_engine=Bidirectional ASR Engine (chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=Enable Inbound Call Queue Monitoring
+_cc.params.fs_docker_container_name=FreeSWITCH Docker Container Name
+_cc.params.fs_conf_directory=FreeSWITCH Configuration Directory
+_cc.params.robot-asr-type=ASR Recognition Type (mrcp/websocket)
+_cc.params.fs-asr-mrcp-param=MRCP ASR Request Parameters
+_cc.params.robot-max-no-speak-time=Max Customer Silence Duration in Robot Call (Auto Hangup Timeout)
+_cc.params.max-call-concurrency=Max Inbound Call Concurrency Limit
+_cc.params.asr-pause-enabled=ASR On-Demand Recognition (Enable After Robot Speech)
+_cc.params.max-wait-time-after-vad-start=Max Wait Time After Speech Detection Start (seconds)
+_cc.params.inbound-transfer-agent-timeout=Inbound Call Transfer to Agent Timeout (seconds)
+_cc.params.hide-inbound-number=Hide Inbound Number When Transferring to Agent
+_cc.params.inbound-play-opnum=Play Agent Number When Transferring Inbound Call
+_cc.params.event-socket-ip=Event Socket IP Address
+_cc.params.event-socket-port=Event Socket Port
+_cc.params.event-socket-pass=Event Socket Password
+_cc.params.event-socket-conn-pool-size=Event Socket Connection Pool Size
+_cc.params.max-agent-number=Max Agent Number
+_cc.params.ws-server-auth-token-secret=Token Secret for Decryption and Verification
+_cc.params.ws-server-port=WebSocket Server Phone Toolbar Port
+_cc.params.vad-intelligent-wait=Enable VAD Intelligent Wait
+_cc.params.vad-intelligent-wait-ms=VAD Intelligent Wait Duration (milliseconds)
+_cc.params.call-center-server-port=Call Center WebAPI Server Port (Read-Only)
+_cc.params.fs-deploy-type=FreeSWITCH Deployment Type (docker/native)
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH Native Deployment Startup Script Path
+_cc.params.fs-root-directory=FreeSWITCH Program Root Directory
+_cc.params.call-center-server-ip-addr=Server External IP Address
+_cc.params.call-center-websocket-port=Call Center WebSocket Port
+_cc.params.tts_content_variables=TTS Content Variables in Scripts
+_cc.params.call-center-api-token=Voice Notification Access Token
+_cc.params.outbound-max-line-number=Global Parameter - Max Outbound Concurrency
+_cc.params.outbound-enable-prediction-algorithm=Enable Predictive Dialing Algorithm for Batch Calls
+_cc.params.aliyun-tts-account-json=Aliyun TTS Account Parameters JSON
+_cc.params.empty-number-detection-enabled=Enable Empty Number Detection
+_cc.params.empty-number-detection-config=Empty Number Detection Configuration
+_cc.params.default_interrupt_ignore_keywords=Default Interruption Ignore Keywords List
+_cc.params.llm-max-try=Max Retry Times for LLM Connection
+_cc.params.llm-conn-timeout=LLM Connection Timeout (milliseconds)
+_cc.params.llm-max-try-fail-tips=LLM Connection Failure Prompt Message
+_cc.params.api-client-white-ips=API Client IP Whitelist
+_cc.params.price_aliyun_asr=Aliyun ASR Price (per hour)
+_cc.params.price_aliyun_tts=Aliyun TTS Price (per 1k requests)
+_cc.params.price_aliyun_tts_flow=Aliyun Voice Cloning Price (per 10k characters)
+_cc.params.price_llm_token=LLM Price (per 1k tokens)
+_cc.params.doubao-tts-account-json=Doubao TTS Account Parameters JSON
+_cc.params.system_license_info=System License Information
+_cc.params.system_hw_fingerprint_cmd=System Hardware Fingerprint Command
+_cc.params.fs-inbound-acl-enabled=Enable FreeSWITCH Inbound ACL Protection
+_cc.params.freeswitch_root=FreeSWITCH Root Directory
+_cc.params.callTask_allowDel_days=Days Interval Allowed to Delete Call Tasks and Records
+_cc.params.firewalld-config-path=Firewalld Configuration File Path
+_cc.params.firewalld-restart-cmd=Firewalld Restart Command
+_cc.params.firewalld-enabled=Enable Firewalld Firewall
+_cc.params.fs-inbound-allow-ip-list=Inbound IP Whitelist (for External Trunk Inbound)
+_cc.params.fs-register-allow-ip-list=Extension Registration IP Whitelist
+_cc.params.fs-register-acl-enabled=Enable FreeSWITCH Extension Registration ACL Protection
+_cc.params.max-wait-time-customer-speaking=Max Time For Wait Customer Speaking
+
+# 新增功能
+_menu.firewalld=Firewalld Configuration
+_menu.awsAsrConf=AWS ASR Configuration
+_menu.awsTtsConf=AWS TTS Configuration
+_menu.deepgramAsrConf=Deepgram ASR Configuration
+_menu.deepgramTtsConf=Deepgram TTS Configuration
+
+# Firewalld Configuration
+firewallRule.add.title=Add Firewall Rule Configuration
+firewallRule.edit.title=Edit Firewall Rule Configuration
+firewallRule.notice.title=Notice
+firewallRule.notice.freeswitch=No need to add Freeswitch related ports
+firewallRule.notice.ssh=No need to add SSH related ports
+firewallRule.form.protocol=Protocol
+firewallRule.form.portStart=Start Port
+firewallRule.form.portEnd=End Port
+firewallRule.form.enableFromSource=Enable Source Restriction
+firewallRule.form.fromSource=Source Address
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=Yes
+firewallRule.boolean.no=No
+firewallRule.placeholder.portStart=Enter start port (1-65535)
+firewallRule.placeholder.portEnd=Enter end port (1-65535)
+firewallRule.placeholder.fromSource=Enter source IP address or subnet
+firewallRule.validation.integer=Please enter an integer
+firewallRule.validation.portRange=Start port must be less than or equal to end port
+firewallRule.validation.portStart.required=Please enter start port
+firewallRule.validation.portEnd.required=Please enter end port
+firewallRule.validation.port.range=Port range must be between 1-65535
 
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=Locked
+_phoneBar.agentStatus.text_6=Conference
+_phoneBar.agentStatus.text_5=FillForm
+_phoneBar.agentStatus.text_4=InCall
+_phoneBar.agentStatus.text_33=Training
+_phoneBar.agentStatus.text_32=Meeting
+_phoneBar.agentStatus.text_31=Rest
+_phoneBar.agentStatus.text_3=Busy
+_phoneBar.agentStatus.text_2=Free
+_phoneBar.agentStatus.text_1=JustLogin
+phonebar.msg.error_login_token=Phone Bar: Unable to get loginToken!
+phonebar.msg.error_wss_domain=ERROR! When WSS is enabled, domain name must be used to access websocketServer! 
+phonebar.msg.websocket_not_supported=Your browser does not support WebSocket, you cannot use the features of this page!
+phonebar.msg.hold_call_hangup=The held call has been hung up.
+phonebar.msg.call_waiting=Customer call is waiting.
+phonebar.msg.call_wait_retrieved=The waiting call has been retrieved.
+phonebar.msg.consultation_started=Consultation has started.
+phonebar.msg.consultation_ended=Consultation has ended.
+phonebar.msg.waiting_call_hangup=The waiting customer has hung up.
+phonebar.msg.ipcc_disconnected=ipccserver connection disconnected.
+phonebar.msg.select_group=Please select a business group!
+phonebar.msg.select_agent=Please select an agent to consult!
+phonebar.msg.select_free_agent=Please select an available agent!
+phonebar.msg.cannot_consult_self=Cannot consult yourself, please select another agent!
+phonebar.msg.select_transfer_group=Please select the transfer business group!
+phonebar.msg.select_transfer_agent=Please select the transfer agent!
+phonebar.msg.cannot_transfer_self=Cannot transfer to yourself, please select another agent!
+phonebar.msg.please_login=Please login first.
+phonebar.msg.no_call=No active call currently.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=Cannot send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=Please enter the phone number!
+phonebar.msg.invalid_phone=Please enter a valid phone number format!
+phonebar.msg.agent_locked=Agent is locked
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false, default UI toolbar buttons disabled.
+phonebar.msg.logout_not_allowed=Logout is not allowed currently!
+phonebar.msg.confirm_close=Closing the page will prevent you from answering calls, are you sure you want to close?
+phonebar.msg.esc_hangup=ESC key pressed, sending hangup command.
+phonebar.msg.confirm_transfer_conference=Do you want to transfer the current call to conference?
+phonebar.msg.enter_member_name=Please enter the participant name!
+phonebar.msg.enter_member_phone=Please enter the participant phone number!
+phonebar.msg.member_exists=Conference member already exists, please do not add again!
+phonebar.msg.start_conference=Starting multi-party call
+phonebar.msg.provide_call_id=Please provide the callId of the call to monitor!
+phonebar.status.free=Free
+phonebar.status.about_to_call=About to call
+phonebar.status.in_call=In call
+page.title=My Workbench
+phonebar.conference.label.type=Conference Type
+phonebar.conference.option.video=Video
+phonebarbar.conference.option.audio=Audio
+phonebar.conference.btn.start=Start Conference
+phonebar.conference.btn.end=End Conference
+phonebar.conference.btn.join=Join
+phonebar.conference.btn.remove=Remove
+phonebar.conference.btn.reinvite=Recall
+phonebar.conference.placeholder.name=Name
+phonebar.conference.placeholder.phone=Phone
+phonebar.conference.alt.mute=Mute this member
+phonebar.conference.alt.vmute=Turn off this member's video
+phonebar.conference.title.remove=Remove conference member
+phonebar.conference.title.reinvite=Recall member
+phonebar.transfer.label.group=Business Group
+phonebar.transfer.label.member=Agent Members
+phonebar.transfer.option.select=Please Select
+phonebar.transfer.placeholder.phone=Phone Number
+phonebar.transfer.title.externalPhone=Transfer current call to external number. Leave empty to ignore.
+phonebar.transfer.btn.transfer=Transfer Call
+phonebar.transfer.title.transfer=Transfer current call to selected agent.
+phonebar.transfer.btn.retrieve=Retrieve Customer
+phonebar.transfer.title.retrieve=Use this button to retrieve customer when consultation fails.
+phonebar.transfer.btn.transferCall=Transfer Customer
+phonebar.transfer.title.transferCall=Use this button to transfer call to expert agent when consultation succeeds.
+phonebar.transfer.btn.consult=Dial Consult
+phonebar.transfer.title.consult=Dial Consult
+phonebar.transfer.status.login=Just Logged In
+phonebar.transfer.status.free=Free
+phonebar.transfer.status.busy=Busy
+phonebar.transfer.status.calling=In Call
+phonebar.transfer.status.after=After Call Work
+phonebar.popup.inbound=Inbound Popup
+phonebar.popup.outbound=Outbound Popup
+phonebar.chat.dialog.end=Dialog ended.
+phonebar.chat.role.customer=Customer
+phonebar.chat.role.agent=Me
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS Models
+inboundllm.form.ttsModels.empty=Please Select TTS Models
+inboundllm.form.asrModels=ASR Models
+inboundllm.form.asrModels.empty=Please Select ASR Models
+callTask.form.ttsModels=TTS Models
+callTask.form.ttsModels.empty=Please Select TTS Models
+callTask.form.asrModels=ASR Models
+callTask.form.asrModels.empty=Please Select ASR Models
+ivr.form.ttsModels=TTS Models
+ivr.form.ttsModels.empty=Please Select TTS Models

+ 1033 - 2
ruoyi-admin/src/main/resources/static/i18n/messages_ja_JP.properties

@@ -4,6 +4,9 @@ sys.sysName=Call Management System
 ## 弹框提示
 tips.defaultPass=Your password is still the initial password. Please change it!
 tips.security.title=Security Tips
+tips.role.cust=Cust
+tips.role.agent=Agent
+tips.role.kb=Kb
 ## 按钮
 btn.ok=OK
 btn.cancel=Cancel
@@ -57,6 +60,38 @@ user.login.welcome=Welcome
 
 # common
 common.adminPage.upload.requiredTips = The uploaded file cannot be empty.
+common.tip.selected.empty=Please select at least one record
+common.tip.del.confirm=Are you sure you want to delete the selected data?
+common.tip.remove.confirm=Are you sure you want to delete this record?
+common.tip.clear.confirm=Are you sure you want to clear all data?
+common.tip.loading=Processing, please wait...
+common.tip.export.confirm="Are you sure you want to export all data?"
+common.tip.success=Operation successful
+common.tip.fail=Operation failed
+common.table.search=search
+common.table.col=col
+common.table.refresh=refresh
+common.tip.upload.success=File imported successfully!
+common.tip.upload.fail=File import failed. Please check if the file format is correct!
+common.msg.callRecords.del.confirm=Are you sure you want to delete this outbound task and its associated call records?
+common.msg.callRecords.del.warning=Deleted data cannot be recovered. Please proceed with caution!
+common.tip.del.success=Deleted successfully!
+common.tip.del.fail=Deletion failed. Please try again later!
+menuTab.close_current=Close Current
+menuTab.close_other=Close Others
+menuTab.close_left=Close Left
+menuTab.close_right=Close Right
+menuTab.close_all=Close All
+menuTab.full=Full Screen
+menuTab.refresh=Refresh
+menuTab.open=Open in New Window
+common.msg.copy.success=Copied to clipboard
+common.msg.copy.fail=Copy failed, please copy manually
+common.msg.unknown.media=Unsupported media format
+common.msg.file.notexists=File does not exist!
+common.time.hour=h
+common.time.minute=m
+common.time.second=s
 
 # 电话工具条
 phonebar.placeholder.phonenum=Please enter your phone number.
@@ -106,6 +141,12 @@ phonebar.msg.free=Free
 phonebar.msg.busy=Busy
 phonebar.msg.customer_channel_hold=Customer Channel Hold
 phonebar.msg.customer_channel_unhold=Customer Channel Hold Ended
+phonebar.msg.inner_consultation_start=Inner Consultation Start
+phonebar.msg.inner_consultation_stop=Inner Consultation Stop
+phonebar.msg.transfer_call_success=Transfer Call Success
+phonebar.msg.conferenceEnd=Conference End
+phonebar.msg.conferenceStart=Conference Start
+phonebar.msg.transferToConferenceSuccess=Transfer To Conference Success
 phonebar.label.loginTime=Extension Login Time:
 phonebar.label.agentStatus=Agent Status:
 phonebar.label.queueStat=Queue Agents:
@@ -149,6 +190,17 @@ params.manage.form.paramType=Parameter Type:
 params.manage.form.title.add=Add Callcenter Parameter Configuration
 params.manage.form.title.edit=Edit Callcenter Parameter Configuration
 
+# firewalld管理页面
+firewalld.manage.query.protocol=Protocol Type
+firewalld.manage.query.portStart=Start Port
+firewalld.manage.table.portEnd=End Port
+firewalld.manage.table.enableFromSource=Allow from Source IP Only
+firewalld.manage.table.fromSource=Source IP
+firewalld.manage.table.opra=Operation
+firewalld.manage.table.modalName=Firewall Rule Configuration
+firewalld.manage.form.title.add=Add Firewall Rule
+firewalld.manage.form.title.edit=Edit Firewall Rule
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=Basic Configurations
 switchconf.label.more=More
@@ -175,6 +227,10 @@ switchconf.cert.label=Certificate Content:
 switchconf.log.fs.label=Freeswitch Logs:
 switchconf.log.cc.label=Callcenter Logs:
 switchconf.log.error.label=Error Logs:
+switchconf.asr.aws.header=AWS ASR
+switchconf.tts.aws.header=AWS TTS
+switchconf.asr.deepgram.header=Deepgram ASR
+switchconf.tts.deepgram.header=Deepgram TTS
 
 # 业务组
 bizgroup.query.bizGroupName=Business Group Name:
@@ -561,8 +617,8 @@ llmAcount.form.name=Name:
 llmAcount.form.providerClassName=Provider Class Name:
 llmAcount.form.concurrentNum=Max Concurrency:
 llmAcount.form.interruptFlag=Interrupt Type:
-llmAcount.form.interruptFlag0=Never Interrupt
-llmAcount.form.interruptFlag1=KeyWords Interrupt
+llmAcount.form.interruptFlag0=Never Interrupt
+llmAcount.form.interruptFlag1=KeyWords Interrupt
 llmAcount.form.interruptFlag2=Always Interrupt
 llmAcount.form.transferManualDigit=Transfer Manual Digit:
 llmAcount.form.interruptKeywords=Interrupt Keywords:
@@ -918,6 +974,7 @@ ivr.table.ttsText=IVR Content
 ivr.table.action=IVR Action
 ivr.table.opra=Operation
 ivr.form.pressKeyInvalidTips.default=Invalid input, please try again. 
+ivr.apply.confirmMessage=Do not execute application settings during outbound calling operations or peak inbound call hours. This may cause call anomalies.
 
 callTask.form.taskType3=IVR
 callTask.form.ivrId=IVR
@@ -931,6 +988,40 @@ ivr.form.hangupTips=Hangup Tips:
 ivr.form.aiInboundId=AI Robot:
 ivr.form.aiInboundId.empty=Please Select an AI Robot
 ivr.form.action.ai=Transfer To AI Robot
+ivr.form.placeholder.ttsText=Enter TTS text or upload audio file
+ivr.form.btn.title.tts=TTS Synthesis
+ivr.form.btn.tts=Synthesize
+ivr.form.btn.title.upload=Upload WAV audio file
+ivr.form.btn.upload=Upload
+ivr.form.tips.helpblock=Supports direct text input or uploading WAV format audio files. Type // to bring up the variable list
+ivr.form.title.ttsAudioPreview=Audio Preview
+ivr.form.placeholder.hangupTips=Enter hangup prompt text or upload audio file
+ivr.form.placeholder.pressKeyInvalidTips=Enter invalid key prompt text or upload audio file
+ivr.form.tips.digitRange=Please enter a valid regular expression to validate user input format
+ivr.validator.msg.checkDigit=This key value already exists at the same level, please use a different key
+ivr.validator.msg.minLenCheck=Minimum length cannot be greater than maximum length
+ivr.validator.msg.maxLenCheck=Maximum length cannot be less than minimum length
+ivr.validator.msg.validRegex=Please enter a valid regular expression format
+ivr.edit.title=Edit IVR Configuration
+ivr.add.title=Add IVR Configuration
+ivr.variableSelector.title=Select Variable
+ivr.variableSelector.tip=Click to insert
+ivr.modal.upload.title=Upload Audio File
+ivr.modal.upload.label=Select WAV file:
+ivr.modal.upload.helpblock=WAV format only, max 50MB
+ivr.modal.upload.progress=Upload progress:
+ivr.modal.upload.confirm=Confirm Upload
+ivr.msg.selectFile=Please select a file first!
+ivr.msg.wavOnly=Please select a WAV format audio file!
+ivr.msg.fileSizeLimit=File size cannot exceed 50MB!
+ivr.msg.uploadSuccess=Upload successful!
+ivr.msg.uploadFail=Upload failed:
+ivr.msg.networkError=Network error:
+ivr.msg.ttsTextRequired=Please enter the text content to be synthesized!
+ivr.msg.ttsSynthesizing=Synthesizing audio...
+ivr.msg.ttsSuccess=TTS synthesis successful!
+ivr.msg.ttsDataError=TTS synthesis returned data format error!
+ivr.msg.ttsFail=TTS synthesis failed
 
 # 反向注册功能支持
 gateways.table.label.register2=Reverse Registration Mode
@@ -1061,5 +1152,945 @@ kbcontent.table.opra=Operation
 kbcontent.form.title=Title:
 kbcontent.form.content=Content:
 
+# 用户管理
+system.user.list.boxTitle=Dept
+system.user.list.btn.Dept=Manage Dept
+system.user.list.btn.Expand=Expand
+system.user.list.btn.Collapse=Collapse
+system.user.list.btn.Refresh=Refresh
+system.user.list.query.loginName=LoginName:
+system.user.list.query.phonenumber=Phonenumber:
+system.user.list.query.status=Status:
+system.user.list.query.createName=CreateName:
+system.user.modalName=User
+system.user.table.userId=User
+system.user.table.loginName=LoginName
+system.user.table.userName=UserName
+system.user.table.deptName=DeptName
+system.user.table.email=Email
+system.user.table.phonenumber=Phonenumber
+system.user.table.userstatus=Status
+system.user.table.createTime=CreateName
+system.user.table.opra=Operation
+system.user.table.btn.more=More
+system.user.table.btn.resetPass=ResetPass
+system.user.table.btn.authRole=AuthRole
+system.user.form.head.base=Base Info
+system.user.form.loginName=LoginName:
+system.user.form.placeholder.loginName=Please Input LoginName
+system.user.form.password=Password:
+system.user.form.placeholder.password=Please Input Password
+system.user.form.tips.password=Push Mouse Show Password
+system.user.form.userName=UserName:
+system.user.form.placeholder.userName=Please Input UserName
+system.user.form.deptName=DeptName:
+system.user.form.placeholder.deptName=Please Select DeptName
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=Please Input Phonenumber
+system.user.form.email=Email:
+system.user.form.placeholder.email=Please Input Email
+system.user.form.sex=Sex:
+system.user.form.status=Status:
+system.user.form.post=Post:
+system.user.form.role=Role:
+system.user.form.head.other=Other Info
+system.user.form.remark=Remark:
+system.user.form.head.authRole=AuthRole
+system.user.form.loginDate=Last Login Date
+system.user.form.loginIp=Last Login Ip:
+system.user.form.updateTime=UpdateTime:
+system.user.form.updateBy=UpdateBy:
+system.user.form.createTime=CreateTime:
+system.user.form.createBy=CreateBy:
+system.user.authrole.table.roleId=RoleId
+system.user.authrole.table.roleSort=RoleSort
+system.user.authrole.table.roleName=RoleName
+system.user.authrole.table.roleKey=RoleKey
+system.user.authrole.table.createTime=CreateTime
+system.user.resetpwd.password=Password:
+system.user.resetpwd.placeholder.password=Please Input Password
+system.user.resetpwd.tips.password=Push Mouse Show Password
+system.user.tips.disable=Are you sure you want to disable this user?
+system.user.tips.enable=Are you sure you want to enable this user?
+
+# Role Management Page
+system.role.list.query.roleName=Role Name:
+system.role.list.query.roleKey=Permission Key:
+system.role.list.query.status=Role Status:
+system.role.list.query.createTime=Create Time:
+system.role.modalName=Role
+system.role.table.roleId=Role ID
+system.role.table.roleName=Role Name
+system.role.table.roleKey=Permission Key
+system.role.table.dataScope=Data Scope
+system.role.table.roleSort=Display Order
+system.role.table.roleStatus=Role Status
+system.role.table.createTime=Create Time
+system.role.table.opra=Operation
+system.role.dataScope.all=All Data
+system.role.dataScope.custom=Custom Data
+system.role.dataScope.dept=Dept Data
+system.role.dataScope.deptAndChild=Dept and Child Data
+system.role.dataScope.self=Self Data Only
+system.role.table.btn.dataScope=Data Scope
+system.role.table.btn.authUser=Assign User
+system.role.table.btn.more=More
+system.role.modal.dataScope=Assign Data Scope
+system.role.modal.authUser=Assign User
+system.role.confirm.disable=Are you sure to disable this role?
+system.role.confirm.enable=Are you sure to enable this role?
+
+# Role Management - Add/Edit Page
+system.role.add.title=Add Role
+system.role.edit.title=Edit Role
+system.role.add.roleName=Role Name:
+system.role.add.roleKey=Permission Key:
+system.role.add.roleKey.help=Permission key defined in controller, e.g., @RequiresRoles("")
+system.role.add.roleSort=Display Order:
+system.role.add.status=Status:
+system.role.add.remark=Remark:
+system.role.add.menuPermission=Menu Permission:
+system.role.add.expandCollapse=Expand/Collapse
+system.role.add.selectAll=Select All/None
+system.role.add.parentChildLink=Parent-Child Link
+system.role.add.validate.roleNameExists=Role name already exists
+system.role.add.validate.roleKeyExists=Role permission already exists
+
+# Role Management - Auth User Page
+system.role.authUser.title=Assign Users to Role
+system.role.authUser.query.loginName=Login Name:
+system.role.authUser.query.phonenumber=Phone Number:
+system.role.authUser.btn.addUser=Add User
+system.role.authUser.btn.cancelAuthBatch=Batch Cancel Authorization
+system.role.authUser.btn.cancelAuth=Cancel Authorization
+system.role.authUser.table.userId=User ID
+system.role.authUser.table.loginName=Login Name
+system.role.authUser.table.userName=User Name
+system.role.authUser.table.email=Email
+system.role.authUser.table.phonenumber=Phone
+system.role.authUser.table.status=User Status
+system.role.authUser.table.createTime=Create Time
+system.role.authUser.table.opra=Operation
+system.role.authUser.modal.selectUser=Select User
+system.role.authUser.msg.selectOne=Please select at least one record
+system.role.authUser.confirm.cancelBatch=Are you sure to cancel authorization for {0} selected records?
+system.role.authUser.confirm.cancelAuth=Are you sure to cancel this user's role authorization?
+
+# Role Management - Data Scope Page
+system.role.dataScope.title=Role Data Scope
+system.role.dataScope.dataScope=Data Scope:
+system.role.dataScope.help=In special cases, set to "Custom Data Scope"
+system.role.dataScope.dataPermission=Data Permission:
+
+# Role Management - Select User Page
+system.role.selectUser.title=Select User for Role Assignment
+
+# Dept Management - List Page
+system.dept.list.title=Dept List
+system.dept.list.query.deptName=Dept Name:
+system.dept.list.query.status=Dept Status:
+system.dept.list.btn.expandCollapse=Expand/Collapse
+system.dept.modalName=Dept
+system.dept.table.deptName=Dept Name
+system.dept.table.orderNum=Order
+system.dept.table.status=Status
+system.dept.table.createTime=Create Time
+system.dept.table.opra=Operation
+
+# Dept Management - Form (shared by add and edit)
+system.dept.add.title=Add Dept
+system.dept.edit.title=Edit Dept
+system.dept.form.parentDept=Parent Dept:
+system.dept.form.deptName=Dept Name:
+system.dept.form.orderNum=Display Order:
+system.dept.form.leader=Leader:
+system.dept.form.phone=Phone:
+system.dept.form.email=Email:
+system.dept.form.status=Dept Status:
+
+# Dept Management - Validation and Messages
+system.dept.validate.deptNameExists=Dept already exists
+system.dept.msg.selectParentFirst=Please add the parent dept first!
+system.dept.msg.parentNotAllow=Parent dept not allowed
+system.dept.modal.selectDept=Select Dept
+
+# Dept Management - Tree Select Page
+system.dept.tree.title=Dept Tree Select
+system.dept.tree.keyword=Keyword:
+system.dept.tree.btn.search= Search 
+system.dept.tree.btn.showSearch=Show Search
+system.dept.tree.btn.hideSearch=Hide Search
+system.dept.tree.expand=Expand
+system.dept.tree.collapse=Collapse
+
+# Config Management - List Page
+system.config.list.title=Config List
+system.config.list.query.configName=Config Name:
+system.config.list.query.configKey=Config Key:
+system.config.list.query.configType=Built-in:
+system.config.list.query.createTime=Create Time:
+system.config.list.btn.refreshCache=Refresh Cache
+system.config.modalName=Config
+system.config.table.configId=Config ID
+system.config.table.configName=Config Name
+system.config.table.configKey=Config Key
+system.config.table.configValue=Config Value
+system.config.table.configType=Built-in
+system.config.table.remark=Remark
+system.config.table.createTime=Create Time
+system.config.table.opra=Operation
+
+# Config Management - Form (shared by add and edit)
+system.config.add.title=Add Config
+system.config.edit.title=Edit Config
+system.config.form.configName=Config Name:
+system.config.form.configKey=Config Key:
+system.config.form.configValue=Config Value:
+system.config.form.configType=Built-in:
+system.config.form.remark=Remark:
+
+# Config Management - Validation
+system.config.validate.configKeyExists=Config key already exists
+
+# Menu Management - List Page
+system.menu.list.title=Menu List
+system.menu.list.query.menuName=Menu Name:
+system.menu.list.query.visible=Menu Status:
+system.menu.list.btn.expandCollapse=Expand/Collapse
+system.menu.modalName=Menu
+system.menu.table.menuName=Menu Name
+system.menu.table.orderNum=Order
+system.menu.table.url=URL
+system.menu.table.menuType=Type
+system.menu.table.visible=Visible
+system.menu.table.perms=Permission
+system.menu.table.opra=Operation
+system.menu.type.catalog=Catalog
+system.menu.type.menu=Menu
+system.menu.type.button=Button
+
+# Menu Management - Form (shared by add and edit)
+system.menu.add.title=Add Menu
+system.menu.edit.title=Edit Menu
+system.menu.form.parentMenu=Parent Menu:
+system.menu.form.menuType=Menu Type:
+system.menu.form.menuName=Menu Name:
+system.menu.form.menuCode=Menu Code:
+system.menu.form.url=URL:
+system.menu.form.url.help=Request URL, e.g., `/system/user`, use `http(s)://` prefix for external links
+system.menu.form.target=Target:
+system.menu.target.tab=Tab
+system.menu.target.blank=New Window
+system.menu.form.perms=Permission:
+system.menu.form.perms.help=Permission identifier in controller, e.g., @RequiresPermissions("")
+system.menu.form.orderNum=Order:
+system.menu.form.orderNum.help=Smaller number comes first
+system.menu.form.icon=Icon:
+system.menu.form.icon.help=Click to select FontAwesome icon
+system.menu.form.icon.placeholder=Select Icon
+system.menu.form.visible=Visible:
+system.menu.form.visible.help=Hidden menus won't appear in sidebar and cannot be accessed
+system.menu.form.isRefresh=Refresh:
+system.menu.form.isRefresh.help=Whether to refresh page when opening menu tab
+
+# Menu Management - Validation and Messages
+system.menu.validate.menuNameExists=Menu already exists
+system.menu.modal.selectMenu=Select Menu
+system.menu.msg.mainMenuNotSelect=Main menu cannot be selected
+
+# Menu Management - Tree Select Page
+system.menu.tree.title=Menu Tree Select
+system.menu.tree.keyword=Keyword:
+system.menu.tree.btn.search= Search 
+system.menu.tree.btn.showSearch=Show Search
+system.menu.tree.btn.hideSearch=Hide Search
+system.menu.tree.expand=Expand
+system.menu.tree.collapse=Collapse
+
+# Menu Management - Icon Page
+system.menu.icon.title=Font Awesome Icon List
+
+# Common Options
+options.yes=Yes
+options.no=No
+
+# User Profile Page
+system.profile.title=User Profile
+
+# Logo Section
+system.profile.logo.title=System Logo
+system.profile.logo.edit=Edit Logo
+system.profile.logo.modal.title=Edit Custom Logo
+
+# Personal Info Section
+system.profile.info.title=Personal Info
+system.profile.info.loginName=Login Name:
+system.profile.info.phone=Phone:
+system.profile.info.dept=Department:
+system.profile.info.email=Email:
+system.profile.info.createTime=Create Time:
+
+# Basic Info Section
+system.profile.basic.title=Basic Info
+system.profile.tab.basic=Basic Info
+system.profile.tab.password=Change Password
+
+# Form Fields
+system.profile.form.userName=User Name:
+system.profile.form.userName.placeholder=Enter user name
+system.profile.form.phone=Phone:
+system.profile.form.phone.placeholder=Enter phone number
+system.profile.form.email=Email:
+system.profile.form.email.placeholder=Enter email
+system.profile.form.sex=Gender:
+system.profile.sex.male=Male
+system.profile.sex.female=Female
+
+# Avatar
+system.profile.avatar.edit=Edit Avatar
+system.profile.avatar.modal.title=Edit User Avatar
+
+# Change Password
+system.profile.password.old=Old Password:
+system.profile.password.old.placeholder=Enter old password
+system.profile.password.new=New Password:
+system.profile.password.new.placeholder=Enter new password
+system.profile.password.confirm=Confirm Password:
+system.profile.password.confirm.placeholder=Confirm new password
+
+# Password Rule Tips
+system.profile.password.rule.number=Password can only be 0-9 digits
+system.profile.password.rule.letter=Password can only be a-z and A-Z letters
+system.profile.password.rule.mix=Password must contain (letters, numbers)
+system.profile.password.rule.special=Password must contain (letters, numbers, special characters !@#$%^&*()-=_+)
+
+# Validation Messages
+system.profile.validate.userName.required=Please enter user name
+system.profile.validate.email.required=Please enter email
+system.profile.validate.email.exists=Email already exists
+system.profile.validate.phone.required=Please enter phone number
+system.profile.validate.phone.exists=Phone number already exists
+system.profile.password.validate.old.required=Please enter old password
+system.profile.password.validate.old.error=Old password is incorrect
+system.profile.password.validate.new.required=Please enter new password
+system.profile.password.validate.new.minlength=Password cannot be less than 6 characters
+system.profile.password.validate.new.maxlength=Password cannot be more than 20 characters
+system.profile.password.validate.confirm.required=Please enter new password again
+system.profile.password.validate.confirm.equalTo=Two password entries are inconsistent
+
+# Upload and Crop
+system.profile.upload.image=Upload Image
+system.profile.upload.image.type.error=Please select an image file.
+system.profile.cropper.loading=Cropper loading, please wait...
+
+# Common Buttons
+btn.confirm=Confirm
+
+# Post Management - List Page
+system.post.list.title=Post List
+system.post.list.query.postCode=Post Code:
+system.post.list.query.postName=Post Name:
+system.post.list.query.status=Post Status:
+system.post.modalName=Post
+system.post.table.postId=Post ID
+system.post.table.postCode=Post Code
+system.post.table.postName=Post Name
+system.post.table.postSort=Display Order
+system.post.table.status=Status
+system.post.table.createTime=Create Time
+system.post.table.opra=Operation
+
+# Post Management - Form (shared by add and edit)
+system.post.add.title=Add Post
+system.post.edit.title=Edit Post
+system.post.form.postName=Post Name:
+system.post.form.postCode=Post Code:
+system.post.form.postSort=Display Order:
+system.post.form.status=Post Status:
+system.post.form.remark=Remark:
+
+# Post Management - Validation
+system.post.validate.postCodeExists=Post code already exists
+system.post.validate.postNameExists=Post name already exists
+
+# Dict Type Management - List Page
+system.dict.type.list.title=Dict Type List
+system.dict.type.list.query.dictName=Dict Name:
+system.dict.type.list.query.dictType=Dict Type:
+system.dict.type.list.query.status=Dict Status:
+system.dict.type.list.query.createTime=Create Time:
+system.dict.type.list.btn.refreshCache=Refresh Cache
+system.dict.type.modalName=Type
+system.dict.type.table.dictId=Dict ID
+system.dict.type.table.dictName=Dict Name
+system.dict.type.table.dictType=Dict Type
+system.dict.type.table.status=Status
+system.dict.type.table.remark=Remark
+system.dict.type.table.createTime=Create Time
+system.dict.type.table.opra=Operation
+system.dict.type.btn.list=List
+system.dict.type.modal.dictData=Dict Data
+
+# Dict Type Management - Form (shared by add and edit)
+system.dict.type.add.title=Add Dict Type
+system.dict.type.edit.title=Edit Dict Type
+system.dict.type.form.dictName=Dict Name:
+system.dict.type.form.dictType=Dict Type:
+system.dict.type.form.dictType.help=Key value in data storage, e.g., sys_user_sex
+system.dict.type.form.status=Status:
+system.dict.type.form.remark=Remark:
+
+# Dict Type Management - Validation
+system.dict.type.validate.dictTypeExists=This dict type already exists
+
+# Dict Tree Select Page
+system.dict.tree.title=Dict Tree Select
+system.dict.tree.keyword=Keyword:
+system.dict.tree.btn.search= Search 
+system.dict.tree.btn.showSearch=Show Search
+system.dict.tree.btn.hideSearch=Hide Search
+
+# Dict Data Management - List Page
+system.dict.data.list.title=Dict Data List
+system.dict.data.list.query.dictType=Dict Name:
+system.dict.data.list.query.dictLabel=Dict Label:
+system.dict.data.list.query.status=Data Status:
+system.dict.data.modalName=Data
+system.dict.data.table.dictCode=Dict Code
+system.dict.data.table.dictLabel=Dict Label
+system.dict.data.table.dictValue=Dict Value
+system.dict.data.table.dictSort=Dict Sort
+system.dict.data.table.status=Status
+system.dict.data.table.remark=Remark
+system.dict.data.table.createTime=Create Time
+system.dict.data.table.opra=Operation
+
+# Dict Data Management - Form (shared by add and edit)
+system.dict.data.add.title=Add Dict Data
+system.dict.data.edit.title=Edit Dict Data
+system.dict.data.form.dictLabel=Dict Label:
+system.dict.data.form.dictValue=Dict Value:
+system.dict.data.form.dictType=Dict Type:
+system.dict.data.form.cssClass=CSS Class:
+system.dict.data.form.dictSort=Dict Sort:
+system.dict.data.form.listClass=List Class:
+system.dict.data.form.listClass.help=Table column display style attribute for dictionary
+system.dict.data.form.isDefault=Is Default:
+system.dict.data.form.status=Status:
+system.dict.data.form.remark=Remark:
+
+# List Class Options
+system.dict.data.listClass.default=Default
+system.dict.data.listClass.primary=Primary
+system.dict.data.listClass.success=Success
+system.dict.data.listClass.info=Info
+system.dict.data.listClass.warning=Warning
+system.dict.data.listClass.danger=Danger
+
+# Common Options
+options.pleaseSelect=---Please Select---
+
+# Cache Monitor Page
+system.cache.title=Cache Monitor
+system.cache.list.title=Cache List
+system.cache.keys.title=Key List
+system.cache.value.title=Cache Content
+system.cache.table.cacheName=Cache Name
+system.cache.table.cacheKey=Cache Key
+system.cache.table.operation=Operation
+system.cache.btn.clear=Clear
+system.cache.btn.clearAll=Clear All
+system.cache.label.cacheName=Cache Name:
+system.cache.label.cacheKey=Cache Key:
+system.cache.label.cacheValue=Cache Content:
+system.cache.msg.refreshListSuccess=Cache list refreshed successfully
+system.cache.msg.refreshKeysSuccess=Key list refreshed successfully
+system.cache.msg.clearCacheNameSuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearCacheKeySuccess=Cache [{0}] cleared successfully
+system.cache.msg.clearAllSuccess=All caches cleared successfully
+
+# Login Log Page
+system.logininfor.list.title=Login Log List
+system.logininfor.list.query.ipaddr=Login Address:
+system.logininfor.list.query.loginName=Login Name:
+system.logininfor.list.query.status=Login Status:
+system.logininfor.list.query.loginTime=Login Time:
+system.logininfor.modalName=Login Log
+system.logininfor.table.infoId=Access ID
+system.logininfor.table.loginName=Login Name
+system.logininfor.table.ipaddr=Login Address
+system.logininfor.table.loginLocation=Login Location
+system.logininfor.table.browser=Browser
+system.logininfor.table.os=Operating System
+system.logininfor.table.status=Login Status
+system.logininfor.table.msg=Operation Info
+system.logininfor.table.loginTime=Login Time
+
+# Online User Page
+system.online.list.title=Online User List
+system.online.list.query.ipaddr=Login Address:
+system.online.list.query.loginName=Login Name:
+system.online.modalName=Online User
+system.online.table.serialNumber=No.
+system.online.table.sessionId=Session ID
+system.online.table.loginName=Login Name
+system.online.table.deptName=Department
+system.online.table.ipaddr=Host
+system.online.table.loginLocation=Login Location
+system.online.table.browser=Browser
+system.online.table.os=Operating System
+system.online.table.status=Session Status
+system.online.table.startTimestamp=Login Time
+system.online.table.lastAccessTime=Last Access Time
+system.online.table.operation=Operation
+system.online.status.online=Online
+system.online.status.offline=Offline
+system.online.msg.confirmForceLogout=Are you sure you want to force the selected user to log out?
+system.online.msg.selectUser=Please select users to force logout
+system.online.msg.confirmBatchForceLogout=Are you sure you want to force logout {0} selected users?
+
+# Common Buttons (additional)
+btn.clean=Clean
+btn.unlock=Unlock
+btn.forceLogout=Force Logout
+
+# Operation Log Detail Page
+system.operlog.detail.title=Operation Log Detail
+system.operlog.detail.module=Operation Module:
+system.operlog.detail.loginInfo=Login Info:
+system.operlog.detail.requestUrl=Request URL:
+system.operlog.detail.costTime=Cost Time
+system.operlog.detail.millisecond=ms
+system.operlog.detail.method=Operation Method:
+system.operlog.detail.operParam=Request Parameters:
+system.operlog.detail.jsonResult=Response Parameters:
+system.operlog.detail.status=Status:
+system.operlog.detail.errorMsg=Error Message:
+system.operlog.status.normal=Normal
+system.operlog.status.abnormal=Abnormal
+
+# Operation Log List Page
+system.operlog.list.title=Operation Log List
+system.operlog.list.query.operIp=Operation Address:
+system.operlog.list.query.title=System Module:
+system.operlog.list.query.operName=Operator:
+system.operlog.list.query.businessType=Operation Type:
+system.operlog.list.query.status=Operation Status:
+system.operlog.list.query.operTime=Operation Time:
+system.operlog.modalName=Operation Log
+system.operlog.table.operId=Log ID
+system.operlog.table.title=System Module
+system.operlog.table.businessType=Operation Type
+system.operlog.table.operName=Operator
+system.operlog.table.deptName=Department
+system.operlog.table.operIp=Operation Address
+system.operlog.table.operLocation=Operation Location
+system.operlog.table.status=Operation Status
+system.operlog.table.operTime=Operation Time
+system.operlog.table.costTime=Cost Time
+system.operlog.table.operation=Operation
+system.operlog.status.success=Success
+system.operlog.status.fail=Fail
+system.operlog.format.costTime=%sms
+
+# Service Monitor Page (netdata/system_io)
+system.netdata.title=Service Monitor
+system.netdata.chart.system.cpu=System CPU
+system.netdata.chart.system.ram=System RAM
+system.netdata.chart.system.io=System Disk I/O
+system.netdata.chart.net.enp4s0=Network Traffic
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 Memory
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui Memory
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH Memory
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL Memory
+system.netdata.loading=Loading...
+system.netdata.status.offline=Offline
+system.netdata.status.online=Online
+system.netdata.status.error=Connection Failed
+system.netdata.time.5min=5 Minutes
+system.netdata.time.15min=15 Minutes
+system.netdata.time.1hour=1 Hour
+system.netdata.time.24hour=24 Hours
+system.netdata.time.3day=3 Days
+system.netdata.btn.refresh=Refresh
+system.netdata.stat.current=Current
+system.netdata.stat.avg=Average
+system.netdata.stat.max=Max
+system.netdata.stat.min=Min
+system.netdata.yaxis.cpu=CPU Usage (%)
+system.netdata.yaxis.mem=Memory Usage (MiB)
+system.netdata.yaxis.io=Disk I/O (KiB/s)
+system.netdata.yaxis.net=Network Traffic (kilobits/s)
+system.netdata.error.noData=No data returned
+system.netdata.error.prefix=Error:
+
+# Server Monitor Page
+system.server.title=Server Monitor
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=Cores
+system.server.cpu.used=User Usage
+system.server.cpu.sys=System Usage
+system.server.cpu.free=Current Free
+system.server.mem.title=Memory
+system.server.mem.memory=Memory
+system.server.mem.jvm=JVM
+system.server.mem.total=Total
+system.server.mem.used=Used
+system.server.mem.free=Free
+system.server.mem.usage=Usage
+system.server.sys.title=Server Info
+system.server.sys.computerName=Server Name
+system.server.sys.osName=Operating System
+system.server.sys.computerIp=Server IP
+system.server.sys.osArch=System Architecture
+system.server.sys.userDir=Project Directory
+system.server.jvm.title=JVM Info
+system.server.jvm.name=Java Name
+system.server.jvm.version=Java Version
+system.server.jvm.startTime=Start Time
+system.server.jvm.runTime=Run Time
+system.server.jvm.home=Installation Path
+system.server.jvm.inputArgs=Runtime Arguments
+system.server.disk.title=Disk Status
+system.server.disk.dirName=Drive Path
+system.server.disk.sysTypeName=File System
+system.server.disk.typeName=Drive Type
+system.server.disk.total=Total Size
+system.server.disk.free=Free Size
+system.server.disk.used=Used Size
+system.server.disk.usage=Usage Percentage
+system.server.table.property=Property
+system.server.table.value=Value
+system.server.unit.count=cnt
+
+# Notice Add Page
+system.notice.add.title=Add Notice
+system.notice.placeholder.noticeContent=Please enter notice content
+system.notice.msg.uploadFail=Image upload failed.
+
+# Notice Edit Page
+system.notice.edit.title=Edit Notice
+
+# Notice List Page
+system.notice.list.title=Notice List
+system.notice.list.query.noticeTitle=Notice Title:
+system.notice.list.query.createBy=Creator:
+system.notice.list.query.noticeType=Notice Type:
+system.notice.modalName=Notice
+system.notice.table.noticeId=No.
+system.notice.table.noticeTitle=Notice Title
+system.notice.table.noticeType=Notice Type
+system.notice.table.status=Status
+system.notice.table.createBy=Creator
+system.notice.table.createTime=Create Time
+system.notice.table.operation=Operation
+
+# Notice Form Labels
+system.notice.label.noticeTitle=Notice Title:
+system.notice.label.noticeType=Notice Type:
+system.notice.label.noticeContent=Notice Content:
+system.notice.label.status=Notice Status:
+
+# Notice View Page
+system.notice.view.title=Notice Detail
+system.notice.view.sendTime=Send Time
+system.notice.view.sender=Sender
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=Language For ASR
+callTask.form.asrLanguageCode.empty=Please Select Language For ASR
+callTask.form.ttsLanguageCode=Language For TTS
+callTask.form.ttsLanguageCode.empty=Please Select Language For TTS
+inboundllm.form.asrLanguageCode=Language For ASR
+inboundllm.form.asrLanguageCode.empty=Please Select Language For ASR
+inboundllm.form.ttsLanguageCode=Language For TTS
+inboundllm.form.ttsLanguageCode.empty=Please Select Language For TTS
+ivr.form.asrLanguageCode=Language For ASR
+ivr.form.asrLanguageCode.empty=Please Select Language For ASR
+ivr.form.ttsLanguageCode=Language For TTS
+ivr.form.ttsLanguageCode.empty=Please Select Language For TTS
+
+# sys_config data
+_sys.config.sys.index.skinName=Default Skin Style for Main Framework Page
+_sys.config.sys.user.initPassword=User Management - Initial Account Password
+_sys.config.sys.index.sideTheme=Sidebar Theme for Main Framework Page
+_sys.config.sys.account.registerUser=Account Self-Service - Enable User Registration
+_sys.config.sys.account.chrtype=User Management - Password Character Range
+_sys.config.sys.account.initPasswordModify=User Management - Initial Password Change Policy
+_sys.config.sys.account.passwordValidateDays=User Management - Account Password Update Cycle
+_sys.config.sys.index.menuStyle=Main Framework Page - Menu Navigation Display Style
+_sys.config.sys.index.footer=Main Framework Page - Enable Footer
+_sys.config.sys.index.tagsView=Main Framework Page - Enable Tabs
+_sys.config.sys.login.blackIPList=User Login - Blacklist
+_sys.config.sys.version=System Version
+_sys.config.sys.logo=System Default Logo
+_sys.config.config_asr_provider_aliyun=Alibaba Cloud
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=China Telecom
+_sys.config.config_asr_provider_aws=Amazon
+_sys.config.config_tts_provider_aliyun=Alibaba Cloud
+_sys.config.config_tts_provider_doubao=Doubao
+_sys.config.config_tts_provider_chinatelecom=China Telecom
+_sys.config.config_tts_provider_aws=Amazon
+_sys.config.config_tts_language_aliyun_tts=Alibaba Cloud TTS Language Settings
+_sys.config.config_tts_language_doubao_vcl_tts=Doubao TTS Language Settings
+_sys.config.config_tts_language_aws_tts=Amazon TTS Language Settings
+_sys.config.config_asr_language_aliyun=Alibaba Cloud ASR Language Settings
+_sys.config.config_asr_language_aws=Amazon ASR Language Settings
+
+# Dictionary Types
+_sys.dict.type.sys_user_sex=User Gender
+_sys.dict.type.sys_show_hide=Menu Status
+_sys.dict.type.sys_normal_disable=System Switch
+_sys.dict.type.sys_job_status=Job Status
+_sys.dict.type.sys_job_group=Job Group
+_sys.dict.type.sys_yes_no=System Yes/No
+_sys.dict.type.sys_notice_type=Notice Type
+_sys.dict.type.sys_notice_status=Notice Status
+_sys.dict.type.sys_oper_type=Operation Type
+_sys.dict.type.sys_common_status=System Status
+
+# Dictionary Values
+_sys.dict.data.label.sys_user_sex.0=Male
+_sys.dict.data.label.sys_user_sex.1=Female
+_sys.dict.data.label.sys_user_sex.2=Unknown
+_sys.dict.data.label.sys_show_hide.0=Show
+_sys.dict.data.label.sys_show_hide.1=Hide
+_sys.dict.data.label.sys_normal_disable.0=Normal
+_sys.dict.data.label.sys_normal_disable.1=Disabled
+_sys.dict.data.label.sys_job_status.0=Normal
+_sys.dict.data.label.sys_job_status.1=Paused
+_sys.dict.data.label.sys_job_group.DEFAULT=Default
+_sys.dict.data.label.sys_job_group.SYSTEM=System
+_sys.dict.data.label.sys_yes_no.Y=Yes
+_sys.dict.data.label.sys_yes_no.N=No
+_sys.dict.data.label.sys_notice_type.1=Notification
+_sys.dict.data.label.sys_notice_type.2=Announcement
+_sys.dict.data.label.sys_notice_status.0=Normal
+_sys.dict.data.label.sys_notice_status.1=Closed
+_sys.dict.data.label.sys_oper_type.0=Other
+_sys.dict.data.label.sys_oper_type.1=Add
+_sys.dict.data.label.sys_oper_type.2=Edit
+_sys.dict.data.label.sys_oper_type.3=Delete
+_sys.dict.data.label.sys_oper_type.4=Authorize
+_sys.dict.data.label.sys_oper_type.5=Export
+_sys.dict.data.label.sys_oper_type.6=Import
+_sys.dict.data.label.sys_oper_type.7=Force Quit
+_sys.dict.data.label.sys_oper_type.8=Generate Code
+_sys.dict.data.label.sys_oper_type.9=Clear Data
+_sys.dict.data.label.sys_common_status.0=Success
+_sys.dict.data.label.sys_common_status.1=Failed
+
+# System Parameters
+_cc.params.phone_encrypted_key=Phone Toolbar - Encryption Key for Outbound Calls
+_cc.params.recording_path=Recording Save Path
+_cc.params.enable_cc_record_stereo=Enable Stereo Recording
+_cc.params.post_cdr_url=CDR Push Interface URL
+_cc.params.recordings_extension=Recording File Format
+_cc.params.conference_video_templates=Available Conference Layouts
+_cc.params.conference_gateway_addr=Video Conference Gateway Address
+_cc.params.conference_recording_path=Video Conference Recording Save Path
+_cc.params.conference_gateway_caller=Video Conference Caller Number
+_cc.params.video_level_id_list=Video Quality Level List
+_cc.params.conference_outboud_profile=Video Conference Outbound Profile
+_cc.params.conference_video_layouts=Video Conference Layout List
+_cc.params.wait-for-second-vad-during-interrupt=Enable Secondary Interruption Setting (Wait for Second Speech)
+_cc.params.outbound-call-extra-params-for-profile-internal2=Outbound Call Extra Parameters (Call Behavior Control)
+_cc.params.call_monitor_enabled=Enable Call Monitoring
+_cc.params.fs_log_file_path=FreeSWITCH Log File Path
+_cc.params.cc_log_file_path=Call Center Log File Path
+_cc.params.fs_call_asr_enabled=Enable Bidirectional ASR Recognition and Result Push
+_cc.params.fs_call_asr_engine=Bidirectional ASR Engine (chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=Enable Inbound Call Queue Monitoring
+_cc.params.fs_docker_container_name=FreeSWITCH Docker Container Name
+_cc.params.fs_conf_directory=FreeSWITCH Configuration Directory
+_cc.params.robot-asr-type=ASR Recognition Type (mrcp/websocket)
+_cc.params.fs-asr-mrcp-param=MRCP ASR Request Parameters
+_cc.params.robot-max-no-speak-time=Max Customer Silence Duration in Robot Call (Auto Hangup Timeout)
+_cc.params.max-call-concurrency=Max Inbound Call Concurrency Limit
+_cc.params.asr-pause-enabled=ASR On-Demand Recognition (Enable After Robot Speech)
+_cc.params.max-wait-time-after-vad-start=Max Wait Time After Speech Detection Start (seconds)
+_cc.params.inbound-transfer-agent-timeout=Inbound Call Transfer to Agent Timeout (seconds)
+_cc.params.hide-inbound-number=Hide Inbound Number When Transferring to Agent
+_cc.params.inbound-play-opnum=Play Agent Number When Transferring Inbound Call
+_cc.params.event-socket-ip=Event Socket IP Address
+_cc.params.event-socket-port=Event Socket Port
+_cc.params.event-socket-pass=Event Socket Password
+_cc.params.event-socket-conn-pool-size=Event Socket Connection Pool Size
+_cc.params.max-agent-number=Max Agent Number
+_cc.params.ws-server-auth-token-secret=Token Secret for Decryption and Verification
+_cc.params.ws-server-port=WebSocket Server Phone Toolbar Port
+_cc.params.vad-intelligent-wait=Enable VAD Intelligent Wait
+_cc.params.vad-intelligent-wait-ms=VAD Intelligent Wait Duration (milliseconds)
+_cc.params.call-center-server-port=Call Center WebAPI Server Port (Read-Only)
+_cc.params.fs-deploy-type=FreeSWITCH Deployment Type (docker/native)
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH Native Deployment Startup Script Path
+_cc.params.fs-root-directory=FreeSWITCH Program Root Directory
+_cc.params.call-center-server-ip-addr=Server External IP Address
+_cc.params.call-center-websocket-port=Call Center WebSocket Port
+_cc.params.tts_content_variables=TTS Content Variables in Scripts
+_cc.params.call-center-api-token=Voice Notification Access Token
+_cc.params.outbound-max-line-number=Global Parameter - Max Outbound Concurrency
+_cc.params.outbound-enable-prediction-algorithm=Enable Predictive Dialing Algorithm for Batch Calls
+_cc.params.aliyun-tts-account-json=Aliyun TTS Account Parameters JSON
+_cc.params.empty-number-detection-enabled=Enable Empty Number Detection
+_cc.params.empty-number-detection-config=Empty Number Detection Configuration
+_cc.params.default_interrupt_ignore_keywords=Default Interruption Ignore Keywords List
+_cc.params.llm-max-try=Max Retry Times for LLM Connection
+_cc.params.llm-conn-timeout=LLM Connection Timeout (milliseconds)
+_cc.params.llm-max-try-fail-tips=LLM Connection Failure Prompt Message
+_cc.params.api-client-white-ips=API Client IP Whitelist
+_cc.params.price_aliyun_asr=Aliyun ASR Price (per hour)
+_cc.params.price_aliyun_tts=Aliyun TTS Price (per 1k requests)
+_cc.params.price_aliyun_tts_flow=Aliyun Voice Cloning Price (per 10k characters)
+_cc.params.price_llm_token=LLM Price (per 1k tokens)
+_cc.params.doubao-tts-account-json=Doubao TTS Account Parameters JSON
+_cc.params.system_license_info=System License Information
+_cc.params.system_hw_fingerprint_cmd=System Hardware Fingerprint Command
+_cc.params.fs-inbound-acl-enabled=Enable FreeSWITCH Inbound ACL Protection
+_cc.params.freeswitch_root=FreeSWITCH Root Directory
+_cc.params.callTask_allowDel_days=Days Interval Allowed to Delete Call Tasks and Records
+_cc.params.firewalld-config-path=Firewalld Configuration File Path
+_cc.params.firewalld-restart-cmd=Firewalld Restart Command
+_cc.params.firewalld-enabled=Enable Firewalld Firewall
+_cc.params.fs-inbound-allow-ip-list=Inbound IP Whitelist (for External Trunk Inbound)
+_cc.params.fs-register-allow-ip-list=Extension Registration IP Whitelist
+_cc.params.fs-register-acl-enabled=Enable FreeSWITCH Extension Registration ACL Protection
+_cc.params.max-wait-time-customer-speaking=Max Time For Wait Customer Speaking
+
+# 新增功能
+_menu.firewalld=Firewalld Configuration
+_menu.awsAsrConf=AWS ASR Configuration
+_menu.awsTtsConf=AWS TTS Configuration
+_menu.deepgramAsrConf=Deepgram ASR Configuration
+_menu.deepgramTtsConf=Deepgram TTS Configuration
+
+# Firewalld Configuration
+firewallRule.add.title=Add Firewall Rule Configuration
+firewallRule.edit.title=Edit Firewall Rule Configuration
+firewallRule.notice.title=Notice
+firewallRule.notice.freeswitch=No need to add Freeswitch related ports
+firewallRule.notice.ssh=No need to add SSH related ports
+firewallRule.form.protocol=Protocol
+firewallRule.form.portStart=Start Port
+firewallRule.form.portEnd=End Port
+firewallRule.form.enableFromSource=Enable Source Restriction
+firewallRule.form.fromSource=Source Address
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=Yes
+firewallRule.boolean.no=No
+firewallRule.placeholder.portStart=Enter start port (1-65535)
+firewallRule.placeholder.portEnd=Enter end port (1-65535)
+firewallRule.placeholder.fromSource=Enter source IP address or subnet
+firewallRule.validation.integer=Please enter an integer
+firewallRule.validation.portRange=Start port must be less than or equal to end port
+firewallRule.validation.portStart.required=Please enter start port
+firewallRule.validation.portEnd.required=Please enter end port
+firewallRule.validation.port.range=Port range must be between 1-65535
+
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=Locked
+_phoneBar.agentStatus.text_6=Conference
+_phoneBar.agentStatus.text_5=FillForm
+_phoneBar.agentStatus.text_4=InCall
+_phoneBar.agentStatus.text_33=Training
+_phoneBar.agentStatus.text_32=Meeting
+_phoneBar.agentStatus.text_31=Rest
+_phoneBar.agentStatus.text_3=Busy
+_phoneBar.agentStatus.text_2=Free
+_phoneBar.agentStatus.text_1=JustLogin
+phonebar.msg.error_login_token=Phone Bar: Unable to get loginToken!
+phonebar.msg.error_wss_domain=ERROR! When WSS is enabled, domain name must be used to access websocketServer! 
+phonebar.msg.websocket_not_supported=Your browser does not support WebSocket, you cannot use the features of this page!
+phonebar.msg.hold_call_hangup=The held call has been hung up.
+phonebar.msg.call_waiting=Customer call is waiting.
+phonebar.msg.call_wait_retrieved=The waiting call has been retrieved.
+phonebar.msg.consultation_started=Consultation has started.
+phonebar.msg.consultation_ended=Consultation has ended.
+phonebar.msg.waiting_call_hangup=The waiting customer has hung up.
+phonebar.msg.ipcc_disconnected=ipccserver connection disconnected.
+phonebar.msg.select_group=Please select a business group!
+phonebar.msg.select_agent=Please select an agent to consult!
+phonebar.msg.select_free_agent=Please select an available agent!
+phonebar.msg.cannot_consult_self=Cannot consult yourself, please select another agent!
+phonebar.msg.select_transfer_group=Please select the transfer business group!
+phonebar.msg.select_transfer_agent=Please select the transfer agent!
+phonebar.msg.cannot_transfer_self=Cannot transfer to yourself, please select another agent!
+phonebar.msg.please_login=Please login first.
+phonebar.msg.no_call=No active call currently.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=Cannot send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=Please enter the phone number!
+phonebar.msg.invalid_phone=Please enter a valid phone number format!
+phonebar.msg.agent_locked=Agent is locked
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false, default UI toolbar buttons disabled.
+phonebar.msg.logout_not_allowed=Logout is not allowed currently!
+phonebar.msg.confirm_close=Closing the page will prevent you from answering calls, are you sure you want to close?
+phonebar.msg.esc_hangup=ESC key pressed, sending hangup command.
+phonebar.msg.confirm_transfer_conference=Do you want to transfer the current call to conference?
+phonebar.msg.enter_member_name=Please enter the participant name!
+phonebar.msg.enter_member_phone=Please enter the participant phone number!
+phonebar.msg.member_exists=Conference member already exists, please do not add again!
+phonebar.msg.start_conference=Starting multi-party call
+phonebar.msg.provide_call_id=Please provide the callId of the call to monitor!
+phonebar.status.free=Free
+phonebar.status.about_to_call=About to call
+phonebar.status.in_call=In call
+page.title=My Workbench
+phonebar.conference.label.type=Conference Type
+phonebar.conference.option.video=Video
+phonebarbar.conference.option.audio=Audio
+phonebar.conference.btn.start=Start Conference
+phonebar.conference.btn.end=End Conference
+phonebar.conference.btn.join=Join
+phonebar.conference.btn.remove=Remove
+phonebar.conference.btn.reinvite=Recall
+phonebar.conference.placeholder.name=Name
+phonebar.conference.placeholder.phone=Phone
+phonebar.conference.alt.mute=Mute this member
+phonebar.conference.alt.vmute=Turn off this member's video
+phonebar.conference.title.remove=Remove conference member
+phonebar.conference.title.reinvite=Recall member
+phonebar.transfer.label.group=Business Group
+phonebar.transfer.label.member=Agent Members
+phonebar.transfer.option.select=Please Select
+phonebar.transfer.placeholder.phone=Phone Number
+phonebar.transfer.title.externalPhone=Transfer current call to external number. Leave empty to ignore.
+phonebar.transfer.btn.transfer=Transfer Call
+phonebar.transfer.title.transfer=Transfer current call to selected agent.
+phonebar.transfer.btn.retrieve=Retrieve Customer
+phonebar.transfer.title.retrieve=Use this button to retrieve customer when consultation fails.
+phonebar.transfer.btn.transferCall=Transfer Customer
+phonebar.transfer.title.transferCall=Use this button to transfer call to expert agent when consultation succeeds.
+phonebar.transfer.btn.consult=Dial Consult
+phonebar.transfer.title.consult=Dial Consult
+phonebar.transfer.status.login=Just Logged In
+phonebar.transfer.status.free=Free
+phonebar.transfer.status.busy=Busy
+phonebar.transfer.status.calling=In Call
+phonebar.transfer.status.after=After Call Work
+phonebar.popup.inbound=Inbound Popup
+phonebar.popup.outbound=Outbound Popup
+phonebar.chat.dialog.end=Dialog ended.
+phonebar.chat.role.customer=Customer
+phonebar.chat.role.agent=Me
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS Models
+inboundllm.form.ttsModels.empty=Please Select TTS Models
+inboundllm.form.asrModels=ASR Models
+inboundllm.form.asrModels.empty=Please Select ASR Models
+callTask.form.ttsModels=TTS Models
+callTask.form.ttsModels.empty=Please Select TTS Models
+callTask.form.asrModels=ASR Models
+callTask.form.asrModels.empty=Please Select ASR Models
+ivr.form.ttsModels=TTS Models
+ivr.form.ttsModels.empty=Please Select TTS Models
 

+ 1032 - 1
ruoyi-admin/src/main/resources/static/i18n/messages_zh.properties

@@ -4,6 +4,9 @@ sys.sysName=呼叫管理系统
 ## 弹框提示
 tips.defaultPass=您的密码还是初始密码,请修改密码!
 tips.security.title=安全提示
+tips.role.cust=客户
+tips.role.agent=坐席
+tips.role.kb=知识库查询
 ## 按钮
 btn.ok=确定
 btn.cancel=取消
@@ -56,7 +59,39 @@ user.login.title=登录呼叫管理系统
 user.login.welcome=欢迎使用
 
 # common
-common.adminPage.upload.requiredTips = 上传的文件不能为空
+common.adminPage.upload.requiredTips=上传的文件不能为空
+common.tip.selected.empty=请至少选择一条记录
+common.tip.del.confirm=确认要删除选中的数据吗?
+common.tip.remove.confirm=确认要删除该条数据吗?
+common.tip.clear.confirm=确定清空所有数据吗?
+common.tip.loading=正在处理中,请稍候...
+common.tip.export.confirm="确定导出所有数据吗?"
+common.tip.success=操作成功
+common.tip.fail=操作失败
+common.table.search=搜索
+common.table.col=列
+common.table.refresh=刷新
+common.tip.upload.success=文件导入成功!
+common.tip.upload.fail=文件导入失败,请检查文件格式是否正确!
+common.msg.callRecords.del.confirm=确定要删除该外呼任务和关联的通话记录吗?
+common.msg.callRecords.del.warning=删除后数据将无法恢复,请谨慎操作!
+common.tip.del.success=删除成功!
+common.tip.del.fail=删除失败,请稍后重试!
+menuTab.close_current=关闭当前
+menuTab.close_other=关闭其他
+menuTab.close_left=关闭左侧
+menuTab.close_right=关闭右侧
+menuTab.close_all=全部关闭
+menuTab.full=全屏显示
+menuTab.refresh=刷新页面
+menuTab.open=新窗口打开
+common.msg.copy.success=已复制到剪贴板
+common.msg.copy.fail=复制失败,请手动复制
+common.msg.unknown.media=不支持的媒体格式
+common.msg.file.notexists=文件不存在!
+common.time.hour=时
+common.time.minute=分
+common.time.second=秒
 
 # 电话工具条
 phonebar.placeholder.phonenum=请输入电话号码
@@ -106,6 +141,12 @@ phonebar.msg.free=空闲
 phonebar.msg.busy=忙碌
 phonebar.msg.customer_channel_hold=通话已保持
 phonebar.msg.customer_channel_unhold=通话已接回
+phonebar.msg.inner_consultation_start=咨询开始.
+phonebar.msg.inner_consultation_stop=咨询结束.
+phonebar.msg.transfer_call_success=电话转接成功.
+phonebar.msg.conferenceEnd=多方通话结束
+phonebar.msg.conferenceStart=多方通话进行中
+phonebar.msg.transferToConferenceSuccess=已接入多方会议
 phonebar.label.loginTime=签入时间:
 phonebar.label.agentStatus=状态:
 phonebar.label.queueStat=当前排队人数:
@@ -149,6 +190,17 @@ params.manage.form.paramType=参数类型:
 params.manage.form.title.add=新增callcenter参数配置
 params.manage.form.title.edit=修改callcenter参数配置
 
+# firewalld管理页面
+firewalld.manage.query.protocol=协议类型
+firewalld.manage.query.portStart=起始端口
+firewalld.manage.table.portEnd=结束端口
+firewalld.manage.table.enableFromSource=仅允许来源IP
+firewalld.manage.table.fromSource=来源IP
+firewalld.manage.table.opra=操作
+firewalld.manage.table.modalName=防火墙规则配置
+firewalld.manage.form.title.add=新增防火墙规则
+firewalld.manage.form.title.edit=修改防火墙规则
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=基础配置
 switchconf.label.more=更多
@@ -175,6 +227,10 @@ switchconf.cert.label=证书内容:
 switchconf.log.fs.label=freeswitch日志:
 switchconf.log.cc.label=callcenter日志:
 switchconf.log.error.label=错误日志:
+switchconf.asr.aws.header=亚马逊ASR参数配置
+switchconf.tts.aws.header=亚马逊tts参数配置
+switchconf.asr.deepgram.header=Deepgram ASR参数配置
+switchconf.tts.deepgram.header=Deepgram TTS参数配置
 
 # 业务组
 bizgroup.query.bizGroupName=业务组名称:
@@ -918,6 +974,7 @@ ivr.table.ttsText=话术内容
 ivr.table.action=处理方式
 ivr.table.opra=操作
 ivr.form.pressKeyInvalidTips.default=输入错误,请重新输入
+ivr.apply.confirmMessage=不要再外呼作业及呼入高峰期间,执行应用设置。否则会导致电话异常。
 
 callTask.form.taskType3=IVR外呼
 callTask.form.ivrId=IVR方案
@@ -931,6 +988,40 @@ ivr.form.hangupTips=挂机提示:
 ivr.form.aiInboundId=AI客服:
 ivr.form.aiInboundId.empty=请选择AI客服
 ivr.form.action.ai=转接到AI客服
+ivr.form.placeholder.ttsText=输入TTS文本或上传音频文件
+ivr.form.btn.title.tts=TTS合成
+ivr.form.btn.tts=合成
+ivr.form.btn.title.upload=上传WAV音频文件
+ivr.form.btn.upload=上传
+ivr.form.tips.helpblock=支持直接输入文本或上传WAV格式音频文件。输入//可调出变量列表
+ivr.form.title.ttsAudioPreview=音频预览
+ivr.form.placeholder.hangupTips=输入挂机提示文本或上传音频文件
+ivr.form.placeholder.pressKeyInvalidTips=输入挂机提示文本或上传音频文件
+ivr.form.tips.digitRange=请输入有效的正则表达式,用于验证用户输入格式
+ivr.validator.msg.checkDigit=该按键值在同一层级下已存在,请使用其他按键
+ivr.validator.msg.minLenCheck=最小长度不能大于最大长度
+ivr.validator.msg.maxLenCheck=最大长度不能小于最小长度
+ivr.validator.msg.validRegex=请输入有效的正则表达式格式
+ivr.edit.title=修改IVR配置
+ivr.add.title=新增IVR配置
+ivr.variableSelector.title=选择变量
+ivr.variableSelector.tip=点击插入
+ivr.modal.upload.title=上传音频文件
+ivr.modal.upload.label=选择WAV文件:
+ivr.modal.upload.helpblock=仅支持WAV格式,最大50MB
+ivr.modal.upload.progress=上传进度:
+ivr.modal.upload.confirm=确认上传
+ivr.msg.selectFile=请先选择文件!
+ivr.msg.wavOnly=请选择 WAV 格式的音频文件!
+ivr.msg.fileSizeLimit=文件大小不能超过 50MB!
+ivr.msg.uploadSuccess=上传成功!
+ivr.msg.uploadFail=上传失败:
+ivr.msg.networkError=网络错误:
+ivr.msg.ttsTextRequired=请输入需要合成的文本内容!
+ivr.msg.ttsSynthesizing=正在合成音频...
+ivr.msg.ttsSuccess=TTS合成成功!
+ivr.msg.ttsDataError=TTS合成返回数据格式错误!
+ivr.msg.ttsFail=TTS合成失败
 
 # 反向注册功能支持
 gateways.table.label.register2=反向注册模式
@@ -1061,4 +1152,944 @@ kbcontent.table.opra=操作
 kbcontent.form.title=标题:
 kbcontent.form.content=内容:
 
+# 用户管理
+system.user.list.boxTitle=组织机构
+system.user.list.btn.Dept=管理部门
+system.user.list.btn.Expand=展开
+system.user.list.btn.Collapse=折叠
+system.user.list.btn.Refresh=刷新部门
+system.user.list.query.loginName=登录账号:
+system.user.list.query.phonenumber=手机号码:
+system.user.list.query.status=用户状态:
+system.user.list.query.createName=创建时间:
+system.user.modalName=用户
+system.user.table.userId=用户
+system.user.table.loginName=登录账号
+system.user.table.userName=用户姓名
+system.user.table.deptName=部门
+system.user.table.email=邮箱
+system.user.table.phonenumber=手机
+system.user.table.userstatus=用户状态
+system.user.table.createTime=创建时间
+system.user.table.opra=操作
+system.user.table.btn.more=更多操作
+system.user.table.btn.resetPass=重置密码
+system.user.table.btn.authRole=分配角色
+system.user.form.head.base=基本信息
+system.user.form.loginName=登录账号:
+system.user.form.placeholder.loginName=请输入登录账号
+system.user.form.password=登录密码:
+system.user.form.placeholder.password=请输入登录密码
+system.user.form.tips.password=登录密码,鼠标按下显示密码
+system.user.form.userName=用户姓名:
+system.user.form.placeholder.userName=请输入用户姓名
+system.user.form.deptName=归属部门:
+system.user.form.placeholder.deptName=请选择归属部门
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=请输入手机号码
+system.user.form.email=邮箱:
+system.user.form.placeholder.email=请输入邮箱
+system.user.form.sex=用户性别:
+system.user.form.status=用户状态:
+system.user.form.post=岗位:
+system.user.form.role=角色:
+system.user.form.head.other=其他信息
+system.user.form.remark=备注:
+system.user.form.head.authRole=分配角色
+system.user.form.loginDate=最后登录时间
+system.user.form.loginIp=最后登录IP:
+system.user.form.updateTime=更新时间:
+system.user.form.updateBy=更新者:
+system.user.form.createTime=创建时间:
+system.user.form.createBy=创建者:
+system.user.authrole.table.roleId=角色编号
+system.user.authrole.table.roleSort=排序
+system.user.authrole.table.roleName=角色名称
+system.user.authrole.table.roleKey=权限字符
+system.user.authrole.table.createTime=创建时间
+system.user.resetpwd.password=输入密码:
+system.user.resetpwd.placeholder.password=请输入重置密码
+system.user.resetpwd.tips.password=重置密码,鼠标按下显示密码
+system.user.tips.disable=确认要停用用户吗?
+system.user.tips.enable=确认要启用用户吗?
+
+# 角色管理页面
+system.role.list.query.roleName=角色名称:
+system.role.list.query.roleKey=权限字符:
+system.role.list.query.status=角色状态:
+system.role.list.query.createTime=创建时间:
+system.role.modalName=角色
+system.role.table.roleId=角色编号
+system.role.table.roleName=角色名称
+system.role.table.roleKey=权限字符
+system.role.table.dataScope=数据权限
+system.role.table.roleSort=显示顺序
+system.role.table.roleStatus=角色状态
+system.role.table.createTime=创建时间
+system.role.table.opra=操作
+system.role.dataScope.all=全部数据权限
+system.role.dataScope.custom=自定义数据权限
+system.role.dataScope.dept=本部门数据权限
+system.role.dataScope.deptAndChild=本部门及以下数据权限
+system.role.dataScope.self=仅本人数据权限
+system.role.table.btn.dataScope=数据权限
+system.role.table.btn.authUser=分配用户
+system.role.table.btn.more=更多操作
+system.role.modal.dataScope=分配数据权限
+system.role.modal.authUser=分配用户
+system.role.confirm.disable=确认要停用角色吗?
+system.role.confirm.enable=确认要启用角色吗?
+
+# 角色管理-新增/修改页面
+system.role.add.title=新增角色
+system.role.edit.title=修改角色
+system.role.add.roleName=角色名称:
+system.role.add.roleKey=权限字符:
+system.role.add.roleKey.help=控制器中定义的权限字符,如:@RequiresRoles("")
+system.role.add.roleSort=显示顺序:
+system.role.add.status=状态:
+system.role.add.remark=备注:
+system.role.add.menuPermission=菜单权限:
+system.role.add.expandCollapse=展开/折叠
+system.role.add.selectAll=全选/全不选
+system.role.add.parentChildLink=父子联动
+system.role.add.validate.roleNameExists=角色名称已经存在
+system.role.add.validate.roleKeyExists=角色权限已经存在
+
+# 角色管理-分配用户页面
+system.role.authUser.title=角色分配用户
+system.role.authUser.query.loginName=登录名称:
+system.role.authUser.query.phonenumber=手机号码:
+system.role.authUser.btn.addUser=添加用户
+system.role.authUser.btn.cancelAuthBatch=批量取消授权
+system.role.authUser.btn.cancelAuth=取消授权
+system.role.authUser.table.userId=用户ID
+system.role.authUser.table.loginName=登录名称
+system.role.authUser.table.userName=用户名称
+system.role.authUser.table.email=邮箱
+system.role.authUser.table.phonenumber=手机
+system.role.authUser.table.status=用户状态
+system.role.authUser.table.createTime=创建时间
+system.role.authUser.table.opra=操作
+system.role.authUser.modal.selectUser=选择用户
+system.role.authUser.msg.selectOne=请至少选择一条记录
+system.role.authUser.confirm.cancelBatch=确认要删除选中的{0}条数据吗?
+system.role.authUser.confirm.cancelAuth=确认要取消该用户角色吗?
+
+# 角色管理-数据权限页面
+system.role.dataScope.title=角色数据权限
+system.role.dataScope.dataScope=数据范围:
+system.role.dataScope.help=特殊情况下,设置为"自定数据权限"
+system.role.dataScope.dataPermission=数据权限:
+
+# 角色管理-选择用户页面
+system.role.selectUser.title=分配角色选择用户
+
+# 部门管理-列表页面
+system.dept.list.title=部门列表
+system.dept.list.query.deptName=部门名称:
+system.dept.list.query.status=部门状态:
+system.dept.list.btn.expandCollapse=展开/折叠
+system.dept.modalName=部门
+system.dept.table.deptName=部门名称
+system.dept.table.orderNum=排序
+system.dept.table.status=状态
+system.dept.table.createTime=创建时间
+system.dept.table.opra=操作
+
+# 部门管理-表单(add和edit复用)
+system.dept.add.title=新增部门
+system.dept.edit.title=修改部门
+system.dept.form.parentDept=上级部门:
+system.dept.form.deptName=部门名称:
+system.dept.form.orderNum=显示排序:
+system.dept.form.leader=负责人:
+system.dept.form.phone=联系电话:
+system.dept.form.email=邮箱:
+system.dept.form.status=部门状态:
+
+# 部门管理-验证和提示
+system.dept.validate.deptNameExists=部门已经存在
+system.dept.msg.selectParentFirst=请先添加用户所属的部门!
+system.dept.msg.parentNotAllow=父部门不能选择
+system.dept.modal.selectDept=部门选择
+
+# 部门管理-树选择页面
+system.dept.tree.title=部门树选择
+system.dept.tree.keyword=关键字:
+system.dept.tree.btn.search= 搜索 
+system.dept.tree.btn.showSearch=显示搜索
+system.dept.tree.btn.hideSearch=隐藏搜索
+system.dept.tree.expand=展开
+system.dept.tree.collapse=折叠
+
+# 参数管理-列表页面
+system.config.list.title=参数列表
+system.config.list.query.configName=参数名称:
+system.config.list.query.configKey=参数键名:
+system.config.list.query.configType=系统内置:
+system.config.list.query.createTime=创建时间:
+system.config.list.btn.refreshCache=刷新缓存
+system.config.modalName=参数
+system.config.table.configId=参数主键
+system.config.table.configName=参数名称
+system.config.table.configKey=参数键名
+system.config.table.configValue=参数键值
+system.config.table.configType=系统内置
+system.config.table.remark=备注
+system.config.table.createTime=创建时间
+system.config.table.opra=操作
+
+# 参数管理-表单(add和edit复用)
+system.config.add.title=新增参数
+system.config.edit.title=修改参数
+system.config.form.configName=参数名称:
+system.config.form.configKey=参数键名:
+system.config.form.configValue=参数键值:
+system.config.form.configType=系统内置:
+system.config.form.remark=备注:
+
+# 参数管理-验证
+system.config.validate.configKeyExists=参数键名已经存在
+
+# 菜单管理-列表页面
+system.menu.list.title=菜单列表
+system.menu.list.query.menuName=菜单名称:
+system.menu.list.query.visible=菜单状态:
+system.menu.list.btn.expandCollapse=展开/折叠
+system.menu.modalName=菜单
+system.menu.table.menuName=菜单名称
+system.menu.table.orderNum=排序
+system.menu.table.url=请求地址
+system.menu.table.menuType=类型
+system.menu.table.visible=可见
+system.menu.table.perms=权限标识
+system.menu.table.opra=操作
+system.menu.type.catalog=目录
+system.menu.type.menu=菜单
+system.menu.type.button=按钮
+
+# 菜单管理-表单(add和edit复用)
+system.menu.add.title=新增菜单
+system.menu.edit.title=修改菜单
+system.menu.form.parentMenu=上级菜单:
+system.menu.form.menuType=菜单类型:
+system.menu.form.menuName=菜单名称:
+system.menu.form.menuCode=菜单编号:
+system.menu.form.url=请求地址:
+system.menu.form.url.help=访问的请求地址,如:`/system/user`,如外网地址需内链访问则以`http(s)://`开头
+system.menu.form.target=打开方式:
+system.menu.target.tab=页签
+system.menu.target.blank=新窗口
+system.menu.form.perms=权限标识:
+system.menu.form.perms.help=控制器中定义的权限标识,如:@RequiresPermissions("")
+system.menu.form.orderNum=显示排序:
+system.menu.form.orderNum.help=数字越小越靠前
+system.menu.form.icon=图标:
+system.menu.form.icon.help=单击选择需要使用的FontAwesome图标
+system.menu.form.icon.placeholder=选择图标
+system.menu.form.visible=菜单状态:
+system.menu.form.visible.help=选择隐藏则菜单将不会出现在侧边栏,也没有权限被访问
+system.menu.form.isRefresh=是否刷新:
+system.menu.form.isRefresh.help=打开菜单选项卡是否刷新页面
+
+# 菜单管理-验证和提示
+system.menu.validate.menuNameExists=菜单已经存在
+system.menu.modal.selectMenu=菜单选择
+system.menu.msg.mainMenuNotSelect=主菜单不能选择
+
+# 菜单管理-树选择页面
+system.menu.tree.title=菜单树选择
+system.menu.tree.keyword=关键字:
+system.menu.tree.btn.search= 搜索 
+system.menu.tree.btn.showSearch=显示搜索
+system.menu.tree.btn.hideSearch=隐藏搜索
+system.menu.tree.expand=展开
+system.menu.tree.collapse=折叠
+
+# 菜单管理-图标页面
+system.menu.icon.title=Font Awesome图标列表
+
+# 通用选项
+options.yes=是
+options.no=否
+
+# 用户个人信息页面
+system.profile.title=用户个人信息
+
+# Logo区域
+system.profile.logo.title=系统logo
+system.profile.logo.edit=修改logo
+system.profile.logo.modal.title=修改自定义logo
+
+# 个人资料区域
+system.profile.info.title=个人资料
+system.profile.info.loginName=登录名称:
+system.profile.info.phone=手机号码:
+system.profile.info.dept=所属部门:
+system.profile.info.email=邮箱地址:
+system.profile.info.createTime=创建时间:
+
+# 基本资料区域
+system.profile.basic.title=基本资料
+system.profile.tab.basic=基本资料
+system.profile.tab.password=修改密码
+
+# 表单字段
+system.profile.form.userName=用户名称:
+system.profile.form.userName.placeholder=请输入用户名称
+system.profile.form.phone=手机号码:
+system.profile.form.phone.placeholder=请输入手机号码
+system.profile.form.email=邮箱:
+system.profile.form.email.placeholder=请输入邮箱
+system.profile.form.sex=性别:
+system.profile.sex.male=男
+system.profile.sex.female=女
+
+# 头像
+system.profile.avatar.edit=修改头像
+system.profile.avatar.modal.title=修改用户头像
+
+# 修改密码
+system.profile.password.old=旧密码:
+system.profile.password.old.placeholder=请输入旧密码
+system.profile.password.new=新密码:
+system.profile.password.new.placeholder=请输入新密码
+system.profile.password.confirm=确认密码:
+system.profile.password.confirm.placeholder=请确认密码
+
+# 密码规则提示
+system.profile.password.rule.number=密码只能为0-9数字
+system.profile.password.rule.letter=密码只能为a-z和A-Z字母
+system.profile.password.rule.mix=密码必须包含(字母,数字)
+system.profile.password.rule.special=密码必须包含(字母,数字,特殊字符!@#$%^&*()-=_+)
+
+# 验证消息
+system.profile.validate.userName.required=请输入用户名称
+system.profile.validate.email.required=请输入邮箱
+system.profile.validate.email.exists=Email已经存在
+system.profile.validate.phone.required=请输入手机号码
+system.profile.validate.phone.exists=手机号码已经存在
+system.profile.password.validate.old.required=请输入原密码
+system.profile.password.validate.old.error=原密码错误
+system.profile.password.validate.new.required=请输入新密码
+system.profile.password.validate.new.minlength=密码不能小于6个字符
+system.profile.password.validate.new.maxlength=密码不能大于20个字符
+system.profile.password.validate.confirm.required=请再次输入新密码
+system.profile.password.validate.confirm.equalTo=两次密码输入不一致
+
+# 上传和裁剪
+system.profile.upload.image=上传图像
+system.profile.upload.image.type.error=请选择一个图片文件。
+system.profile.cropper.loading=裁剪框加载中,请稍候...
+
+# 通用按钮
+btn.confirm=确定
+
+# 岗位管理-列表页面
+system.post.list.title=岗位列表
+system.post.list.query.postCode=岗位编码:
+system.post.list.query.postName=岗位名称:
+system.post.list.query.status=岗位状态:
+system.post.modalName=岗位
+system.post.table.postId=岗位编号
+system.post.table.postCode=岗位编码
+system.post.table.postName=岗位名称
+system.post.table.postSort=显示顺序
+system.post.table.status=状态
+system.post.table.createTime=创建时间
+system.post.table.opra=操作
+
+# 岗位管理-表单(add和edit复用)
+system.post.add.title=新增岗位
+system.post.edit.title=修改岗位
+system.post.form.postName=岗位名称:
+system.post.form.postCode=岗位编码:
+system.post.form.postSort=显示顺序:
+system.post.form.status=岗位状态:
+system.post.form.remark=备注:
+
+# 岗位管理-验证
+system.post.validate.postCodeExists=岗位编码已经存在
+system.post.validate.postNameExists=岗位名称已经存在
+
+# 字典类型管理-列表页面
+system.dict.type.list.title=字典类型列表
+system.dict.type.list.query.dictName=字典名称:
+system.dict.type.list.query.dictType=字典类型:
+system.dict.type.list.query.status=字典状态:
+system.dict.type.list.query.createTime=创建时间:
+system.dict.type.list.btn.refreshCache=刷新缓存
+system.dict.type.modalName=类型
+system.dict.type.table.dictId=字典主键
+system.dict.type.table.dictName=字典名称
+system.dict.type.table.dictType=字典类型
+system.dict.type.table.status=状态
+system.dict.type.table.remark=备注
+system.dict.type.table.createTime=创建时间
+system.dict.type.table.opra=操作
+system.dict.type.btn.list=列表
+system.dict.type.modal.dictData=字典数据
+
+# 字典类型管理-表单(add和edit复用)
+system.dict.type.add.title=新增字典类型
+system.dict.type.edit.title=修改字典类型
+system.dict.type.form.dictName=字典名称:
+system.dict.type.form.dictType=字典类型:
+system.dict.type.form.dictType.help=数据存储中的Key值,如:sys_user_sex
+system.dict.type.form.status=状态:
+system.dict.type.form.remark=备注:
+
+# 字典类型管理-验证
+system.dict.type.validate.dictTypeExists=该字典类型已经存在
+
+# 字典树选择页面
+system.dict.tree.title=字典树选择
+system.dict.tree.keyword=关键字:
+system.dict.tree.btn.search= 搜索 
+system.dict.tree.btn.showSearch=显示搜索
+system.dict.tree.btn.hideSearch=隐藏搜索
+
+# 字典数据管理-列表页面
+system.dict.data.list.title=字典数据列表
+system.dict.data.list.query.dictType=字典名称:
+system.dict.data.list.query.dictLabel=字典标签:
+system.dict.data.list.query.status=数据状态:
+system.dict.data.modalName=数据
+system.dict.data.table.dictCode=字典编码
+system.dict.data.table.dictLabel=字典标签
+system.dict.data.table.dictValue=字典键值
+system.dict.data.table.dictSort=字典排序
+system.dict.data.table.status=状态
+system.dict.data.table.remark=备注
+system.dict.data.table.createTime=创建时间
+system.dict.data.table.opra=操作
+
+# 字典数据管理-表单(add和edit复用)
+system.dict.data.add.title=新增字典数据
+system.dict.data.edit.title=修改字典数据
+system.dict.data.form.dictLabel=字典标签:
+system.dict.data.form.dictValue=字典键值:
+system.dict.data.form.dictType=字典类型:
+system.dict.data.form.cssClass=样式属性:
+system.dict.data.form.dictSort=字典排序:
+system.dict.data.form.listClass=回显样式:
+system.dict.data.form.listClass.help=table表格字典列显示样式属性
+system.dict.data.form.isDefault=系统默认:
+system.dict.data.form.status=状态:
+system.dict.data.form.remark=备注:
+
+# 回显样式选项
+system.dict.data.listClass.default=默认
+system.dict.data.listClass.primary=主要
+system.dict.data.listClass.success=成功
+system.dict.data.listClass.info=信息
+system.dict.data.listClass.warning=警告
+system.dict.data.listClass.danger=危险
+
+# 通用选项
+options.pleaseSelect=---请选择---
+
+# 缓存监控页面
+system.cache.title=缓存监控
+system.cache.list.title=缓存列表
+system.cache.keys.title=键名列表
+system.cache.value.title=缓存内容
+system.cache.table.cacheName=缓存名称
+system.cache.table.cacheKey=缓存键名
+system.cache.table.operation=操作
+system.cache.btn.clear=清空
+system.cache.btn.clearAll=清理全部
+system.cache.label.cacheName=缓存名称:
+system.cache.label.cacheKey=缓存键名:
+system.cache.label.cacheValue=缓存内容:
+system.cache.msg.refreshListSuccess=刷新缓存列表成功
+system.cache.msg.refreshKeysSuccess=刷新键名列表成功
+system.cache.msg.clearCacheNameSuccess=清理缓存[{0}]成功
+system.cache.msg.clearCacheKeySuccess=清理缓存[{0}]成功
+system.cache.msg.clearAllSuccess=清理全部缓存成功
+
+# 登录日志页面
+system.logininfor.list.title=登录日志列表
+system.logininfor.list.query.ipaddr=登录地址:
+system.logininfor.list.query.loginName=登录名称:
+system.logininfor.list.query.status=登录状态:
+system.logininfor.list.query.loginTime=登录时间:
+system.logininfor.modalName=登录日志
+system.logininfor.table.infoId=访问编号
+system.logininfor.table.loginName=登录名称
+system.logininfor.table.ipaddr=登录地址
+system.logininfor.table.loginLocation=登录地点
+system.logininfor.table.browser=浏览器
+system.logininfor.table.os=操作系统
+system.logininfor.table.status=登录状态
+system.logininfor.table.msg=操作信息
+system.logininfor.table.loginTime=登录时间
+
+# 在线用户页面
+system.online.list.title=在线用户列表
+system.online.list.query.ipaddr=登录地址:
+system.online.list.query.loginName=登录名称:
+system.online.modalName=在线用户
+system.online.table.serialNumber=序号
+system.online.table.sessionId=会话编号
+system.online.table.loginName=登录名称
+system.online.table.deptName=部门名称
+system.online.table.ipaddr=主机
+system.online.table.loginLocation=登录地点
+system.online.table.browser=浏览器
+system.online.table.os=操作系统
+system.online.table.status=会话状态
+system.online.table.startTimestamp=登录时间
+system.online.table.lastAccessTime=最后访问时间
+system.online.table.operation=操作
+system.online.status.online=在线
+system.online.status.offline=离线
+system.online.msg.confirmForceLogout=确定要强制选中用户下线吗?
+system.online.msg.selectUser=请选择要强退的用户
+system.online.msg.confirmBatchForceLogout=确认要强退选中的{0}条数据吗?
+
+# 通用按钮(补充)
+btn.clean=清空
+btn.unlock=解锁
+btn.forceLogout=强退
+
+# 操作日志详细页面
+system.operlog.detail.title=操作日志详细
+system.operlog.detail.module=操作模块:
+system.operlog.detail.loginInfo=登录信息:
+system.operlog.detail.requestUrl=请求地址:
+system.operlog.detail.costTime=耗时
+system.operlog.detail.millisecond=毫秒
+system.operlog.detail.method=操作方法:
+system.operlog.detail.operParam=请求参数:
+system.operlog.detail.jsonResult=返回参数:
+system.operlog.detail.status=状态:
+system.operlog.detail.errorMsg=异常信息:
+system.operlog.status.normal=正常
+system.operlog.status.abnormal=异常
+
+# 操作日志列表页面
+system.operlog.list.title=操作日志列表
+system.operlog.list.query.operIp=操作地址:
+system.operlog.list.query.title=系统模块:
+system.operlog.list.query.operName=操作人员:
+system.operlog.list.query.businessType=操作类型:
+system.operlog.list.query.status=操作状态:
+system.operlog.list.query.operTime=操作时间:
+system.operlog.modalName=操作日志
+system.operlog.table.operId=日志编号
+system.operlog.table.title=系统模块
+system.operlog.table.businessType=操作类型
+system.operlog.table.operName=操作人员
+system.operlog.table.deptName=部门名称
+system.operlog.table.operIp=操作地址
+system.operlog.table.operLocation=操作地点
+system.operlog.table.status=操作状态
+system.operlog.table.operTime=操作时间
+system.operlog.table.costTime=消耗时间
+system.operlog.table.operation=操作
+system.operlog.status.success=成功
+system.operlog.status.fail=失败
+system.operlog.format.costTime=%s毫秒
+
+# 服务监控页面(netdata/system_io)
+system.netdata.title=服务监控
+system.netdata.chart.system.cpu=系统整体 CPU
+system.netdata.chart.system.ram=系统整体内存
+system.netdata.chart.system.io=系统磁盘 I/O
+system.netdata.chart.net.enp4s0=网卡流量
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 内存
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui 内存
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH 内存
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL 内存
+system.netdata.loading=加载中...
+system.netdata.status.offline=离线
+system.netdata.status.online=在线
+system.netdata.status.error=连接失败
+system.netdata.time.5min=5分钟
+system.netdata.time.15min=15分钟
+system.netdata.time.1hour=1小时
+system.netdata.time.24hour=24小时
+system.netdata.time.3day=3天
+system.netdata.btn.refresh=刷新
+system.netdata.stat.current=当前值
+system.netdata.stat.avg=平均值
+system.netdata.stat.max=最大值
+system.netdata.stat.min=最小值
+system.netdata.yaxis.cpu=CPU 使用率 (%)
+system.netdata.yaxis.mem=内存使用 (MiB)
+system.netdata.yaxis.io=磁盘 I/O (KiB/s)
+system.netdata.yaxis.net=网络流量 (kilobits/s)
+system.netdata.error.noData=无数据返回
+system.netdata.error.prefix=错误:
+
+# 服务器监控页面
+system.server.title=服务器监控
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=核心数
+system.server.cpu.used=用户使用率
+system.server.cpu.sys=系统使用率
+system.server.cpu.free=当前空闲率
+system.server.mem.title=内存
+system.server.mem.memory=内存
+system.server.mem.jvm=JVM
+system.server.mem.total=总内存
+system.server.mem.used=已用内存
+system.server.mem.free=剩余内存
+system.server.mem.usage=使用率
+system.server.sys.title=服务器信息
+system.server.sys.computerName=服务器名称
+system.server.sys.osName=操作系统
+system.server.sys.computerIp=服务器IP
+system.server.sys.osArch=系统架构
+system.server.sys.userDir=项目路径
+system.server.jvm.title=Java虚拟机信息
+system.server.jvm.name=Java名称
+system.server.jvm.version=Java版本
+system.server.jvm.startTime=启动时间
+system.server.jvm.runTime=运行时长
+system.server.jvm.home=安装路径
+system.server.jvm.inputArgs=运行参数
+system.server.disk.title=磁盘状态
+system.server.disk.dirName=盘符路径
+system.server.disk.sysTypeName=文件系统
+system.server.disk.typeName=盘符类型
+system.server.disk.total=总大小
+system.server.disk.free=可用大小
+system.server.disk.used=已用大小
+system.server.disk.usage=已用百分比
+system.server.table.property=属性
+system.server.table.value=值
+system.server.unit.count=个
+
+# 通知公告新增页面
+system.notice.add.title=新增通知公告
+system.notice.placeholder.noticeContent=请输入公告内容
+system.notice.msg.uploadFail=图片上传失败。
+
+# 通知公告修改页面
+system.notice.edit.title=修改通知公告
+
+# 通知公告列表页面
+system.notice.list.title=通知公告列表
+system.notice.list.query.noticeTitle=公告标题:
+system.notice.list.query.createBy=操作人员:
+system.notice.list.query.noticeType=公告类型:
+system.notice.modalName=公告
+system.notice.table.noticeId=序号
+system.notice.table.noticeTitle=公告标题
+system.notice.table.noticeType=公告类型
+system.notice.table.status=状态
+system.notice.table.createBy=创建者
+system.notice.table.createTime=创建时间
+system.notice.table.operation=操作
+
+# 通知公告表单标签
+system.notice.label.noticeTitle=公告标题:
+system.notice.label.noticeType=公告类型:
+system.notice.label.noticeContent=公告内容:
+system.notice.label.status=公告状态:
+
+# 通知公告查看页面
+system.notice.view.title=公告详细
+system.notice.view.sendTime=发送时间
+system.notice.view.sender=发件人
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=ASR语言
+callTask.form.asrLanguageCode.empty=请选择ASR语言
+callTask.form.ttsLanguageCode=TTS语言
+callTask.form.ttsLanguageCode.empty=请选择TTS语言
+inboundllm.form.asrLanguageCode=ASR语言
+inboundllm.form.asrLanguageCode.empty=请选择ASR语言
+inboundllm.form.ttsLanguageCode=TTS语言
+inboundllm.form.ttsLanguageCode.empty=请选择TTS语言
+ivr.form.asrLanguageCode=ASR语言
+ivr.form.asrLanguageCode.empty=请选择ASR语言
+ivr.form.ttsLanguageCode=TTS语言
+ivr.form.ttsLanguageCode.empty=请选择TTS语言
+
+# sys_config数据
+_sys.config.sys.index.skinName=主框架页-默认皮肤样式名称
+_sys.config.sys.user.initPassword=用户管理-账号初始密码
+_sys.config.sys.index.sideTheme=主框架页-侧边栏主题
+_sys.config.sys.account.registerUser=账号自助-是否开启用户注册功能
+_sys.config.sys.account.chrtype=用户管理-密码字符范围
+_sys.config.sys.account.initPasswordModify=用户管理-初始密码修改策略
+_sys.config.sys.account.passwordValidateDays=用户管理-账号密码更新周期
+_sys.config.sys.index.menuStyle=主框架页-菜单导航显示风格
+_sys.config.sys.index.footer=主框架页-是否开启页脚
+_sys.config.sys.index.tagsView=主框架页-是否开启页签
+_sys.config.sys.login.blackIPList=用户登录-黑名单列表
+_sys.config.sys.version=系统版本号
+_sys.config.sys.logo=系统默认logo
+_sys.config.config_asr_provider_aliyun=阿里云
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=电信
+_sys.config.config_asr_provider_aws=亚马逊
+_sys.config.config_tts_provider_aliyun=阿里云
+_sys.config.config_tts_provider_doubao=豆包
+_sys.config.config_tts_provider_chinatelecom=电信
+_sys.config.config_tts_provider_aws=亚马逊
+_sys.config.config_tts_language_aliyun_tts=阿里云tts语言设置
+_sys.config.config_tts_language_doubao_vcl_tts=豆包tts语言设置
+_sys.config.config_tts_language_aws_tts=亚马逊tts语言设置
+_sys.config.config_asr_language_aliyun=阿里云asr语言设置
+_sys.config.config_asr_language_aws=亚马逊asr语言设置
+
+# 字典类型
+_sys.dict.type.sys_user_sex=用户性别
+_sys.dict.type.sys_show_hide=菜单状态
+_sys.dict.type.sys_normal_disable=系统开关
+_sys.dict.type.sys_job_status=任务状态
+_sys.dict.type.sys_job_group=任务分组
+_sys.dict.type.sys_yes_no=系统是否
+_sys.dict.type.sys_notice_type=通知类型
+_sys.dict.type.sys_notice_status=通知状态
+_sys.dict.type.sys_oper_type=操作类型
+_sys.dict.type.sys_common_status=系统状态
+
+# 字典值
+_sys.dict.data.label.sys_user_sex.0=男
+_sys.dict.data.label.sys_user_sex.1=女
+_sys.dict.data.label.sys_user_sex.2=未知
+_sys.dict.data.label.sys_show_hide.0=显示
+_sys.dict.data.label.sys_show_hide.1=隐藏
+_sys.dict.data.label.sys_normal_disable.0=正常
+_sys.dict.data.label.sys_normal_disable.1=停用
+_sys.dict.data.label.sys_job_status.0=正常
+_sys.dict.data.label.sys_job_status.1=暂停
+_sys.dict.data.label.sys_job_group.DEFAULT=默认
+_sys.dict.data.label.sys_job_group.SYSTEM=系统
+_sys.dict.data.label.sys_yes_no.Y=是
+_sys.dict.data.label.sys_yes_no.N=否
+_sys.dict.data.label.sys_notice_type.1=通知
+_sys.dict.data.label.sys_notice_type.2=公告
+_sys.dict.data.label.sys_notice_status.0=正常
+_sys.dict.data.label.sys_notice_status.1=关闭
+_sys.dict.data.label.sys_oper_type.0=其他
+_sys.dict.data.label.sys_oper_type.1=新增
+_sys.dict.data.label.sys_oper_type.2=修改
+_sys.dict.data.label.sys_oper_type.3=删除
+_sys.dict.data.label.sys_oper_type.4=授权
+_sys.dict.data.label.sys_oper_type.5=导出
+_sys.dict.data.label.sys_oper_type.6=导入
+_sys.dict.data.label.sys_oper_type.7=强退
+_sys.dict.data.label.sys_oper_type.8=生成代码
+_sys.dict.data.label.sys_oper_type.9=清空数据
+_sys.dict.data.label.sys_common_status.0=成功
+_sys.dict.data.label.sys_common_status.1=失败
+
+# 系统参数
+_cc.params.phone_encrypted_key=电话工具条-外呼电话的解密key
+_cc.params.recording_path=录音保存路径
+_cc.params.enable_cc_record_stereo=是否开启立体音
+_cc.params.post_cdr_url=话单推送接口
+_cc.params.recordings_extension=录音类型
+_cc.params.conference_video_templates=可用的会议布局
+_cc.params.conference_gateway_addr=电话视频会议网关地址
+_cc.params.conference_recording_path=视频会议录音/录像保存路径
+_cc.params.conference_gateway_caller=电话视频会议主叫
+_cc.params.video_level_id_list=视频清晰度列表
+_cc.params.conference_outboud_profile=电话视频会议外呼的profile
+_cc.params.conference_video_layouts=电话视频会议的布局列表
+_cc.params.wait-for-second-vad-during-interrupt=是否开启二次打断设置(首次讲话不立即响应,等待客户第二次说话)
+_cc.params.outbound-call-extra-params-for-profile-internal2=外呼时携带的额外变量参数(用户控制呼叫行为及设置特性等)
+_cc.params.call_monitor_enabled=是否启用通话监听
+_cc.params.fs_log_file_path=FreeSWITCH日志路径
+_cc.params.cc_log_file_path=call-center日志路径
+_cc.params.fs_call_asr_enabled=是否启动双向asr语音识别并推送结果
+_cc.params.fs_call_asr_engine=双向asr语音识别,使用哪个asr引擎(chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=是否启用呼入电话排队监控
+_cc.params.fs_docker_container_name=Freeswitch的docker容器名称
+_cc.params.fs_conf_directory=Freeswitch配置文件路径
+_cc.params.robot-asr-type=语音识别类型; mrcp、websocket
+_cc.params.fs-asr-mrcp-param=mrcp 语音识别请求参数
+_cc.params.robot-max-no-speak-time=机器人通话中,最大客户静默时长; 客户超时不讲话通话会被自动掐断
+_cc.params.max-call-concurrency=呼入通话最大并发数限制
+_cc.params.asr-pause-enabled=asr按需识别,仅在机器话术播报完毕后才启动
+_cc.params.max-wait-time-after-vad-start=检测到客户说话开始后,等待说话结束的最大时长;秒
+_cc.params.inbound-transfer-agent-timeout=呼入电话转接坐席超时时间; 秒
+_cc.params.hide-inbound-number=转接到坐席时,隐藏呼入号码
+_cc.params.inbound-play-opnum=转接到坐席时,是否播报工号
+_cc.params.event-socket-ip=event socket ip address
+_cc.params.event-socket-port=event socket port
+_cc.params.event-socket-pass=event socket password
+_cc.params.event-socket-conn-pool-size=event socket connection pool size
+_cc.params.max-agent-number=最大坐席人数
+_cc.params.ws-server-auth-token-secret=解密并验证token的Key
+_cc.params.ws-server-port=(websocket server) 电话工具条端口
+_cc.params.vad-intelligent-wait=是否开启vad智能等待
+_cc.params.vad-intelligent-wait-ms=vad智能等待的毫秒数
+_cc.params.call-center-server-port=呼叫中心WebAPI的 server-port (只读)
+_cc.params.fs-deploy-type=FreeSWITCH 部署方式; docker/native
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH native部署方式的启动脚本路径
+_cc.params.fs-root-directory=FreeSWITCH 程序根目录
+_cc.params.call-center-server-ip-addr=服务器对外暴露的IP地址
+_cc.params.call-center-websocket-port=呼叫中心电话工具条端口
+_cc.params.tts_content_variables=话术中涉及到的变量
+_cc.params.call-center-api-token=语音通知的访问Token
+_cc.params.outbound-max-line-number=全局参数; 可用的最大外呼并发数
+_cc.params.outbound-enable-prediction-algorithm=群呼转人工坐席的场景下,是否开启预测外呼算法
+_cc.params.aliyun-tts-account-json=阿里云tts账号参数json
+_cc.params.empty-number-detection-enabled=空号识别功能是否开启
+_cc.params.empty-number-detection-config=空号识别定义
+_cc.params.default_interrupt_ignore_keywords=打断忽略关键字列表默认值
+_cc.params.llm-max-try=连接大模型的最大尝试次数
+_cc.params.llm-conn-timeout=连接大模型的超时时间; 毫秒
+_cc.params.llm-max-try-fail-tips=连接大模型的失败提示语
+_cc.params.api-client-white-ips=允许访问接口的ip白名单
+_cc.params.price_aliyun_asr=阿里云asr单价(每小时)
+_cc.params.price_aliyun_tts=阿里云tts单价(每千次)
+_cc.params.price_aliyun_tts_flow=阿里云声音克隆单价(每万字符)
+_cc.params.price_llm_token=大模型单价(每千token)
+_cc.params.doubao-tts-account-json=豆包tts账号参数json
+_cc.params.system_license_info=system license info
+_cc.params.system_hw_fingerprint_cmd=system_hw_fingerprint_cmd
+_cc.params.fs-inbound-acl-enabled=是否启用freeswitch的呼入白名单防护
+_cc.params.freeswitch_root=freeswitch根目录
+_cc.params.callTask_allowDel_days=间隔多少天允许删除任务及通话记录
+_cc.params.firewalld-config-path=firewalld 防火墙配置文件路径
+_cc.params.firewalld-restart-cmd=重启firewalld防火墙的命令
+_cc.params.firewalld-enabled=是否启用firewalld防护墙
+_cc.params.fs-inbound-allow-ip-list=呼入ip白名单(用于外部线路呼入)
+_cc.params.fs-register-allow-ip-list=分机注册的白名单 
+_cc.params.fs-register-acl-enabled=是否启用freeswitch的分机注册防护
+_cc.params.max-wait-time-customer-speaking=客户未说话静默时长
+
+# 新增功能
+_menu.firewalld=防火墙管理
+_menu.awsAsrConf=亚马逊ASR配置
+_menu.awsTtsConf=亚马逊TTS配置
+_menu.deepgramAsrConf=Deepgram ASR配置
+_menu.deepgramTtsConf=Deepgram TTS配置
+
+# 防火墙管理
+firewallRule.add.title=新增防火墙规则配置
+firewallRule.edit.title=修改防火墙规则配置
+firewallRule.notice.title=温馨提示
+firewallRule.notice.freeswitch=无需添加 Freeswitch 相关端口
+firewallRule.notice.ssh=无需添加 SSH 相关端口
+firewallRule.form.protocol=协议
+firewallRule.form.portStart=起始端口
+firewallRule.form.portEnd=结束端口
+firewallRule.form.enableFromSource=启用来源限制
+firewallRule.form.fromSource=来源地址
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=是
+firewallRule.boolean.no=否
+firewallRule.placeholder.portStart=请输入起始端口号(1-65535)
+firewallRule.placeholder.portEnd=请输入结束端口号(1-65535)
+firewallRule.placeholder.fromSource=请输入来源IP地址或网段
+firewallRule.validation.integer=请输入整数
+firewallRule.validation.portRange=起始端口必须小于等于结束端口
+firewallRule.validation.portStart.required=请输入起始端口
+firewallRule.validation.portEnd.required=请输入结束端口
+firewallRule.validation.port.range=端口范围必须在 1-65535 之间
+
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=预占
+_phoneBar.agentStatus.text_6=多方会议
+_phoneBar.agentStatus.text_5=话后整理
+_phoneBar.agentStatus.text_4=通话中
+_phoneBar.agentStatus.text_33=培训
+_phoneBar.agentStatus.text_32=会议
+_phoneBar.agentStatus.text_31=小休
+_phoneBar.agentStatus.text_3=置忙
+_phoneBar.agentStatus.text_2=置闲
+_phoneBar.agentStatus.text_1=已签入
+phonebar.msg.error_login_token=电话工具条:无法获取 loginToken!
+phonebar.msg.error_wss_domain=ERROR! 启用了wss之后,必须使用域名访问websocketServer! 
+phonebar.msg.websocket_not_supported=您的浏览器不支持websocket,您无法使用本页面的功能!
+phonebar.msg.hold_call_hangup=保持的通话已挂机.
+phonebar.msg.call_waiting=客户电话等待中.
+phonebar.msg.call_wait_retrieved=等待的电话已接回.
+phonebar.msg.consultation_started=咨询已开始.
+phonebar.msg.consultation_ended=咨询已结束.
+phonebar.msg.waiting_call_hangup=等待的客户已挂机.
+phonebar.msg.ipcc_disconnected=ipccserver 连接断开.
+phonebar.msg.select_group=请选择业务组!
+phonebar.msg.select_agent=请选择要咨询的坐席成员!
+phonebar.msg.select_free_agent=请选择空闲的坐席成员!
+phonebar.msg.cannot_consult_self=不能咨询自己,请选择其他坐席成员!
+phonebar.msg.select_transfer_group=请选择转接的业务组!
+phonebar.msg.select_transfer_agent=请选择转接的坐席成员!
+phonebar.msg.cannot_transfer_self=不能转给自己,请选择其他坐席成员!
+phonebar.msg.please_login=请先上线.
+phonebar.msg.no_call=当前没有通话.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=cant not send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=请输入外呼号码!
+phonebar.msg.invalid_phone=请输入正确格式的外呼号码!
+phonebar.msg.agent_locked=坐席已锁定
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false , 已禁用默认ui工具条按钮.
+phonebar.msg.logout_not_allowed=当前不允许签出!
+phonebar.msg.confirm_close=关闭网页将导致您无法接听电话,确定要关闭吗 ?
+phonebar.msg.esc_hangup=按下了ESC键, 即将发送挂机指令.
+phonebar.msg.confirm_transfer_conference=是否把当前通话转换为会议 ?
+phonebar.msg.enter_member_name=请填写参会者姓名!
+phonebar.msg.enter_member_phone=请填写参会者手机号!
+phonebar.msg.member_exists=会议成员已经存在,请不要重复添加!
+phonebar.msg.start_conference=正常发起多方通话
+phonebar.msg.provide_call_id=请提供待监听电话的 callId !
+phonebar.status.free=空闲
+phonebar.status.about_to_call=即将呼叫
+phonebar.status.in_call=通话中
+page.title=我的工作台
+phonebar.conference.label.type=会议类型
+phonebar.conference.option.video=视频
+phonebar.conference.option.audio=音频
+phonebar.conference.btn.start=启动会议
+phonebar.conference.btn.end=结束会议
+phonebar.conference.btn.join=加入会议
+phonebar.conference.btn.remove=移除
+phonebar.conference.btn.reinvite=重呼
+phonebar.conference.placeholder.name=姓名
+phonebar.conference.placeholder.phone=手机号
+phonebar.conference.alt.mute=禁言该成员
+phonebar.conference.alt.vmute=关闭该成员的视频
+phonebar.conference.title.remove=踢除会议成员
+phonebar.conference.title.reinvite=重新呼叫
+phonebar.transfer.label.group=业务组
+phonebar.transfer.label.member=坐席成员
+phonebar.transfer.option.select=请选择
+phonebar.transfer.placeholder.phone=电话号码
+phonebar.transfer.title.externalPhone=可以把当前通话转接到外线号码上。如果该文本框留空,则忽略处理。
+phonebar.transfer.btn.transfer=转接电话
+phonebar.transfer.title.transfer=把当前电话转接给他/她处理。
+phonebar.transfer.btn.retrieve=接回客户
+phonebar.transfer.title.retrieve=在咨询失败的情况下使用该按钮,接回处于等待中的电话。
+phonebar.transfer.btn.transferCall=转接客户
+phonebar.transfer.title.transferCall=在咨询成功的情况下使用该按钮,把电话转接给专家坐席。
+phonebar.transfer.btn.consult=拨号咨询
+phonebar.transfer.title.consult=拨号咨询
+phonebar.transfer.status.login=刚签入
+phonebar.transfer.status.free=空闲
+phonebar.transfer.status.busy=忙碌
+phonebar.transfer.status.calling=通话中
+phonebar.transfer.status.after=事后处理
+phonebar.popup.inbound=呼入弹屏
+phonebar.popup.outbound=外呼弹屏
+phonebar.chat.dialog.end=对话已结束。
+phonebar.chat.role.customer=客户
+phonebar.chat.role.agent=我
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS模型
+inboundllm.form.ttsModels.empty=请选择语音合成模型
+inboundllm.form.asrModels=ASR模型
+inboundllm.form.asrModels.empty=请选择语音识别模型
+callTask.form.ttsModels=TTS模型
+callTask.form.ttsModels.empty=请选择语音合成模型
+callTask.form.asrModels=ASR模型
+callTask.form.asrModels.empty=请选择语音识别模型
+ivr.form.ttsModels=TTS模型
+ivr.form.ttsModels.empty=请选择语音合成模型

+ 1032 - 1
ruoyi-admin/src/main/resources/static/i18n/messages_zh_CN.properties

@@ -4,6 +4,9 @@ sys.sysName=呼叫管理系统
 ## 弹框提示
 tips.defaultPass=您的密码还是初始密码,请修改密码!
 tips.security.title=安全提示
+tips.role.cust=客户
+tips.role.agent=坐席
+tips.role.kb=知识库查询
 ## 按钮
 btn.ok=确定
 btn.cancel=取消
@@ -56,7 +59,39 @@ user.login.title=登录呼叫管理系统
 user.login.welcome=欢迎使用
 
 # common
-common.adminPage.upload.requiredTips = 上传的文件不能为空
+common.adminPage.upload.requiredTips=上传的文件不能为空
+common.tip.selected.empty=请至少选择一条记录
+common.tip.del.confirm=确认要删除选中的数据吗?
+common.tip.remove.confirm=确认要删除该条数据吗?
+common.tip.clear.confirm=确定清空所有数据吗?
+common.tip.loading=正在处理中,请稍候...
+common.tip.export.confirm="确定导出所有数据吗?"
+common.tip.success=操作成功
+common.tip.fail=操作失败
+common.table.search=搜索
+common.table.col=列
+common.table.refresh=刷新
+common.tip.upload.success=文件导入成功!
+common.tip.upload.fail=文件导入失败,请检查文件格式是否正确!
+common.msg.callRecords.del.confirm=确定要删除该外呼任务和关联的通话记录吗?
+common.msg.callRecords.del.warning=删除后数据将无法恢复,请谨慎操作!
+common.tip.del.success=删除成功!
+common.tip.del.fail=删除失败,请稍后重试!
+menuTab.close_current=关闭当前
+menuTab.close_other=关闭其他
+menuTab.close_left=关闭左侧
+menuTab.close_right=关闭右侧
+menuTab.close_all=全部关闭
+menuTab.full=全屏显示
+menuTab.refresh=刷新页面
+menuTab.open=新窗口打开
+common.msg.copy.success=已复制到剪贴板
+common.msg.copy.fail=复制失败,请手动复制
+common.msg.unknown.media=不支持的媒体格式
+common.msg.file.notexists=文件不存在!
+common.time.hour=时
+common.time.minute=分
+common.time.second=秒
 
 # 电话工具条
 phonebar.placeholder.phonenum=请输入电话号码
@@ -106,6 +141,12 @@ phonebar.msg.free=空闲
 phonebar.msg.busy=忙碌
 phonebar.msg.customer_channel_hold=通话已保持
 phonebar.msg.customer_channel_unhold=通话已接回
+phonebar.msg.inner_consultation_start=咨询开始.
+phonebar.msg.inner_consultation_stop=咨询结束.
+phonebar.msg.transfer_call_success=电话转接成功.
+phonebar.msg.conferenceEnd=多方通话结束
+phonebar.msg.conferenceStart=多方通话进行中
+phonebar.msg.transferToConferenceSuccess=已接入多方会议
 phonebar.label.loginTime=签入时间:
 phonebar.label.agentStatus=状态:
 phonebar.label.queueStat=当前排队人数:
@@ -149,6 +190,17 @@ params.manage.form.paramType=参数类型:
 params.manage.form.title.add=新增callcenter参数配置
 params.manage.form.title.edit=修改callcenter参数配置
 
+# firewalld管理页面
+firewalld.manage.query.protocol=协议类型
+firewalld.manage.query.portStart=起始端口
+firewalld.manage.table.portEnd=结束端口
+firewalld.manage.table.enableFromSource=仅允许来源IP
+firewalld.manage.table.fromSource=来源IP
+firewalld.manage.table.opra=操作
+firewalld.manage.table.modalName=防火墙规则配置
+firewalld.manage.form.title.add=新增防火墙规则
+firewalld.manage.form.title.edit=修改防火墙规则
+
 # freeswitch配置页面
 switchconf.label.baseConfigs=基础配置
 switchconf.label.more=更多
@@ -175,6 +227,10 @@ switchconf.cert.label=证书内容:
 switchconf.log.fs.label=freeswitch日志:
 switchconf.log.cc.label=callcenter日志:
 switchconf.log.error.label=错误日志:
+switchconf.asr.aws.header=亚马逊ASR参数配置
+switchconf.tts.aws.header=亚马逊tts参数配置
+switchconf.asr.deepgram.header=Deepgram ASR参数配置
+switchconf.tts.deepgram.header=Deepgram TTS参数配置
 
 # 业务组
 bizgroup.query.bizGroupName=业务组名称:
@@ -918,6 +974,7 @@ ivr.table.ttsText=话术内容
 ivr.table.action=处理方式
 ivr.table.opra=操作
 ivr.form.pressKeyInvalidTips.default=输入错误,请重新输入
+ivr.apply.confirmMessage=不要再外呼作业及呼入高峰期间,执行应用设置。否则会导致电话异常。
 
 callTask.form.taskType3=IVR外呼
 callTask.form.ivrId=IVR方案
@@ -931,6 +988,40 @@ ivr.form.hangupTips=挂机提示:
 ivr.form.aiInboundId=AI客服:
 ivr.form.aiInboundId.empty=请选择AI客服
 ivr.form.action.ai=转接到AI客服
+ivr.form.placeholder.ttsText=输入TTS文本或上传音频文件
+ivr.form.btn.title.tts=TTS合成
+ivr.form.btn.tts=合成
+ivr.form.btn.title.upload=上传WAV音频文件
+ivr.form.btn.upload=上传
+ivr.form.tips.helpblock=支持直接输入文本或上传WAV格式音频文件。输入//可调出变量列表
+ivr.form.title.ttsAudioPreview=音频预览
+ivr.form.placeholder.hangupTips=输入挂机提示文本或上传音频文件
+ivr.form.placeholder.pressKeyInvalidTips=输入挂机提示文本或上传音频文件
+ivr.form.tips.digitRange=请输入有效的正则表达式,用于验证用户输入格式
+ivr.validator.msg.checkDigit=该按键值在同一层级下已存在,请使用其他按键
+ivr.validator.msg.minLenCheck=最小长度不能大于最大长度
+ivr.validator.msg.maxLenCheck=最大长度不能小于最小长度
+ivr.validator.msg.validRegex=请输入有效的正则表达式格式
+ivr.edit.title=修改IVR配置
+ivr.add.title=新增IVR配置
+ivr.variableSelector.title=选择变量
+ivr.variableSelector.tip=点击插入
+ivr.modal.upload.title=上传音频文件
+ivr.modal.upload.label=选择WAV文件:
+ivr.modal.upload.helpblock=仅支持WAV格式,最大50MB
+ivr.modal.upload.progress=上传进度:
+ivr.modal.upload.confirm=确认上传
+ivr.msg.selectFile=请先选择文件!
+ivr.msg.wavOnly=请选择 WAV 格式的音频文件!
+ivr.msg.fileSizeLimit=文件大小不能超过 50MB!
+ivr.msg.uploadSuccess=上传成功!
+ivr.msg.uploadFail=上传失败:
+ivr.msg.networkError=网络错误:
+ivr.msg.ttsTextRequired=请输入需要合成的文本内容!
+ivr.msg.ttsSynthesizing=正在合成音频...
+ivr.msg.ttsSuccess=TTS合成成功!
+ivr.msg.ttsDataError=TTS合成返回数据格式错误!
+ivr.msg.ttsFail=TTS合成失败
 
 # 反向注册功能支持
 gateways.table.label.register2=反向注册模式
@@ -1061,4 +1152,944 @@ kbcontent.table.opra=操作
 kbcontent.form.title=标题:
 kbcontent.form.content=内容:
 
+# 用户管理
+system.user.list.boxTitle=组织机构
+system.user.list.btn.Dept=管理部门
+system.user.list.btn.Expand=展开
+system.user.list.btn.Collapse=折叠
+system.user.list.btn.Refresh=刷新部门
+system.user.list.query.loginName=登录账号:
+system.user.list.query.phonenumber=手机号码:
+system.user.list.query.status=用户状态:
+system.user.list.query.createName=创建时间:
+system.user.modalName=用户
+system.user.table.userId=用户
+system.user.table.loginName=登录账号
+system.user.table.userName=用户姓名
+system.user.table.deptName=部门
+system.user.table.email=邮箱
+system.user.table.phonenumber=手机
+system.user.table.userstatus=用户状态
+system.user.table.createTime=创建时间
+system.user.table.opra=操作
+system.user.table.btn.more=更多操作
+system.user.table.btn.resetPass=重置密码
+system.user.table.btn.authRole=分配角色
+system.user.form.head.base=基本信息
+system.user.form.loginName=登录账号:
+system.user.form.placeholder.loginName=请输入登录账号
+system.user.form.password=登录密码:
+system.user.form.placeholder.password=请输入登录密码
+system.user.form.tips.password=登录密码,鼠标按下显示密码
+system.user.form.userName=用户姓名:
+system.user.form.placeholder.userName=请输入用户姓名
+system.user.form.deptName=归属部门:
+system.user.form.placeholder.deptName=请选择归属部门
+system.user.form.phonenumber=手机号码:
+system.user.form.placeholder.phonenumber=请输入手机号码
+system.user.form.email=邮箱:
+system.user.form.placeholder.email=请输入邮箱
+system.user.form.sex=用户性别:
+system.user.form.status=用户状态:
+system.user.form.post=岗位:
+system.user.form.role=角色:
+system.user.form.head.other=其他信息
+system.user.form.remark=备注:
+system.user.form.head.authRole=分配角色
+system.user.form.loginDate=最后登录时间
+system.user.form.loginIp=最后登录IP:
+system.user.form.updateTime=更新时间:
+system.user.form.updateBy=更新者:
+system.user.form.createTime=创建时间:
+system.user.form.createBy=创建者:
+system.user.authrole.table.roleId=角色编号
+system.user.authrole.table.roleSort=排序
+system.user.authrole.table.roleName=角色名称
+system.user.authrole.table.roleKey=权限字符
+system.user.authrole.table.createTime=创建时间
+system.user.resetpwd.password=输入密码:
+system.user.resetpwd.placeholder.password=请输入重置密码
+system.user.resetpwd.tips.password=重置密码,鼠标按下显示密码
+system.user.tips.disable=确认要停用用户吗?
+system.user.tips.enable=确认要启用用户吗?
+
+# 角色管理页面
+system.role.list.query.roleName=角色名称:
+system.role.list.query.roleKey=权限字符:
+system.role.list.query.status=角色状态:
+system.role.list.query.createTime=创建时间:
+system.role.modalName=角色
+system.role.table.roleId=角色编号
+system.role.table.roleName=角色名称
+system.role.table.roleKey=权限字符
+system.role.table.dataScope=数据权限
+system.role.table.roleSort=显示顺序
+system.role.table.roleStatus=角色状态
+system.role.table.createTime=创建时间
+system.role.table.opra=操作
+system.role.dataScope.all=全部数据权限
+system.role.dataScope.custom=自定义数据权限
+system.role.dataScope.dept=本部门数据权限
+system.role.dataScope.deptAndChild=本部门及以下数据权限
+system.role.dataScope.self=仅本人数据权限
+system.role.table.btn.dataScope=数据权限
+system.role.table.btn.authUser=分配用户
+system.role.table.btn.more=更多操作
+system.role.modal.dataScope=分配数据权限
+system.role.modal.authUser=分配用户
+system.role.confirm.disable=确认要停用角色吗?
+system.role.confirm.enable=确认要启用角色吗?
+
+# 角色管理-新增/修改页面
+system.role.add.title=新增角色
+system.role.edit.title=修改角色
+system.role.add.roleName=角色名称:
+system.role.add.roleKey=权限字符:
+system.role.add.roleKey.help=控制器中定义的权限字符,如:@RequiresRoles("")
+system.role.add.roleSort=显示顺序:
+system.role.add.status=状态:
+system.role.add.remark=备注:
+system.role.add.menuPermission=菜单权限:
+system.role.add.expandCollapse=展开/折叠
+system.role.add.selectAll=全选/全不选
+system.role.add.parentChildLink=父子联动
+system.role.add.validate.roleNameExists=角色名称已经存在
+system.role.add.validate.roleKeyExists=角色权限已经存在
+
+# 角色管理-分配用户页面
+system.role.authUser.title=角色分配用户
+system.role.authUser.query.loginName=登录名称:
+system.role.authUser.query.phonenumber=手机号码:
+system.role.authUser.btn.addUser=添加用户
+system.role.authUser.btn.cancelAuthBatch=批量取消授权
+system.role.authUser.btn.cancelAuth=取消授权
+system.role.authUser.table.userId=用户ID
+system.role.authUser.table.loginName=登录名称
+system.role.authUser.table.userName=用户名称
+system.role.authUser.table.email=邮箱
+system.role.authUser.table.phonenumber=手机
+system.role.authUser.table.status=用户状态
+system.role.authUser.table.createTime=创建时间
+system.role.authUser.table.opra=操作
+system.role.authUser.modal.selectUser=选择用户
+system.role.authUser.msg.selectOne=请至少选择一条记录
+system.role.authUser.confirm.cancelBatch=确认要删除选中的{0}条数据吗?
+system.role.authUser.confirm.cancelAuth=确认要取消该用户角色吗?
+
+# 角色管理-数据权限页面
+system.role.dataScope.title=角色数据权限
+system.role.dataScope.dataScope=数据范围:
+system.role.dataScope.help=特殊情况下,设置为"自定数据权限"
+system.role.dataScope.dataPermission=数据权限:
+
+# 角色管理-选择用户页面
+system.role.selectUser.title=分配角色选择用户
+
+# 部门管理-列表页面
+system.dept.list.title=部门列表
+system.dept.list.query.deptName=部门名称:
+system.dept.list.query.status=部门状态:
+system.dept.list.btn.expandCollapse=展开/折叠
+system.dept.modalName=部门
+system.dept.table.deptName=部门名称
+system.dept.table.orderNum=排序
+system.dept.table.status=状态
+system.dept.table.createTime=创建时间
+system.dept.table.opra=操作
+
+# 部门管理-表单(add和edit复用)
+system.dept.add.title=新增部门
+system.dept.edit.title=修改部门
+system.dept.form.parentDept=上级部门:
+system.dept.form.deptName=部门名称:
+system.dept.form.orderNum=显示排序:
+system.dept.form.leader=负责人:
+system.dept.form.phone=联系电话:
+system.dept.form.email=邮箱:
+system.dept.form.status=部门状态:
+
+# 部门管理-验证和提示
+system.dept.validate.deptNameExists=部门已经存在
+system.dept.msg.selectParentFirst=请先添加用户所属的部门!
+system.dept.msg.parentNotAllow=父部门不能选择
+system.dept.modal.selectDept=部门选择
+
+# 部门管理-树选择页面
+system.dept.tree.title=部门树选择
+system.dept.tree.keyword=关键字:
+system.dept.tree.btn.search= 搜索 
+system.dept.tree.btn.showSearch=显示搜索
+system.dept.tree.btn.hideSearch=隐藏搜索
+system.dept.tree.expand=展开
+system.dept.tree.collapse=折叠
+
+# 参数管理-列表页面
+system.config.list.title=参数列表
+system.config.list.query.configName=参数名称:
+system.config.list.query.configKey=参数键名:
+system.config.list.query.configType=系统内置:
+system.config.list.query.createTime=创建时间:
+system.config.list.btn.refreshCache=刷新缓存
+system.config.modalName=参数
+system.config.table.configId=参数主键
+system.config.table.configName=参数名称
+system.config.table.configKey=参数键名
+system.config.table.configValue=参数键值
+system.config.table.configType=系统内置
+system.config.table.remark=备注
+system.config.table.createTime=创建时间
+system.config.table.opra=操作
+
+# 参数管理-表单(add和edit复用)
+system.config.add.title=新增参数
+system.config.edit.title=修改参数
+system.config.form.configName=参数名称:
+system.config.form.configKey=参数键名:
+system.config.form.configValue=参数键值:
+system.config.form.configType=系统内置:
+system.config.form.remark=备注:
+
+# 参数管理-验证
+system.config.validate.configKeyExists=参数键名已经存在
+
+# 菜单管理-列表页面
+system.menu.list.title=菜单列表
+system.menu.list.query.menuName=菜单名称:
+system.menu.list.query.visible=菜单状态:
+system.menu.list.btn.expandCollapse=展开/折叠
+system.menu.modalName=菜单
+system.menu.table.menuName=菜单名称
+system.menu.table.orderNum=排序
+system.menu.table.url=请求地址
+system.menu.table.menuType=类型
+system.menu.table.visible=可见
+system.menu.table.perms=权限标识
+system.menu.table.opra=操作
+system.menu.type.catalog=目录
+system.menu.type.menu=菜单
+system.menu.type.button=按钮
+
+# 菜单管理-表单(add和edit复用)
+system.menu.add.title=新增菜单
+system.menu.edit.title=修改菜单
+system.menu.form.parentMenu=上级菜单:
+system.menu.form.menuType=菜单类型:
+system.menu.form.menuName=菜单名称:
+system.menu.form.menuCode=菜单编号:
+system.menu.form.url=请求地址:
+system.menu.form.url.help=访问的请求地址,如:`/system/user`,如外网地址需内链访问则以`http(s)://`开头
+system.menu.form.target=打开方式:
+system.menu.target.tab=页签
+system.menu.target.blank=新窗口
+system.menu.form.perms=权限标识:
+system.menu.form.perms.help=控制器中定义的权限标识,如:@RequiresPermissions("")
+system.menu.form.orderNum=显示排序:
+system.menu.form.orderNum.help=数字越小越靠前
+system.menu.form.icon=图标:
+system.menu.form.icon.help=单击选择需要使用的FontAwesome图标
+system.menu.form.icon.placeholder=选择图标
+system.menu.form.visible=菜单状态:
+system.menu.form.visible.help=选择隐藏则菜单将不会出现在侧边栏,也没有权限被访问
+system.menu.form.isRefresh=是否刷新:
+system.menu.form.isRefresh.help=打开菜单选项卡是否刷新页面
+
+# 菜单管理-验证和提示
+system.menu.validate.menuNameExists=菜单已经存在
+system.menu.modal.selectMenu=菜单选择
+system.menu.msg.mainMenuNotSelect=主菜单不能选择
+
+# 菜单管理-树选择页面
+system.menu.tree.title=菜单树选择
+system.menu.tree.keyword=关键字:
+system.menu.tree.btn.search= 搜索 
+system.menu.tree.btn.showSearch=显示搜索
+system.menu.tree.btn.hideSearch=隐藏搜索
+system.menu.tree.expand=展开
+system.menu.tree.collapse=折叠
+
+# 菜单管理-图标页面
+system.menu.icon.title=Font Awesome图标列表
+
+# 通用选项
+options.yes=是
+options.no=否
+
+# 用户个人信息页面
+system.profile.title=用户个人信息
+
+# Logo区域
+system.profile.logo.title=系统logo
+system.profile.logo.edit=修改logo
+system.profile.logo.modal.title=修改自定义logo
+
+# 个人资料区域
+system.profile.info.title=个人资料
+system.profile.info.loginName=登录名称:
+system.profile.info.phone=手机号码:
+system.profile.info.dept=所属部门:
+system.profile.info.email=邮箱地址:
+system.profile.info.createTime=创建时间:
+
+# 基本资料区域
+system.profile.basic.title=基本资料
+system.profile.tab.basic=基本资料
+system.profile.tab.password=修改密码
+
+# 表单字段
+system.profile.form.userName=用户名称:
+system.profile.form.userName.placeholder=请输入用户名称
+system.profile.form.phone=手机号码:
+system.profile.form.phone.placeholder=请输入手机号码
+system.profile.form.email=邮箱:
+system.profile.form.email.placeholder=请输入邮箱
+system.profile.form.sex=性别:
+system.profile.sex.male=男
+system.profile.sex.female=女
+
+# 头像
+system.profile.avatar.edit=修改头像
+system.profile.avatar.modal.title=修改用户头像
+
+# 修改密码
+system.profile.password.old=旧密码:
+system.profile.password.old.placeholder=请输入旧密码
+system.profile.password.new=新密码:
+system.profile.password.new.placeholder=请输入新密码
+system.profile.password.confirm=确认密码:
+system.profile.password.confirm.placeholder=请确认密码
+
+# 密码规则提示
+system.profile.password.rule.number=密码只能为0-9数字
+system.profile.password.rule.letter=密码只能为a-z和A-Z字母
+system.profile.password.rule.mix=密码必须包含(字母,数字)
+system.profile.password.rule.special=密码必须包含(字母,数字,特殊字符!@#$%^&*()-=_+)
+
+# 验证消息
+system.profile.validate.userName.required=请输入用户名称
+system.profile.validate.email.required=请输入邮箱
+system.profile.validate.email.exists=Email已经存在
+system.profile.validate.phone.required=请输入手机号码
+system.profile.validate.phone.exists=手机号码已经存在
+system.profile.password.validate.old.required=请输入原密码
+system.profile.password.validate.old.error=原密码错误
+system.profile.password.validate.new.required=请输入新密码
+system.profile.password.validate.new.minlength=密码不能小于6个字符
+system.profile.password.validate.new.maxlength=密码不能大于20个字符
+system.profile.password.validate.confirm.required=请再次输入新密码
+system.profile.password.validate.confirm.equalTo=两次密码输入不一致
+
+# 上传和裁剪
+system.profile.upload.image=上传图像
+system.profile.upload.image.type.error=请选择一个图片文件。
+system.profile.cropper.loading=裁剪框加载中,请稍候...
+
+# 通用按钮
+btn.confirm=确定
+
+# 岗位管理-列表页面
+system.post.list.title=岗位列表
+system.post.list.query.postCode=岗位编码:
+system.post.list.query.postName=岗位名称:
+system.post.list.query.status=岗位状态:
+system.post.modalName=岗位
+system.post.table.postId=岗位编号
+system.post.table.postCode=岗位编码
+system.post.table.postName=岗位名称
+system.post.table.postSort=显示顺序
+system.post.table.status=状态
+system.post.table.createTime=创建时间
+system.post.table.opra=操作
+
+# 岗位管理-表单(add和edit复用)
+system.post.add.title=新增岗位
+system.post.edit.title=修改岗位
+system.post.form.postName=岗位名称:
+system.post.form.postCode=岗位编码:
+system.post.form.postSort=显示顺序:
+system.post.form.status=岗位状态:
+system.post.form.remark=备注:
+
+# 岗位管理-验证
+system.post.validate.postCodeExists=岗位编码已经存在
+system.post.validate.postNameExists=岗位名称已经存在
+
+# 字典类型管理-列表页面
+system.dict.type.list.title=字典类型列表
+system.dict.type.list.query.dictName=字典名称:
+system.dict.type.list.query.dictType=字典类型:
+system.dict.type.list.query.status=字典状态:
+system.dict.type.list.query.createTime=创建时间:
+system.dict.type.list.btn.refreshCache=刷新缓存
+system.dict.type.modalName=类型
+system.dict.type.table.dictId=字典主键
+system.dict.type.table.dictName=字典名称
+system.dict.type.table.dictType=字典类型
+system.dict.type.table.status=状态
+system.dict.type.table.remark=备注
+system.dict.type.table.createTime=创建时间
+system.dict.type.table.opra=操作
+system.dict.type.btn.list=列表
+system.dict.type.modal.dictData=字典数据
+
+# 字典类型管理-表单(add和edit复用)
+system.dict.type.add.title=新增字典类型
+system.dict.type.edit.title=修改字典类型
+system.dict.type.form.dictName=字典名称:
+system.dict.type.form.dictType=字典类型:
+system.dict.type.form.dictType.help=数据存储中的Key值,如:sys_user_sex
+system.dict.type.form.status=状态:
+system.dict.type.form.remark=备注:
+
+# 字典类型管理-验证
+system.dict.type.validate.dictTypeExists=该字典类型已经存在
+
+# 字典树选择页面
+system.dict.tree.title=字典树选择
+system.dict.tree.keyword=关键字:
+system.dict.tree.btn.search= 搜索 
+system.dict.tree.btn.showSearch=显示搜索
+system.dict.tree.btn.hideSearch=隐藏搜索
+
+# 字典数据管理-列表页面
+system.dict.data.list.title=字典数据列表
+system.dict.data.list.query.dictType=字典名称:
+system.dict.data.list.query.dictLabel=字典标签:
+system.dict.data.list.query.status=数据状态:
+system.dict.data.modalName=数据
+system.dict.data.table.dictCode=字典编码
+system.dict.data.table.dictLabel=字典标签
+system.dict.data.table.dictValue=字典键值
+system.dict.data.table.dictSort=字典排序
+system.dict.data.table.status=状态
+system.dict.data.table.remark=备注
+system.dict.data.table.createTime=创建时间
+system.dict.data.table.opra=操作
+
+# 字典数据管理-表单(add和edit复用)
+system.dict.data.add.title=新增字典数据
+system.dict.data.edit.title=修改字典数据
+system.dict.data.form.dictLabel=字典标签:
+system.dict.data.form.dictValue=字典键值:
+system.dict.data.form.dictType=字典类型:
+system.dict.data.form.cssClass=样式属性:
+system.dict.data.form.dictSort=字典排序:
+system.dict.data.form.listClass=回显样式:
+system.dict.data.form.listClass.help=table表格字典列显示样式属性
+system.dict.data.form.isDefault=系统默认:
+system.dict.data.form.status=状态:
+system.dict.data.form.remark=备注:
+
+# 回显样式选项
+system.dict.data.listClass.default=默认
+system.dict.data.listClass.primary=主要
+system.dict.data.listClass.success=成功
+system.dict.data.listClass.info=信息
+system.dict.data.listClass.warning=警告
+system.dict.data.listClass.danger=危险
+
+# 通用选项
+options.pleaseSelect=---请选择---
+
+# 缓存监控页面
+system.cache.title=缓存监控
+system.cache.list.title=缓存列表
+system.cache.keys.title=键名列表
+system.cache.value.title=缓存内容
+system.cache.table.cacheName=缓存名称
+system.cache.table.cacheKey=缓存键名
+system.cache.table.operation=操作
+system.cache.btn.clear=清空
+system.cache.btn.clearAll=清理全部
+system.cache.label.cacheName=缓存名称:
+system.cache.label.cacheKey=缓存键名:
+system.cache.label.cacheValue=缓存内容:
+system.cache.msg.refreshListSuccess=刷新缓存列表成功
+system.cache.msg.refreshKeysSuccess=刷新键名列表成功
+system.cache.msg.clearCacheNameSuccess=清理缓存[{0}]成功
+system.cache.msg.clearCacheKeySuccess=清理缓存[{0}]成功
+system.cache.msg.clearAllSuccess=清理全部缓存成功
+
+# 登录日志页面
+system.logininfor.list.title=登录日志列表
+system.logininfor.list.query.ipaddr=登录地址:
+system.logininfor.list.query.loginName=登录名称:
+system.logininfor.list.query.status=登录状态:
+system.logininfor.list.query.loginTime=登录时间:
+system.logininfor.modalName=登录日志
+system.logininfor.table.infoId=访问编号
+system.logininfor.table.loginName=登录名称
+system.logininfor.table.ipaddr=登录地址
+system.logininfor.table.loginLocation=登录地点
+system.logininfor.table.browser=浏览器
+system.logininfor.table.os=操作系统
+system.logininfor.table.status=登录状态
+system.logininfor.table.msg=操作信息
+system.logininfor.table.loginTime=登录时间
+
+# 在线用户页面
+system.online.list.title=在线用户列表
+system.online.list.query.ipaddr=登录地址:
+system.online.list.query.loginName=登录名称:
+system.online.modalName=在线用户
+system.online.table.serialNumber=序号
+system.online.table.sessionId=会话编号
+system.online.table.loginName=登录名称
+system.online.table.deptName=部门名称
+system.online.table.ipaddr=主机
+system.online.table.loginLocation=登录地点
+system.online.table.browser=浏览器
+system.online.table.os=操作系统
+system.online.table.status=会话状态
+system.online.table.startTimestamp=登录时间
+system.online.table.lastAccessTime=最后访问时间
+system.online.table.operation=操作
+system.online.status.online=在线
+system.online.status.offline=离线
+system.online.msg.confirmForceLogout=确定要强制选中用户下线吗?
+system.online.msg.selectUser=请选择要强退的用户
+system.online.msg.confirmBatchForceLogout=确认要强退选中的{0}条数据吗?
+
+# 通用按钮(补充)
+btn.clean=清空
+btn.unlock=解锁
+btn.forceLogout=强退
+
+# 操作日志详细页面
+system.operlog.detail.title=操作日志详细
+system.operlog.detail.module=操作模块:
+system.operlog.detail.loginInfo=登录信息:
+system.operlog.detail.requestUrl=请求地址:
+system.operlog.detail.costTime=耗时
+system.operlog.detail.millisecond=毫秒
+system.operlog.detail.method=操作方法:
+system.operlog.detail.operParam=请求参数:
+system.operlog.detail.jsonResult=返回参数:
+system.operlog.detail.status=状态:
+system.operlog.detail.errorMsg=异常信息:
+system.operlog.status.normal=正常
+system.operlog.status.abnormal=异常
+
+# 操作日志列表页面
+system.operlog.list.title=操作日志列表
+system.operlog.list.query.operIp=操作地址:
+system.operlog.list.query.title=系统模块:
+system.operlog.list.query.operName=操作人员:
+system.operlog.list.query.businessType=操作类型:
+system.operlog.list.query.status=操作状态:
+system.operlog.list.query.operTime=操作时间:
+system.operlog.modalName=操作日志
+system.operlog.table.operId=日志编号
+system.operlog.table.title=系统模块
+system.operlog.table.businessType=操作类型
+system.operlog.table.operName=操作人员
+system.operlog.table.deptName=部门名称
+system.operlog.table.operIp=操作地址
+system.operlog.table.operLocation=操作地点
+system.operlog.table.status=操作状态
+system.operlog.table.operTime=操作时间
+system.operlog.table.costTime=消耗时间
+system.operlog.table.operation=操作
+system.operlog.status.success=成功
+system.operlog.status.fail=失败
+system.operlog.format.costTime=%s毫秒
+
+# 服务监控页面(netdata/system_io)
+system.netdata.title=服务监控
+system.netdata.chart.system.cpu=系统整体 CPU
+system.netdata.chart.system.ram=系统整体内存
+system.netdata.chart.system.io=系统磁盘 I/O
+system.netdata.chart.net.enp4s0=网卡流量
+system.netdata.chart.app.easycallcenter365_cpu=easycallcenter365 CPU
+system.netdata.chart.app.easycallcenter365_mem=easycallcenter365 内存
+system.netdata.chart.app.easycallcenter365-gui_cpu=easycallcenter365-gui CPU
+system.netdata.chart.app.easycallcenter365-gui_mem=easycallcenter365-gui 内存
+system.netdata.chart.app.freeswitch_cpu=FreeSWITCH CPU
+system.netdata.chart.app.freeswitch_mem=FreeSWITCH 内存
+system.netdata.chart.app.mysql_cpu=MySQL CPU
+system.netdata.chart.app.mysql_mem=MySQL 内存
+system.netdata.loading=加载中...
+system.netdata.status.offline=离线
+system.netdata.status.online=在线
+system.netdata.status.error=连接失败
+system.netdata.time.5min=5分钟
+system.netdata.time.15min=15分钟
+system.netdata.time.1hour=1小时
+system.netdata.time.24hour=24小时
+system.netdata.time.3day=3天
+system.netdata.btn.refresh=刷新
+system.netdata.stat.current=当前值
+system.netdata.stat.avg=平均值
+system.netdata.stat.max=最大值
+system.netdata.stat.min=最小值
+system.netdata.yaxis.cpu=CPU 使用率 (%)
+system.netdata.yaxis.mem=内存使用 (MiB)
+system.netdata.yaxis.io=磁盘 I/O (KiB/s)
+system.netdata.yaxis.net=网络流量 (kilobits/s)
+system.netdata.error.noData=无数据返回
+system.netdata.error.prefix=错误:
+
+# 服务器监控页面
+system.server.title=服务器监控
+system.server.cpu.title=CPU
+system.server.cpu.cpuNum=核心数
+system.server.cpu.used=用户使用率
+system.server.cpu.sys=系统使用率
+system.server.cpu.free=当前空闲率
+system.server.mem.title=内存
+system.server.mem.memory=内存
+system.server.mem.jvm=JVM
+system.server.mem.total=总内存
+system.server.mem.used=已用内存
+system.server.mem.free=剩余内存
+system.server.mem.usage=使用率
+system.server.sys.title=服务器信息
+system.server.sys.computerName=服务器名称
+system.server.sys.osName=操作系统
+system.server.sys.computerIp=服务器IP
+system.server.sys.osArch=系统架构
+system.server.sys.userDir=项目路径
+system.server.jvm.title=Java虚拟机信息
+system.server.jvm.name=Java名称
+system.server.jvm.version=Java版本
+system.server.jvm.startTime=启动时间
+system.server.jvm.runTime=运行时长
+system.server.jvm.home=安装路径
+system.server.jvm.inputArgs=运行参数
+system.server.disk.title=磁盘状态
+system.server.disk.dirName=盘符路径
+system.server.disk.sysTypeName=文件系统
+system.server.disk.typeName=盘符类型
+system.server.disk.total=总大小
+system.server.disk.free=可用大小
+system.server.disk.used=已用大小
+system.server.disk.usage=已用百分比
+system.server.table.property=属性
+system.server.table.value=值
+system.server.unit.count=个
+
+# 通知公告新增页面
+system.notice.add.title=新增通知公告
+system.notice.placeholder.noticeContent=请输入公告内容
+system.notice.msg.uploadFail=图片上传失败。
+
+# 通知公告修改页面
+system.notice.edit.title=修改通知公告
+
+# 通知公告列表页面
+system.notice.list.title=通知公告列表
+system.notice.list.query.noticeTitle=公告标题:
+system.notice.list.query.createBy=操作人员:
+system.notice.list.query.noticeType=公告类型:
+system.notice.modalName=公告
+system.notice.table.noticeId=序号
+system.notice.table.noticeTitle=公告标题
+system.notice.table.noticeType=公告类型
+system.notice.table.status=状态
+system.notice.table.createBy=创建者
+system.notice.table.createTime=创建时间
+system.notice.table.operation=操作
+
+# 通知公告表单标签
+system.notice.label.noticeTitle=公告标题:
+system.notice.label.noticeType=公告类型:
+system.notice.label.noticeContent=公告内容:
+system.notice.label.status=公告状态:
+
+# 通知公告查看页面
+system.notice.view.title=公告详细
+system.notice.view.sendTime=发送时间
+system.notice.view.sender=发件人
+
+# asr/tts增加语言选择
+callTask.form.asrLanguageCode=ASR语言
+callTask.form.asrLanguageCode.empty=请选择ASR语言
+callTask.form.ttsLanguageCode=TTS语言
+callTask.form.ttsLanguageCode.empty=请选择TTS语言
+inboundllm.form.asrLanguageCode=ASR语言
+inboundllm.form.asrLanguageCode.empty=请选择ASR语言
+inboundllm.form.ttsLanguageCode=TTS语言
+inboundllm.form.ttsLanguageCode.empty=请选择TTS语言
+ivr.form.asrLanguageCode=ASR语言
+ivr.form.asrLanguageCode.empty=请选择ASR语言
+ivr.form.ttsLanguageCode=TTS语言
+ivr.form.ttsLanguageCode.empty=请选择TTS语言
+
+# sys_config数据
+_sys.config.sys.index.skinName=主框架页-默认皮肤样式名称
+_sys.config.sys.user.initPassword=用户管理-账号初始密码
+_sys.config.sys.index.sideTheme=主框架页-侧边栏主题
+_sys.config.sys.account.registerUser=账号自助-是否开启用户注册功能
+_sys.config.sys.account.chrtype=用户管理-密码字符范围
+_sys.config.sys.account.initPasswordModify=用户管理-初始密码修改策略
+_sys.config.sys.account.passwordValidateDays=用户管理-账号密码更新周期
+_sys.config.sys.index.menuStyle=主框架页-菜单导航显示风格
+_sys.config.sys.index.footer=主框架页-是否开启页脚
+_sys.config.sys.index.tagsView=主框架页-是否开启页签
+_sys.config.sys.login.blackIPList=用户登录-黑名单列表
+_sys.config.sys.version=系统版本号
+_sys.config.sys.logo=系统默认logo
+_sys.config.config_asr_provider_aliyun=阿里云
+_sys.config.config_asr_provider_funasr=FunASR
+_sys.config.config_asr_provider_chinatelecom=电信
+_sys.config.config_asr_provider_aws=亚马逊
+_sys.config.config_tts_provider_aliyun=阿里云
+_sys.config.config_tts_provider_doubao=豆包
+_sys.config.config_tts_provider_chinatelecom=电信
+_sys.config.config_tts_provider_aws=亚马逊
+_sys.config.config_tts_language_aliyun_tts=阿里云tts语言设置
+_sys.config.config_tts_language_doubao_vcl_tts=豆包tts语言设置
+_sys.config.config_tts_language_aws_tts=亚马逊tts语言设置
+_sys.config.config_asr_language_aliyun=阿里云asr语言设置
+_sys.config.config_asr_language_aws=亚马逊asr语言设置
+
+# 字典类型
+_sys.dict.type.sys_user_sex=用户性别
+_sys.dict.type.sys_show_hide=菜单状态
+_sys.dict.type.sys_normal_disable=系统开关
+_sys.dict.type.sys_job_status=任务状态
+_sys.dict.type.sys_job_group=任务分组
+_sys.dict.type.sys_yes_no=系统是否
+_sys.dict.type.sys_notice_type=通知类型
+_sys.dict.type.sys_notice_status=通知状态
+_sys.dict.type.sys_oper_type=操作类型
+_sys.dict.type.sys_common_status=系统状态
+
+# 字典值
+_sys.dict.data.label.sys_user_sex.0=男
+_sys.dict.data.label.sys_user_sex.1=女
+_sys.dict.data.label.sys_user_sex.2=未知
+_sys.dict.data.label.sys_show_hide.0=显示
+_sys.dict.data.label.sys_show_hide.1=隐藏
+_sys.dict.data.label.sys_normal_disable.0=正常
+_sys.dict.data.label.sys_normal_disable.1=停用
+_sys.dict.data.label.sys_job_status.0=正常
+_sys.dict.data.label.sys_job_status.1=暂停
+_sys.dict.data.label.sys_job_group.DEFAULT=默认
+_sys.dict.data.label.sys_job_group.SYSTEM=系统
+_sys.dict.data.label.sys_yes_no.Y=是
+_sys.dict.data.label.sys_yes_no.N=否
+_sys.dict.data.label.sys_notice_type.1=通知
+_sys.dict.data.label.sys_notice_type.2=公告
+_sys.dict.data.label.sys_notice_status.0=正常
+_sys.dict.data.label.sys_notice_status.1=关闭
+_sys.dict.data.label.sys_oper_type.0=其他
+_sys.dict.data.label.sys_oper_type.1=新增
+_sys.dict.data.label.sys_oper_type.2=修改
+_sys.dict.data.label.sys_oper_type.3=删除
+_sys.dict.data.label.sys_oper_type.4=授权
+_sys.dict.data.label.sys_oper_type.5=导出
+_sys.dict.data.label.sys_oper_type.6=导入
+_sys.dict.data.label.sys_oper_type.7=强退
+_sys.dict.data.label.sys_oper_type.8=生成代码
+_sys.dict.data.label.sys_oper_type.9=清空数据
+_sys.dict.data.label.sys_common_status.0=成功
+_sys.dict.data.label.sys_common_status.1=失败
+
+# 系统参数
+_cc.params.phone_encrypted_key=电话工具条-外呼电话的解密key
+_cc.params.recording_path=录音保存路径
+_cc.params.enable_cc_record_stereo=是否开启立体音
+_cc.params.post_cdr_url=话单推送接口
+_cc.params.recordings_extension=录音类型
+_cc.params.conference_video_templates=可用的会议布局
+_cc.params.conference_gateway_addr=电话视频会议网关地址
+_cc.params.conference_recording_path=视频会议录音/录像保存路径
+_cc.params.conference_gateway_caller=电话视频会议主叫
+_cc.params.video_level_id_list=视频清晰度列表
+_cc.params.conference_outboud_profile=电话视频会议外呼的profile
+_cc.params.conference_video_layouts=电话视频会议的布局列表
+_cc.params.wait-for-second-vad-during-interrupt=是否开启二次打断设置(首次讲话不立即响应,等待客户第二次说话)
+_cc.params.outbound-call-extra-params-for-profile-internal2=外呼时携带的额外变量参数(用户控制呼叫行为及设置特性等)
+_cc.params.call_monitor_enabled=是否启用通话监听
+_cc.params.fs_log_file_path=FreeSWITCH日志路径
+_cc.params.cc_log_file_path=call-center日志路径
+_cc.params.fs_call_asr_enabled=是否启动双向asr语音识别并推送结果
+_cc.params.fs_call_asr_engine=双向asr语音识别,使用哪个asr引擎(chinatelecom/funasr/aliyun)
+_cc.params.inbound_call_monitor_enabled=是否启用呼入电话排队监控
+_cc.params.fs_docker_container_name=Freeswitch的docker容器名称
+_cc.params.fs_conf_directory=Freeswitch配置文件路径
+_cc.params.robot-asr-type=语音识别类型; mrcp、websocket
+_cc.params.fs-asr-mrcp-param=mrcp 语音识别请求参数
+_cc.params.robot-max-no-speak-time=机器人通话中,最大客户静默时长; 客户超时不讲话通话会被自动掐断
+_cc.params.max-call-concurrency=呼入通话最大并发数限制
+_cc.params.asr-pause-enabled=asr按需识别,仅在机器话术播报完毕后才启动
+_cc.params.max-wait-time-after-vad-start=检测到客户说话开始后,等待说话结束的最大时长;秒
+_cc.params.inbound-transfer-agent-timeout=呼入电话转接坐席超时时间; 秒
+_cc.params.hide-inbound-number=转接到坐席时,隐藏呼入号码
+_cc.params.inbound-play-opnum=转接到坐席时,是否播报工号
+_cc.params.event-socket-ip=event socket ip address
+_cc.params.event-socket-port=event socket port
+_cc.params.event-socket-pass=event socket password
+_cc.params.event-socket-conn-pool-size=event socket connection pool size
+_cc.params.max-agent-number=最大坐席人数
+_cc.params.ws-server-auth-token-secret=解密并验证token的Key
+_cc.params.ws-server-port=(websocket server) 电话工具条端口
+_cc.params.vad-intelligent-wait=是否开启vad智能等待
+_cc.params.vad-intelligent-wait-ms=vad智能等待的毫秒数
+_cc.params.call-center-server-port=呼叫中心WebAPI的 server-port (只读)
+_cc.params.fs-deploy-type=FreeSWITCH 部署方式; docker/native
+_cc.params.fs-deploy-native-start-up-script=FreeSWITCH native部署方式的启动脚本路径
+_cc.params.fs-root-directory=FreeSWITCH 程序根目录
+_cc.params.call-center-server-ip-addr=服务器对外暴露的IP地址
+_cc.params.call-center-websocket-port=呼叫中心电话工具条端口
+_cc.params.tts_content_variables=话术中涉及到的变量
+_cc.params.call-center-api-token=语音通知的访问Token
+_cc.params.outbound-max-line-number=全局参数; 可用的最大外呼并发数
+_cc.params.outbound-enable-prediction-algorithm=群呼转人工坐席的场景下,是否开启预测外呼算法
+_cc.params.aliyun-tts-account-json=阿里云tts账号参数json
+_cc.params.empty-number-detection-enabled=空号识别功能是否开启
+_cc.params.empty-number-detection-config=空号识别定义
+_cc.params.default_interrupt_ignore_keywords=打断忽略关键字列表默认值
+_cc.params.llm-max-try=连接大模型的最大尝试次数
+_cc.params.llm-conn-timeout=连接大模型的超时时间; 毫秒
+_cc.params.llm-max-try-fail-tips=连接大模型的失败提示语
+_cc.params.api-client-white-ips=允许访问接口的ip白名单
+_cc.params.price_aliyun_asr=阿里云asr单价(每小时)
+_cc.params.price_aliyun_tts=阿里云tts单价(每千次)
+_cc.params.price_aliyun_tts_flow=阿里云声音克隆单价(每万字符)
+_cc.params.price_llm_token=大模型单价(每千token)
+_cc.params.doubao-tts-account-json=豆包tts账号参数json
+_cc.params.system_license_info=system license info
+_cc.params.system_hw_fingerprint_cmd=system_hw_fingerprint_cmd
+_cc.params.fs-inbound-acl-enabled=是否启用freeswitch的呼入白名单防护
+_cc.params.freeswitch_root=freeswitch根目录
+_cc.params.callTask_allowDel_days=间隔多少天允许删除任务及通话记录
+_cc.params.firewalld-config-path=firewalld 防火墙配置文件路径
+_cc.params.firewalld-restart-cmd=重启firewalld防火墙的命令
+_cc.params.firewalld-enabled=是否启用firewalld防护墙
+_cc.params.fs-inbound-allow-ip-list=呼入ip白名单(用于外部线路呼入)
+_cc.params.fs-register-allow-ip-list=分机注册的白名单 
+_cc.params.fs-register-acl-enabled=是否启用freeswitch的分机注册防护
+_cc.params.max-wait-time-customer-speaking=客户未说话静默时长
+
+# 新增功能
+_menu.firewalld=防火墙管理
+_menu.awsAsrConf=亚马逊ASR配置
+_menu.awsTtsConf=亚马逊TTS配置
+_menu.deepgramAsrConf=Deepgram ASR配置
+_menu.deepgramTtsConf=Deepgram TTS配置
+
+# 防火墙管理
+firewallRule.add.title=新增防火墙规则配置
+firewallRule.edit.title=修改防火墙规则配置
+firewallRule.notice.title=温馨提示
+firewallRule.notice.freeswitch=无需添加 Freeswitch 相关端口
+firewallRule.notice.ssh=无需添加 SSH 相关端口
+firewallRule.form.protocol=协议
+firewallRule.form.portStart=起始端口
+firewallRule.form.portEnd=结束端口
+firewallRule.form.enableFromSource=启用来源限制
+firewallRule.form.fromSource=来源地址
+firewallRule.protocol.udp=UDP
+firewallRule.protocol.tcp=TCP
+firewallRule.boolean.yes=是
+firewallRule.boolean.no=否
+firewallRule.placeholder.portStart=请输入起始端口号(1-65535)
+firewallRule.placeholder.portEnd=请输入结束端口号(1-65535)
+firewallRule.placeholder.fromSource=请输入来源IP地址或网段
+firewallRule.validation.integer=请输入整数
+firewallRule.validation.portRange=起始端口必须小于等于结束端口
+firewallRule.validation.portStart.required=请输入起始端口
+firewallRule.validation.portEnd.required=请输入结束端口
+firewallRule.validation.port.range=端口范围必须在 1-65535 之间
+
+# 电话工具条状态
+_phoneBar.agentStatus.text_7=预占
+_phoneBar.agentStatus.text_6=多方会议
+_phoneBar.agentStatus.text_5=话后整理
+_phoneBar.agentStatus.text_4=通话中
+_phoneBar.agentStatus.text_33=培训
+_phoneBar.agentStatus.text_32=会议
+_phoneBar.agentStatus.text_31=小休
+_phoneBar.agentStatus.text_3=置忙
+_phoneBar.agentStatus.text_2=置闲
+_phoneBar.agentStatus.text_1=已签入
+phonebar.msg.error_login_token=电话工具条:无法获取 loginToken!
+phonebar.msg.error_wss_domain=ERROR! 启用了wss之后,必须使用域名访问websocketServer! 
+phonebar.msg.websocket_not_supported=您的浏览器不支持websocket,您无法使用本页面的功能!
+phonebar.msg.hold_call_hangup=保持的通话已挂机.
+phonebar.msg.call_waiting=客户电话等待中.
+phonebar.msg.call_wait_retrieved=等待的电话已接回.
+phonebar.msg.consultation_started=咨询已开始.
+phonebar.msg.consultation_ended=咨询已结束.
+phonebar.msg.waiting_call_hangup=等待的客户已挂机.
+phonebar.msg.ipcc_disconnected=ipccserver 连接断开.
+phonebar.msg.select_group=请选择业务组!
+phonebar.msg.select_agent=请选择要咨询的坐席成员!
+phonebar.msg.select_free_agent=请选择空闲的坐席成员!
+phonebar.msg.cannot_consult_self=不能咨询自己,请选择其他坐席成员!
+phonebar.msg.select_transfer_group=请选择转接的业务组!
+phonebar.msg.select_transfer_agent=请选择转接的坐席成员!
+phonebar.msg.cannot_transfer_self=不能转给自己,请选择其他坐席成员!
+phonebar.msg.please_login=请先上线.
+phonebar.msg.no_call=当前没有通话.
+phonebar.msg.missing_mp4_path=Parameter mp4FilePath is missing!
+phonebar.msg.cannot_video_reinvite=cant not send video reInvite. Precondition is: Call is connected and callType is audio.
+phonebar.msg.default_video_level=auto default set videoLevel=
+phonebar.msg.default_call_type=auto default set callType=
+phonebar.msg.enter_phone=请输入外呼号码!
+phonebar.msg.invalid_phone=请输入正确格式的外呼号码!
+phonebar.msg.agent_locked=坐席已锁定
+phonebar.msg.default_ui_disabled=callConfig.useDefaultUi = false , 已禁用默认ui工具条按钮.
+phonebar.msg.logout_not_allowed=当前不允许签出!
+phonebar.msg.confirm_close=关闭网页将导致您无法接听电话,确定要关闭吗 ?
+phonebar.msg.esc_hangup=按下了ESC键, 即将发送挂机指令.
+phonebar.msg.confirm_transfer_conference=是否把当前通话转换为会议 ?
+phonebar.msg.enter_member_name=请填写参会者姓名!
+phonebar.msg.enter_member_phone=请填写参会者手机号!
+phonebar.msg.member_exists=会议成员已经存在,请不要重复添加!
+phonebar.msg.start_conference=正常发起多方通话
+phonebar.msg.provide_call_id=请提供待监听电话的 callId !
+phonebar.status.free=空闲
+phonebar.status.about_to_call=即将呼叫
+phonebar.status.in_call=通话中
+page.title=我的工作台
+phonebar.conference.label.type=会议类型
+phonebar.conference.option.video=视频
+phonebar.conference.option.audio=音频
+phonebar.conference.btn.start=启动会议
+phonebar.conference.btn.end=结束会议
+phonebar.conference.btn.join=加入会议
+phonebar.conference.btn.remove=移除
+phonebar.conference.btn.reinvite=重呼
+phonebar.conference.placeholder.name=姓名
+phonebar.conference.placeholder.phone=手机号
+phonebar.conference.alt.mute=禁言该成员
+phonebar.conference.alt.vmute=关闭该成员的视频
+phonebar.conference.title.remove=踢除会议成员
+phonebar.conference.title.reinvite=重新呼叫
+phonebar.transfer.label.group=业务组
+phonebar.transfer.label.member=坐席成员
+phonebar.transfer.option.select=请选择
+phonebar.transfer.placeholder.phone=电话号码
+phonebar.transfer.title.externalPhone=可以把当前通话转接到外线号码上。如果该文本框留空,则忽略处理。
+phonebar.transfer.btn.transfer=转接电话
+phonebar.transfer.title.transfer=把当前电话转接给他/她处理。
+phonebar.transfer.btn.retrieve=接回客户
+phonebar.transfer.title.retrieve=在咨询失败的情况下使用该按钮,接回处于等待中的电话。
+phonebar.transfer.btn.transferCall=转接客户
+phonebar.transfer.title.transferCall=在咨询成功的情况下使用该按钮,把电话转接给专家坐席。
+phonebar.transfer.btn.consult=拨号咨询
+phonebar.transfer.title.consult=拨号咨询
+phonebar.transfer.status.login=刚签入
+phonebar.transfer.status.free=空闲
+phonebar.transfer.status.busy=忙碌
+phonebar.transfer.status.calling=通话中
+phonebar.transfer.status.after=事后处理
+phonebar.popup.inbound=呼入弹屏
+phonebar.popup.outbound=外呼弹屏
+phonebar.chat.dialog.end=对话已结束。
+phonebar.chat.role.customer=客户
+phonebar.chat.role.agent=我
 
+# asr和tts支持不同模型选择
+inboundllm.form.ttsModels=TTS模型
+inboundllm.form.ttsModels.empty=请选择语音合成模型
+inboundllm.form.asrModels=ASR模型
+inboundllm.form.asrModels.empty=请选择语音识别模型
+callTask.form.ttsModels=TTS模型
+callTask.form.ttsModels.empty=请选择语音合成模型
+callTask.form.asrModels=ASR模型
+callTask.form.asrModels.empty=请选择语音识别模型
+ivr.form.ttsModels=TTS模型
+ivr.form.ttsModels.empty=请选择语音合成模型

BIN
ruoyi-admin/src/main/resources/static/img/login-background.jpg


ファイルの差分が大きいため隠しています
+ 280 - 281
ruoyi-admin/src/main/resources/static/phone-bar/ccPhoneBarSocket.js


+ 10 - 10
ruoyi-admin/src/main/resources/static/ruoyi/index.js

@@ -317,7 +317,7 @@ $(function() {
                 $('.mainContent').find('iframe.RuoYi_iframe').css({"visibility": "hidden", "position": "absolute"}).parents('.mainContent').append(str1);
             }
             
-            $.modal.loading("数据加载中,请稍候...");
+            $.modal.loading(i18n("common.tip.loading"));
 
             $('.mainContent iframe:visible').on('load', function() {
             	$.modal.closeLoading();
@@ -583,14 +583,14 @@ $(function() {
         autoHide: true,
         items: {
             "close_current": {
-                name: "关闭当前",
+                name: i18n("menuTab.close_current"),
                 icon: "fa-close",
                 callback: function(key, opt) {
                     opt.$trigger.find('i').trigger("click");
                 }
             },
             "close_other": {
-                name: "关闭其他",
+                name: i18n("menuTab.close_other"),
                 icon: "fa-window-close-o",
                 callback: function(key, opt) {
                     setActiveTab(this);
@@ -598,7 +598,7 @@ $(function() {
                 }
             },
             "close_left": {
-                name: "关闭左侧",
+                name: i18n("menuTab.close_left"),
                 icon: "fa-reply",
                 callback: function(key, opt) {
                     setActiveTab(this);
@@ -613,7 +613,7 @@ $(function() {
                 }
             },
             "close_right": {
-                name: "关闭右侧",
+                name: i18n("menuTab.close_right"),
                 icon: "fa-share",
                 callback: function(key, opt) {
                     setActiveTab(this);
@@ -624,7 +624,7 @@ $(function() {
                 }
             },
             "close_all": {
-                name: "全部关闭",
+                name: i18n("menuTab.close_all"),
                 icon: "fa-window-close",
                 callback: function(key, opt) {
                     tabCloseAll();
@@ -632,7 +632,7 @@ $(function() {
             },
             "step": "---------",
             "full": {
-                name: "全屏显示",
+                name: i18n("menuTab.full"),
                 icon: "fa-arrows-alt",
                 callback: function(key, opt) {
                     setActiveTab(this);
@@ -641,20 +641,20 @@ $(function() {
                 }
             },
             "refresh": {
-                name: "刷新页面",
+                name: i18n("menuTab.refresh"),
                 icon: "fa-refresh",
                 callback: function(key, opt) {
                     setActiveTab(this);
                     var target = $('.RuoYi_iframe[data-id="' + this.data('id') + '"]');
                     var url = target.attr('src');
-                    $.modal.loading("数据加载中,请稍候...");
+                    $.modal.loading(i18n("common.tip.loading"));
                     target.attr('src', url).on('load', function() {
                     	$.modal.closeLoading();
                     });
                 }
             },
             "open": {
-                name: "新窗口打开",
+                name: i18n("menuTab.open"),
                 icon: "fa-link",
                 callback: function(key, opt) {
                     var target = $('.RuoYi_iframe[data-id="' + this.data('id') + '"]');

+ 1 - 1
ruoyi-admin/src/main/resources/static/ruoyi/js/common.js

@@ -304,7 +304,7 @@ function createMenuItem(dataUrl, menuName, isRefresh) {
             $('.mainContent', topWindow).find('iframe.RuoYi_iframe').css({"visibility": "hidden", "position": "absolute"}).parents('.mainContent').append(str1);
         }
         
-        window.parent.$.modal.loading("数据加载中,请稍候...");
+        window.parent.$.modal.loading(i18n("common.tip.loading"));
         $('.mainContent iframe:visible', topWindow).on('load', function() {
             window.parent.$.modal.closeLoading();
         });

+ 17 - 16
ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js

@@ -393,13 +393,13 @@ var table = {
             // 导出数据
             exportExcel: function(formId) {
                 table.set();
-                $.modal.confirm("确定导出所有" + table.options.modalName + "吗?", function() {
+                $.modal.confirm(i18n("common.tip.export.confirm"), function() {
                     var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId;
                     var params = $("#" + table.options.id).bootstrapTable('getOptions');
                     var dataParam = $("#" + currentId).serializeArray();
                     dataParam.push({ "name": "orderByColumn", "value": params.sortName });
                     dataParam.push({ "name": "isAsc", "value": params.sortOrder });
-                    $.modal.loading("正在导出数据,请稍候...");
+                    $.modal.loading(i18n("common.tip.loading"));
                     $.post(table.options.exportUrl, dataParam, function(result) {
                         if (result.code == web_status.SUCCESS) {
                             window.location.href = ctx + "common/download?fileName=" + encodeURI(result.msg) + "&delete=" + true;
@@ -641,6 +641,7 @@ var table = {
                 table.config[options.id] = options;
                 $.table.initEvent();
                 $.bttTable = $('#' + options.id).bootstrapTreeTable({
+                    locale: currentLang,
                     code: options.code,                                 // 用于设置父子关系
                     parentCode: options.parentCode,                     // 用于设置父子关系
                     type: 'post',                                       // 请求方式(*)
@@ -1068,7 +1069,7 @@ var table = {
                     dataType: dataType,
                     data: data,
                     beforeSend: function () {
-                        $.modal.loading("正在处理中,请稍候...");
+                        $.modal.loading(i18n("common.tip.loading"));
                     },
                     success: function(result) {
                         if (typeof callback == "function") {
@@ -1116,7 +1117,7 @@ var table = {
                 } else {
                     var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
                     if (id.length == 0) {
-                        $.modal.alertWarning("请至少选择一条记录");
+                        $.modal.alertWarning(i18n("common.tip.selected.empty"));
                         return;
                     }
                     url = table.options.detailUrl.replace("{id}", id);
@@ -1126,7 +1127,7 @@ var table = {
             // 删除信息
             remove: function(id) {
                 table.set();
-                $.modal.confirm("确定删除该条" + table.options.modalName + "信息吗?", function() {
+                $.modal.confirm(i18n("common.tip.remove.confirm"), function() {
                     var url = $.common.isEmpty(id) ? table.options.removeUrl : table.options.removeUrl.replace("{id}", id);
                     if (table.options.type == table_type.bootstrapTreeTable) {
                         $.operate.get(url);
@@ -1141,10 +1142,10 @@ var table = {
                 table.set();
                 var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
                 if (rows.length == 0) {
-                    $.modal.alertWarning("请至少选择一条记录");
+                    $.modal.alertWarning(i18n("common.tip.selected.empty"));
                     return;
                 }
-                $.modal.confirm("确认要删除选中的" + rows.length + "条数据吗?", function() {
+                $.modal.confirm(i18n("common.tip.del.confirm"), function() {
                     var url = table.options.removeUrl;
                     var data = { "ids": rows.join() };
                     $.operate.submit(url, "post", "json", data);
@@ -1153,7 +1154,7 @@ var table = {
             // 清空信息
             clean: function() {
                 table.set();
-                $.modal.confirm("确定清空所有" + table.options.modalName + "吗?", function() {
+                $.modal.confirm(i18n("common.tip.clear.confirm"), function() {
                     var url = table.options.cleanUrl;
                     $.operate.submit(url, "post", "json", "");
                 });
@@ -1184,7 +1185,7 @@ var table = {
                 if ($.common.isEmpty(id) && table.options.type == table_type.bootstrapTreeTable) {
                     var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0];
                     if ($.common.isEmpty(row)) {
-                        $.modal.alertWarning("请至少选择一条记录");
+                        $.modal.alertWarning(i18n("common.tip.selected.empty"));
                         return;
                     }
                     var url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]);
@@ -1199,7 +1200,7 @@ var table = {
                 if ($.common.isEmpty(id) && table.options.type == table_type.bootstrapTreeTable) {
                     var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0];
                     if ($.common.isEmpty(row)) {
-                        $.modal.alertWarning("请至少选择一条记录");
+                        $.modal.alertWarning(i18n("common.tip.selected.empty"));
                         return;
                     }
                     var url = table.options.copyUrl.replace("{id}", row[table.options.uniqueId]);
@@ -1223,7 +1224,7 @@ var table = {
                     if (table.options.type == table_type.bootstrapTreeTable) {
                         var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0];
                         if ($.common.isEmpty(row)) {
-                            $.modal.alertWarning("请至少选择一条记录");
+                            $.modal.alertWarning(i18n("common.tip.selected.empty"));
                             return;
                         }
                         url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]);
@@ -1242,7 +1243,7 @@ var table = {
                 } else {
                     var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
                     if (id.length == 0) {
-                        $.modal.alertWarning("请至少选择一条记录");
+                        $.modal.alertWarning(i18n("common.tip.selected.empty"));
                         return;
                     }
                     url = table.options.updateUrl.replace("{id}", id);
@@ -1257,7 +1258,7 @@ var table = {
                 } else {
                     var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
                     if (id.length == 0) {
-                        $.modal.alertWarning("请至少选择一条记录");
+                        $.modal.alertWarning(i18n("common.tip.selected.empty"));
                         return;
                     }
                     url = table.options.copyUrl.replace("{id}", id);
@@ -1278,7 +1279,7 @@ var table = {
                     dataType: "json",
                     data: data,
                     beforeSend: function () {
-                        $.modal.loading("正在处理中,请稍候...");
+                        $.modal.loading(i18n("common.tip.loading"));
                         $.modal.disable();
                     },
                     success: function(result) {
@@ -1298,7 +1299,7 @@ var table = {
                     dataType: "json",
                     data: data,
                     beforeSend: function () {
-                        $.modal.loading("正在处理中,请稍候...");
+                        $.modal.loading(i18n("common.tip.loading"));
                     },
                     success: function(result) {
                         if (typeof callback == "function") {
@@ -1324,7 +1325,7 @@ var table = {
                     dataType: "json",
                     data: data,
                     beforeSend: function () {
-                        $.modal.loading("正在处理中,请稍候...");
+                        $.modal.loading(i18n("common.tip.loading"));
                     },
                     success: function(result) {
                         if (typeof callback == "function") {

+ 381 - 2
ruoyi-admin/src/main/resources/templates/aicall/account/add.html

@@ -2,11 +2,17 @@
 <html lang="zh" xmlns:th="http://www.thymeleaf.org" >
 <head>
     <th:block th:include="include :: header('修改机器人参数配置')" />
+    <base target="_blank">
 </head>
 <body class="white-bg">
 <div class="wrapper wrapper-content animated fadeInRight ibox-content">
     <form class="form-horizontal m" id="form-account-add" th:object="${ccLlmAgentAccount}">
         <input name="id" th:field="*{id}" type="hidden">
+        <input name="rootId" id="rootId" type="hidden">
+        <input name="openingRemarksWav" id="openingRemarksWav" type="hidden">
+        <input name="customerNoVoiceTipsWav" id="customerNoVoiceTipsWav" type="hidden">
+        <input name="hangupTipsWav" id="hangupTipsWav" type="hidden">
+        <input name="transferToAgentTipsWav" id="transferToAgentTipsWav" type="hidden">
         <div class="col-xs-12">
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.name}"></label>
@@ -145,7 +151,19 @@
             this.value = v;
         });
 
+        // 绑定文件上传按钮事件(动态生成的元素使用事件委托)
+        $(document).on('click', '.btn-file-upload', function() {
+            var fieldId = $(this).data('field');
+            var uploadType = $(this).data('upload-type') || 'voice';
+            openUploadModal(fieldId, uploadType);
+        });
 
+        // 初始化Wav隐藏字段值(从accountJson中获取)
+        $('#openingRemarksWav').val(_accountJson.openingRemarksWav || '');
+        $('#customerNoVoiceTipsWav').val(_accountJson.customerNoVoiceTipsWav || '');
+        $('#hangupTipsWav').val(_accountJson.hangupTipsWav || '');
+        $('#transferToAgentTipsWav').val(_accountJson.transferToAgentTipsWav || '');
+        $('#rootId').val(_accountJson.rootId || '123');
 
         // 错误处理:如果存在错误消息,则显示错误并禁用所有操作
         if (_errorMsg && _errorMsg !== '' && _errorMsg !== null) {
@@ -157,6 +175,24 @@
             $.modal.alertError(_errorMsg);
         }
 
+        // 音频预览回显 - 如果有已上传的录音文件则显示预览
+        setTimeout(function() {
+            // 从_accountJson获取文件URL(accountJson的属性)
+            var fileUrlMapping = {
+                'openingRemarks': _accountJson.openingRemarksFileUrl || '',
+                'customerNoVoiceTips': _accountJson.customerNoVoiceTipsFileUrl || '',
+                'hangupTips': _accountJson.hangupTipsFileUrl || '',
+                'transferToAgentTips': _accountJson.transferToAgentTipsFileUrl || ''
+            };
+
+            Object.keys(fileUrlMapping).forEach(function(fieldName) {
+                var fileUrl = fileUrlMapping[fieldName];
+                if (fileUrl && fileUrl.endsWith('.wav')) {
+                    showAudioPreview(fieldName, fileUrl);
+                }
+            });
+        }, 500); // 延迟执行,确保动态字段已渲染
+
     });
 
     // 根据选择的实现类动态更新表单字段
@@ -167,7 +203,7 @@
         $("#intentionTipsContainer").hide(); // 客户意向提示词默认隐藏
         _hideIntentionTipsContainer = true;
 
-        if (["DeepSeekChat", "ChatGpt4o", "JiutianChat"].includes(providerClassName)) {
+        if (["DeepSeekChat", "ChatGPT", "ClaudeChat", "JiutianChat"].includes(providerClassName)) {
             // 显示serverUrl、apiKey、modelName、
             // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
             container.append(_dynamicFieldsDiv["serverUrl"]);
@@ -187,7 +223,7 @@
             _hideIntentionTipsContainer = false;
         }
 
-        if (["LocalLlmChat"].includes(providerClassName)) {
+        if (["XingWenChat"].includes(providerClassName)) {
             // 显示serverUrl、apiKey、modelName、
             // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
             container.append(_dynamicFieldsDiv["serverUrl"]);
@@ -205,6 +241,32 @@
             // _hideIntentionTipsContainer = false;
         }
 
+        if (["LocalLlmChat"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、modelName、
+            // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            container.append(_dynamicFieldsDiv["llmTips"]);
+            container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+        }
+
+        if (["LocalWavFile"].includes(providerClassName)) {
+            // 同LocalLlmChat显示的元素,但使用带上传按钮的录音字段
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            container.append(_dynamicFieldsDiv["llmTips"]);
+            container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTipsWav"]);
+            container.append(_dynamicFieldsDiv["hangupTipsWav"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTipsWav"]);
+            container.append(_dynamicFieldsDiv["openingRemarksWav"]);
+        }
 
         if (["LocalNlpChat"].includes(providerClassName)) {
             container.append(_dynamicFieldsDiv["serverUrl"]);
@@ -302,6 +364,15 @@
         return this.optional(element) || (/^[0-9]$/.test(value));
     }, '请输入单个数字 0-9');
 
+    // LocalWavFile录音文件必填校验
+    $.validator.addMethod('wavFileRequired', function (value, element) {
+        var providerClassName = $("#providerClassNameSelect").val();
+        if (providerClassName === "LocalWavFile") {
+            return value && value.trim() !== "";
+        }
+        return true;
+    }, '请上传录音文件');
+
     $("#form-account-add").validate({
         focusCleanup: true,
         rules: {
@@ -326,6 +397,33 @@
 
     function submitHandler() {
         if ($("#form-account-add").valid()) {
+            var providerClassName = $("#providerClassNameSelect").val();
+
+            // LocalWavFile时必须上传录音文件
+            if (providerClassName === "LocalWavFile") {
+                var requiredWavFields = ['openingRemarksWav', 'customerNoVoiceTipsWav', 'hangupTipsWav', 'transferToAgentTipsWav'];
+                var missingFields = [];
+
+                requiredWavFields.forEach(function(fieldName) {
+                    var fieldValue = $('#' + fieldName).val();
+                    if (!fieldValue || fieldValue.trim() === "") {
+                        missingFields.push(fieldName);
+                    }
+                });
+
+                if (missingFields.length > 0) {
+                    var fieldNameMap = {
+                        'openingRemarksWav': '开场白',
+                        'customerNoVoiceTipsWav': '客户无声音提示',
+                        'hangupTipsWav': '挂机提示',
+                        'transferToAgentTipsWav': '转人工提示'
+                    };
+                    var missingNames = missingFields.map(function(f) { return fieldNameMap[f] || f; });
+                    top.layer.msg("以下录音文件必须上传:" + missingNames.join("、"), {icon: 2});
+                    return;
+                }
+            }
+
             // 收集动态字段
             var dynamicFields = {};
             $("#dynamicFieldsContainer").find("input, textarea, select").each(function() {
@@ -337,6 +435,25 @@
                 dynamicFields[fieldName] = fieldValue;
             });
 
+            // 收集Wav字段(从隐藏的input中获取)
+            var wavFieldMapping = {
+                'openingRemarksWav': $('#openingRemarksWav').val(),
+                'customerNoVoiceTipsWav': $('#customerNoVoiceTipsWav').val(),
+                'hangupTipsWav': $('#hangupTipsWav').val(),
+                'transferToAgentTipsWav': $('#transferToAgentTipsWav').val()
+            };
+            console.log($('#transferToAgentTipsWav').val())
+
+            Object.keys(wavFieldMapping).forEach(function(fieldName) {
+                var fieldValue = wavFieldMapping[fieldName];
+                if (fieldValue === undefined || fieldValue === null) {
+                    fieldValue = "";
+                }
+                dynamicFields[fieldName] = fieldValue;
+                console.log(fieldName)
+                console.log(fieldValue)
+            });
+
             // 如果 intentionTipsContainer 不显示,则强制清空 intentionTips
             if (_hideIntentionTipsContainer) {
                 $("textarea[name='intentionTips']").val("");
@@ -488,6 +605,118 @@
                 </div>
             </div>`;
 
+        // openingRemarksWav - 带录音上传按钮
+        dynamicFieldsDiv["openingRemarksWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.openingRemarks")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="openingRemarks" class="form-control" rows="3" required>${_accountJson.openingRemarks || ''}</textarea>
+                        <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
+                        <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="openingRemarks" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="openingRemarksWavHidden" value="${_accountJson.openingRemarksWav || ''}">
+                </div>
+            </div>
+            <div class="form-group openingRemarksAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.openingRemarksFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
+        // customerNoVoiceTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["customerNoVoiceTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.customerNoVoiceTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="customerNoVoiceTips" class="form-control" rows="3" required>${_accountJson.customerNoVoiceTips || ''}</textarea>
+                        <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                        <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="customerNoVoiceTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="customerNoVoiceTipsWavHidden" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group customerNoVoiceTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.customerNoVoiceTipsFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
+        // hangupTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["hangupTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.hangupTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="hangupTips" class="form-control" rows="3" required>${_accountJson.hangupTips || ''}</textarea>
+                        <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
+                        <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="hangupTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="hangupTipsWavHidden" value="${_accountJson.hangupTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group hangupTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.hangupTipsFileUrlv || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
+        // transferToAgentTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["transferToAgentTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.transferToAgentTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="transferToAgentTips" class="form-control" rows="3" required>${_accountJson.transferToAgentTips || ''}</textarea>
+                        <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
+                        <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="transferToAgentTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="transferToAgentTipsWavHidden" value="${_accountJson.transferToAgentTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group transferToAgentTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.transferToAgentTipsFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
         return dynamicFieldsDiv;
 
     }
@@ -522,6 +751,156 @@
             }
         });
     }
+
+    // ========== 文件上传功能 ==========
+
+    // 打开文件上传模态框
+    function openUploadModal(fieldId, uploadType) {
+        var modalHtml = `
+            <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel">
+                <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                        <div class="modal-header">
+                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                                <span aria-hidden="true">&times;</span>
+                            </button>
+                            <h4 class="modal-title" id="uploadModalLabel">上传录音文件</h4>
+                        </div>
+                        <div class="modal-body">
+                            <form id="uploadForm" enctype="multipart/form-data">
+                                <div class="form-group">
+                                    <label>选择文件</label>
+                                    <input type="file" id="fileInput" name="file" accept=".wav"
+                                           class="form-control" style="height:auto;">
+                                    <span class="help-block">仅支持wav格式,最大50MB</span>
+                                </div>
+                                <div class="form-group" id="uploadProgress" style="display:none;">
+                                    <label>上传进度</label>
+                                    <div class="progress">
+                                        <div class="progress-bar" role="progressbar" style="width: 0%;">
+                                            0%
+                                        </div>
+                                    </div>
+                                </div>
+                            </form>
+                        </div>
+                        <div class="modal-footer">
+                            <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                            <button type="button" class="btn btn-primary" id="confirmUpload">确认上传</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            `;
+
+        $('#uploadModal').remove();
+        $('body').append(modalHtml);
+
+        var $modal = $('#uploadModal');
+        var $fileInput = $('#fileInput');
+
+        $modal.modal('show');
+
+        $('#confirmUpload').off('click').on('click', function() {
+            var file = $fileInput[0].files[0];
+            if (!file) {
+                top.layer.msg("请选择文件", {icon: 2});
+                return;
+            }
+
+            if (!validateAudioFile(file)) {
+                return;
+            }
+
+            var rootId = $('#rootId').val();
+            uploadFileWithProgress(file, fieldId, rootId, function(success, result) {
+                $modal.modal('hide');
+                if (success) {
+                    // 更新对应的Wav隐藏字段(使用Hidden ID)
+                    var wavHiddenId = '#' + fieldId + 'WavHidden';
+                    $(wavHiddenId).val(result.filePath);
+                    $('#' + fieldId + 'Wav').val(result.filePath);
+                    // 显示音频预览
+                    showAudioPreview(fieldId, result.fileUrl);
+                    top.layer.msg("上传成功", {icon: 1});
+                } else {
+                    top.layer.msg("上传失败:" + result, {icon: 2});
+                }
+            });
+        });
+
+        $modal.off('hidden.bs.modal').on('hidden.bs.modal', function() {
+            $modal.remove();
+        });
+    }
+
+    // 验证音频文件
+    function validateAudioFile(file) {
+        if (!file.name.toLowerCase().endsWith('.wav')) {
+            top.layer.msg("仅支持wav格式文件", {icon: 2});
+            return false;
+        }
+
+        var maxSize = 50 * 1024 * 1024; // 50MB
+        if (file.size > maxSize) {
+            top.layer.msg("文件大小不能超过50MB", {icon: 2});
+            return false;
+        }
+
+        return true;
+    }
+
+    // 带进度条上传
+    function uploadFileWithProgress(file, fieldId, rootId, callback) {
+        var formData = new FormData();
+        formData.append('file', file);
+        formData.append('rootId', rootId);
+
+        $('#uploadProgress').show();
+
+        $.ajax({
+            url: ctx + 'cc/ivr/uploadVoice', // 复用IVR的上传接口
+            type: 'POST',
+            data: formData,
+            processData: false,
+            contentType: false,
+            xhr: function() {
+                var xhr = $.ajaxSettings.xhr();
+                if (xhr.upload) {
+                    xhr.upload.addEventListener('progress', function(e) {
+                        if (e.lengthComputable) {
+                            var percent = Math.floor((e.loaded / e.total) * 100);
+                            $('.progress-bar').css('width', percent + '%').text(percent + '%');
+                        }
+                    }, false);
+                }
+                return xhr;
+            },
+            success: function(rsp) {
+                if (rsp.code === 0 || rsp.code === 200) {
+                    callback(true, rsp.data);
+                } else {
+                    callback(false, rsp.msg);
+                }
+            },
+            error: function(xhr, status, error) {
+                callback(false, "网络错误:" + error);
+            }
+        });
+    }
+
+    // 显示音频预览
+    function showAudioPreview(fieldId, fileUrl) {
+        var previewClass = '.' + fieldId + 'AudioPreview';
+        var $preview = $(previewClass);
+
+        if ($preview.length && fileUrl) {
+            $preview.find('audio source').attr('src', ctx + fileUrl);
+            $preview.find('audio')[0].load();
+            $preview.show();
+        }
+    }
+
 </script>
 </body>
 </html>

+ 742 - 363
ruoyi-admin/src/main/resources/templates/aicall/account/edit.html

@@ -2,398 +2,515 @@
 <html lang="zh" xmlns:th="http://www.thymeleaf.org" >
 <head>
     <th:block th:include="include :: header('修改机器人参数配置')" />
+    <base target="_blank">
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-account-edit" th:object="${ccLlmAgentAccount}">
-            <input name="id" th:field="*{id}" type="hidden">
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.name}"></label>
-                    <div class="col-sm-8">
-                        <input name="name" th:field="*{name}" class="form-control" required type="text">
-                    </div>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-account-edit" th:object="${ccLlmAgentAccount}">
+        <input name="id" th:field="*{id}" type="hidden">
+        <input name="rootId" id="rootId" type="hidden">
+        <input name="openingRemarksWav" id="openingRemarksWav" type="hidden">
+        <input name="customerNoVoiceTipsWav" id="customerNoVoiceTipsWav" type="hidden">
+        <input name="hangupTipsWav" id="hangupTipsWav" type="hidden">
+        <input name="transferToAgentTipsWav" id="transferToAgentTipsWav" type="hidden">
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.name}"></label>
+                <div class="col-sm-8">
+                    <input name="name" th:field="*{name}" class="form-control" required type="text">
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.providerClassName}"></label>
-                    <div class="col-sm-8">
-                        <select name="providerClassName" th:field="*{providerClassName}" class="form-control" required id="providerClassNameSelect">
-                            <!-- 选项将通过 JavaScript 动态填充 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.providerClassName}"></label>
+                <div class="col-sm-8">
+                    <select name="providerClassName" th:field="*{providerClassName}" class="form-control" required id="providerClassNameSelect">
+                        <!-- 选项将通过 JavaScript 动态填充 -->
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.concurrentNum}"></label>
-                    <div class="col-sm-8">
-                        <input name="concurrentNum" th:field="*{concurrentNum}" class="form-control" required type="text">
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.concurrentNum}"></label>
+                <div class="col-sm-8">
+                    <input name="concurrentNum" th:field="*{concurrentNum}" class="form-control" required type="text">
                 </div>
             </div>
-            <div class="col-xs-12" id="dynamicFieldsContainer">
-                <!-- 动态字段将在这里显示 -->
-            </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.interruptFlag}"></label>
-                    <div class="col-sm-8">
-                        <select name="interruptFlag" class="form-control" required id="interruptFlagSelect" th:field="*{interruptFlag}">
-                            <option value="0" th:text="#{llmAcount.form.interruptFlag0}"></option>
-                            <option value="1" th:text="#{llmAcount.form.interruptFlag1}"></option>
-                            <option value="2" th:text="#{llmAcount.form.interruptFlag2}"></option>
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12" id="dynamicFieldsContainer">
+            <!-- 动态字段将在这里显示 -->
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.interruptFlag}"></label>
+                <div class="col-sm-8">
+                    <select name="interruptFlag" class="form-control" required id="interruptFlagSelect" th:field="*{interruptFlag}">
+                        <option value="0" th:text="#{llmAcount.form.interruptFlag0}"></option>
+                        <option value="1" th:text="#{llmAcount.form.interruptFlag1}"></option>
+                        <option value="2" th:text="#{llmAcount.form.interruptFlag2}"></option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12" id="interruptKeywordsContainer" style="display: none;">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptKeywords}"></label>
-                    <div class="col-sm-8">
-                        <textarea name="interruptKeywords" class="form-control" rows="5" th:field="*{interruptKeywords}"></textarea>
-                    </div>
+        </div>
+        <div class="col-xs-12" id="interruptKeywordsContainer" style="display: none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptKeywords}"></label>
+                <div class="col-sm-8">
+                    <textarea name="interruptKeywords" class="form-control" rows="5" th:field="*{interruptKeywords}"></textarea>
                 </div>
             </div>
-            <div class="col-xs-12" id="interruptIgnoreKeywordsContainer" style="display: none;">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptIgnoreKeywords}"></label>
-                    <div class="col-sm-8">
-                        <textarea name="interruptIgnoreKeywords" class="form-control" rows="5" th:field="*{interruptIgnoreKeywords}"></textarea>
-                    </div>
+        </div>
+        <div class="col-xs-12" id="interruptIgnoreKeywordsContainer" style="display: none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptIgnoreKeywords}"></label>
+                <div class="col-sm-8">
+                    <textarea name="interruptIgnoreKeywords" class="form-control" rows="5" th:field="*{interruptIgnoreKeywords}"></textarea>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label" th:text="#{llmAcount.form.transferManualDigit}"></label>
-                    <div class="col-sm-8">
-                        <input name="transferManualDigit" class="form-control" type="text" maxlength="1" placeholder="例如: 1" th:field="*{transferManualDigit}">
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label" th:text="#{llmAcount.form.transferManualDigit}"></label>
+                <div class="col-sm-8">
+                    <input name="transferManualDigit" class="form-control" type="text" maxlength="1" placeholder="例如: 1" th:field="*{transferManualDigit}">
                 </div>
             </div>
-            <div class="col-xs-12" id="intentionTipsContainer" >
-                <div class="form-group">
-                    <label class="col-sm-3 control-label" th:text="#{llmAcount.form.intentionTips}"></label>
-                    <div class="col-sm-8">
-                        <textarea name="intentionTips" class="form-control" rows="5" th:field="*{intentionTips}"></textarea>
-                    </div>
+        </div>
+        <div class="col-xs-12" id="intentionTipsContainer" >
+            <div class="form-group">
+                <label class="col-sm-3 control-label" th:text="#{llmAcount.form.intentionTips}"></label>
+                <div class="col-sm-8">
+                    <textarea name="intentionTips" class="form-control" rows="5" th:field="*{intentionTips}"></textarea>
                 </div>
             </div>
-        </form>
-    </div>
-    <th:block th:include="include :: footer" />
-    <script th:inline="javascript">
-        var prefix = ctx + "aicall/account"
-        var _hideIntentionTipsContainer = true;
-        var _accountJson = JSON.parse([[${ccLlmAgentAccount.accountJson}]]);
-        var _providerClassName = [[${ccLlmAgentAccount.providerClassName}]];
-        var _dynamicFieldsDiv = initDynamicFieldsDiv(_accountJson);
-        var _errorMsg = [[${errorMsg}]];
-
-        $(document).ready(function() {
-
-            // 获取实现类下拉框数据
-            $.ajax({
-                url: ctx + "aicall/provider/all", // 接口地址
-                type: "GET",
-                success: function(response) {
-                    // 假设返回的数据是一个数组,例如:[{providerClassName: "Provider1"}, {providerClassName: "Provider2"}]
-                    var providers = response.data;
-                    var select = $("#providerClassNameSelect");
-                    select.empty(); // 清空之前的选项
-                    providers.forEach(function(provider) {
-                        select.append($("<option>", {
-                            value: provider.providerClassName,
-                            text: provider.providerClassName
-                        }));
-                    });
-                    select.val(_providerClassName);
-                    // 初始化动态字段
-                    updateDynamicFields(select.val());
-                },
-                error: function(xhr, status, error) {
-                    console.error("获取实现类数据失败:", error);
-                }
-            });
-
-            // 监听下拉框变化
-            $("#providerClassNameSelect").change(function() {
-                updateDynamicFields($(this).val());
-            });
-
-            // 监听下拉框变化
-            $("#providerClassNameSelect").change(function() {
-                updateDynamicFields($(this).val());
-            });
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+<script th:inline="javascript">
+    var prefix = ctx + "aicall/account"
+    var _hideIntentionTipsContainer = true;
+    var _accountJson = JSON.parse([[${ccLlmAgentAccount.accountJson}]]);
+    var _providerClassName = [[${ccLlmAgentAccount.providerClassName}]];
+    var _dynamicFieldsDiv = initDynamicFieldsDiv(_accountJson);
+    var _errorMsg = [[${errorMsg}]];
+
+    $(document).ready(function() {
+
+        // 获取实现类下拉框数据
+        $.ajax({
+            url: ctx + "aicall/provider/all", // 接口地址
+            type: "GET",
+            success: function(response) {
+                // 假设返回的数据是一个数组,例如:[{providerClassName: "Provider1"}, {providerClassName: "Provider2"}]
+                var providers = response.data;
+                var select = $("#providerClassNameSelect");
+                select.empty(); // 清空之前的选项
+                providers.forEach(function(provider) {
+                    select.append($("<option>", {
+                        value: provider.providerClassName,
+                        text: provider.providerClassName
+                    }));
+                });
+                select.val(_providerClassName);
+                // 初始化动态字段
+                updateDynamicFields(select.val());
+            },
+            error: function(xhr, status, error) {
+                console.error("获取实现类数据失败:", error);
+            }
+        });
 
-            //
-            $("#interruptFlagSelect").change(function() {
-                toggleInterruptFields($(this).val());
-            });
+        // 监听下拉框变化
+        $("#providerClassNameSelect").change(function() {
+            updateDynamicFields($(this).val());
+        });
 
-            // 初始化时根据当前值设置显示状态
-            toggleInterruptFields($("#interruptFlagSelect").val());
-
-            // 转人工数字按键:只允许输入 0-9 的单个数字
-            $('input[name="transferManualDigit"]').on('input propertychange paste', function () {
-                let v = this.value;
-                // 只保留0-9的数字
-                v = v.replace(/[^0-9]/g, '');
-                // 限制为单个数字
-                if (v.length > 1) v = v.substring(0, 1);
-                this.value = v;
-            });
+        // 监听下拉框变化
+        $("#providerClassNameSelect").change(function() {
+            updateDynamicFields($(this).val());
+        });
 
+        //
+        $("#interruptFlagSelect").change(function() {
+            toggleInterruptFields($(this).val());
+        });
 
+        // 初始化时根据当前值设置显示状态
+        toggleInterruptFields($("#interruptFlagSelect").val());
 
-            // 错误处理:如果存在错误消息,则显示错误并禁用所有操作
-            if (_errorMsg && _errorMsg !== '' && _errorMsg !== null) {
-                // // 禁用表单内所有输入元素
-                // $('#form-account-edit').find('input, select, textarea').prop('disabled', true);
-                // 禁用确定
-                $(window.parent.document).find('.btn-confirm, .layui-layer-btn0').hide();
-                // 显示错误消息
-                $.modal.alertError(_errorMsg);
-            }
+        // 转人工数字按键:只允许输入 0-9 的单个数字
+        $('input[name="transferManualDigit"]').on('input propertychange paste', function () {
+            let v = this.value;
+            // 只保留0-9的数字
+            v = v.replace(/[^0-9]/g, '');
+            // 限制为单个数字
+            if (v.length > 1) v = v.substring(0, 1);
+            this.value = v;
+        });
 
+        // 绑定文件上传按钮事件(动态生成的元素使用事件委托)
+        $(document).on('click', '.btn-file-upload', function() {
+            var fieldId = $(this).data('field');
+            var uploadType = $(this).data('upload-type') || 'voice';
+            openUploadModal(fieldId, uploadType);
         });
 
-        // 根据选择的实现类动态更新表单字段
-        function updateDynamicFields(providerClassName) {
-            var container = $("#dynamicFieldsContainer");
-            container.empty(); // 清空之前的动态字段
-
-            $("#intentionTipsContainer").hide(); // 客户意向提示词默认隐藏
-            _hideIntentionTipsContainer = true;
-
-            if (["DeepSeekChat", "ChatGpt4o", "JiutianChat"].includes(providerClassName)) {
-                // 显示serverUrl、apiKey、modelName、
-                // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["apiKey"]);
-                container.append(_dynamicFieldsDiv["modelName"]);
-                container.append(_dynamicFieldsDiv["llmTips"]);
-                container.append(_dynamicFieldsDiv["faqContext"]);
-                container.append(_dynamicFieldsDiv["kbCatId"]);
-                loadKbCatOptions(); // 加载下拉数据
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-
-                // intentionTipsContainer
-                $("#intentionTipsContainer").show();
-                _hideIntentionTipsContainer = false;
-            }
+        // 初始化Wav隐藏字段值(从accountJson中获取)
+        $('#openingRemarksWav').val(_accountJson.openingRemarksWav || '');
+        $('#customerNoVoiceTipsWav').val(_accountJson.customerNoVoiceTipsWav || '');
+        $('#hangupTipsWav').val(_accountJson.hangupTipsWav || '');
+        $('#transferToAgentTipsWav').val(_accountJson.transferToAgentTipsWav || '');
+        $('#rootId').val(_accountJson.rootId || '123');
+
+        // 错误处理:如果存在错误消息,则显示错误并禁用所有操作
+        if (_errorMsg && _errorMsg !== '' && _errorMsg !== null) {
+            // // 禁用表单内所有输入元素
+            // $('#form-account-edit').find('input, select, textarea').prop('disabled', true);
+            // 禁用确定
+            $(window.parent.document).find('.btn-confirm, .layui-layer-btn0').hide();
+            // 显示错误消息
+            $.modal.alertError(_errorMsg);
+        }
 
-            if (["LocalLlmChat"].includes(providerClassName)) {
-                // 显示serverUrl、apiKey、modelName、
-                // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                // container.append(_dynamicFieldsDiv["apiKey"]);
-                container.append(_dynamicFieldsDiv["modelName"]);
-                // container.append(_dynamicFieldsDiv["llmTips"]);
-                // container.append(_dynamicFieldsDiv["faqContext"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-
-                // // intentionTipsContainer
-                // $("#intentionTipsContainer").show();
-                // _hideIntentionTipsContainer = false;
-            }
+        // 音频预览回显 - 如果有已上传的录音文件则显示预览
+        setTimeout(function() {
+            // 从_accountJson获取文件URL(accountJson的属性)
+            var fileUrlMapping = {
+                'openingRemarks': _accountJson.openingRemarksFileUrl || '',
+                'customerNoVoiceTips': _accountJson.customerNoVoiceTipsFileUrl || '',
+                'hangupTips': _accountJson.hangupTipsFileUrl || '',
+                'transferToAgentTips': _accountJson.transferToAgentTipsFileUrl || ''
+            };
+
+            Object.keys(fileUrlMapping).forEach(function(fieldName) {
+                var fileUrl = fileUrlMapping[fieldName];
+                if (fileUrl && fileUrl.endsWith('.wav')) {
+                    showAudioPreview(fieldName, fileUrl);
+                }
+            });
+        }, 500); // 延迟执行,确保动态字段已渲染
+
+    });
+
+    // 根据选择的实现类动态更新表单字段
+    function updateDynamicFields(providerClassName) {
+        var container = $("#dynamicFieldsContainer");
+        container.empty(); // 清空之前的动态字段
+
+        $("#intentionTipsContainer").hide(); // 客户意向提示词默认隐藏
+        _hideIntentionTipsContainer = true;
+
+        if (["DeepSeekChat", "ChatGPT", "ClaudeChat", "JiutianChat"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、modelName、
+            // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            container.append(_dynamicFieldsDiv["llmTips"]);
+            container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["kbCatId"]);
+            loadKbCatOptions(); // 加载下拉数据
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+
+            // intentionTipsContainer
+            $("#intentionTipsContainer").show();
+            _hideIntentionTipsContainer = false;
+        }
 
+        if (["XingWenChat"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、modelName、
+            // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            // container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            // container.append(_dynamicFieldsDiv["llmTips"]);
+            // container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+
+            // // intentionTipsContainer
+            // $("#intentionTipsContainer").show();
+            // _hideIntentionTipsContainer = false;
+        }
 
-            if (["LocalNlpChat"].includes(providerClassName)) {
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["botId"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-            }
+        if (["LocalLlmChat"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、modelName、
+            // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            container.append(_dynamicFieldsDiv["llmTips"]);
+            container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+        }
 
-            if (["Coze"].includes(providerClassName)) {
-                // 显示 serverUrl、botId、tokenType、tokenTypeFields、
-                // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["botId"]);
-                container.append(_dynamicFieldsDiv["tokenType"]);
-                container.append(_dynamicFieldsDiv["tokenTypeFields"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-
-                // 监听 tokenType 下拉框变化
-                $("#tokenTypeSelect").change(function() {
-                    var selectedTokenType = $(this).val();
-                    updateTokenTypeSelect(selectedTokenType);
-                });
+        if (["LocalWavFile"].includes(providerClassName)) {
+            // 同LocalLlmChat显示的元素,但使用带上传按钮的录音字段
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["modelName"]);
+            container.append(_dynamicFieldsDiv["llmTips"]);
+            container.append(_dynamicFieldsDiv["faqContext"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTipsWav"]);
+            container.append(_dynamicFieldsDiv["hangupTipsWav"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTipsWav"]);
+            container.append(_dynamicFieldsDiv["openingRemarksWav"]);
+        }
+
+        if (["LocalNlpChat"].includes(providerClassName)) {
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["botId"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+        }
 
-                $("#tokenTypeSelect").val(_accountJson.tokenType);
-                var selectedTokenType = $("#tokenTypeSelect").val();
+        if (["Coze"].includes(providerClassName)) {
+            // 显示 serverUrl、botId、tokenType、tokenTypeFields、
+            // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["botId"]);
+            container.append(_dynamicFieldsDiv["tokenType"]);
+            container.append(_dynamicFieldsDiv["tokenTypeFields"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+
+            // 监听 tokenType 下拉框变化
+            $("#tokenTypeSelect").change(function() {
+                var selectedTokenType = $(this).val();
                 updateTokenTypeSelect(selectedTokenType);
-            }
-            if (["MaxKB"].includes(providerClassName)) {
-                // 显示serverUrl、apiKey、
-                // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["apiKey"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-            }
-            if (["Dify"].includes(providerClassName)) {
-                // 显示serverUrl、apiKey、
-                // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["apiKey"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-            }
-            if (["JiutianWorkflow", "JiutianAgent"].includes(providerClassName)) {
-                // 显示serverUrl、apiKey、
-                // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
-                container.append(_dynamicFieldsDiv["serverUrl"]);
-                container.append(_dynamicFieldsDiv["apiKey"]);
-                container.append(_dynamicFieldsDiv["botId"]);
-                container.append(_dynamicFieldsDiv["transferToAgentTips"]);
-                container.append(_dynamicFieldsDiv["hangupTips"]);
-                container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
-                container.append(_dynamicFieldsDiv["openingRemarks"]);
-            }
+            });
 
+            $("#tokenTypeSelect").val(_accountJson.tokenType);
+            var selectedTokenType = $("#tokenTypeSelect").val();
+            updateTokenTypeSelect(selectedTokenType);
         }
-
-        function updateTokenTypeSelect(selectedTokenType){
-            console.log(selectedTokenType)
-            if (selectedTokenType === "oauth") {
-                $(".oauthFields").show();
-                $(".patFields").hide();
-            } else if (selectedTokenType === "pat") {
-                $(".patFields").show();
-                $(".oauthFields").hide();
-            }
+        if (["MaxKB"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、
+            // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+        }
+        if (["Dify"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、
+            // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
+        }
+        if (["JiutianWorkflow", "JiutianAgent"].includes(providerClassName)) {
+            // 显示serverUrl、apiKey、
+            // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
+            container.append(_dynamicFieldsDiv["serverUrl"]);
+            container.append(_dynamicFieldsDiv["apiKey"]);
+            container.append(_dynamicFieldsDiv["botId"]);
+            container.append(_dynamicFieldsDiv["transferToAgentTips"]);
+            container.append(_dynamicFieldsDiv["hangupTips"]);
+            container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
+            container.append(_dynamicFieldsDiv["openingRemarks"]);
         }
 
-        // 实时限制:只能输 0-200 的整数
-        $('input[name="concurrentNum"]').on('input propertychange paste', function () {
-            let v = this.value;
-            // 去掉非数字
-            v = v.replace(/[^0-9]/g, '');
-            // 去掉前导 0
-            v = v.replace(/^0+(\d)/, '$1');
-            // 上限 200
-            if (v > 200) v = 200;
-            this.value = v;
-        });
+    }
 
-        // 自定义校验方法:0-200 整数
-        $.validator.addMethod('range0_200', function (value, element) {
-            return this.optional(element) || (/^\d+$/.test(value) && value >= 0 && value <= 200);
-        }, '请输入 0-200 之间的整数');
-
-        $.validator.addMethod('singleDigit', function (value, element) {
-            return this.optional(element) || (/^[0-9]$/.test(value));
-        }, '请输入单个数字 0-9');
-
-        $("#form-account-edit").validate({
-            focusCleanup: true,
-            rules: {
-                concurrentNum: {
-                    required: true,
-                    range0_200: true
-                },
-                transferManualDigit: {
-                    singleDigit: true
-                }
+    function updateTokenTypeSelect(selectedTokenType){
+        console.log(selectedTokenType)
+        if (selectedTokenType === "oauth") {
+            $(".oauthFields").show();
+            $(".patFields").hide();
+        } else if (selectedTokenType === "pat") {
+            $(".patFields").show();
+            $(".oauthFields").hide();
+        }
+    }
+
+    // 实时限制:只能输 0-200 的整数
+    $('input[name="concurrentNum"]').on('input propertychange paste', function () {
+        let v = this.value;
+        // 去掉非数字
+        v = v.replace(/[^0-9]/g, '');
+        // 去掉前导 0
+        v = v.replace(/^0+(\d)/, '$1');
+        // 上限 200
+        if (v > 200) v = 200;
+        this.value = v;
+    });
+
+    // 自定义校验方法:0-200 整数
+    $.validator.addMethod('range0_200', function (value, element) {
+        return this.optional(element) || (/^\d+$/.test(value) && value >= 0 && value <= 200);
+    }, '请输入 0-200 之间的整数');
+
+    $.validator.addMethod('singleDigit', function (value, element) {
+        return this.optional(element) || (/^[0-9]$/.test(value));
+    }, '请输入单个数字 0-9');
+
+    // LocalWavFile录音文件必填校验
+    $.validator.addMethod('wavFileRequired', function (value, element) {
+        var providerClassName = $("#providerClassNameSelect").val();
+        if (providerClassName === "LocalWavFile") {
+            return value && value.trim() !== "";
+        }
+        return true;
+    }, '请上传录音文件');
+
+    $("#form-account-edit").validate({
+        focusCleanup: true,
+        rules: {
+            concurrentNum: {
+                required: true,
+                range0_200: true
             },
-            messages: {
-                concurrentNum: {
-                    required: '必填',
-                    range0_200: '请输入 0-200 之间的整数'
-                },
-                transferManualDigit: {
-                    singleDigit: '请输入单个数字 0-9'
-                }
+            transferManualDigit: {
+                singleDigit: true
             }
-        });
+        },
+        messages: {
+            concurrentNum: {
+                required: '必填',
+                range0_200: '请输入 0-200 之间的整数'
+            },
+            transferManualDigit: {
+                singleDigit: '请输入单个数字 0-9'
+            }
+        }
+    });
+
+    function submitHandler() {
+        if ($("#form-account-edit").valid()) {
+            var providerClassName = $("#providerClassNameSelect").val();
+
+            // LocalWavFile时必须上传录音文件
+            if (providerClassName === "LocalWavFile") {
+                var requiredWavFields = ['openingRemarksWav', 'customerNoVoiceTipsWav', 'hangupTipsWav', 'transferToAgentTipsWav'];
+                var missingFields = [];
 
-        function submitHandler() {
-            if ($("#form-account-edit").valid()) {
-                // 收集动态字段
-                var dynamicFields = {};
-                $("#dynamicFieldsContainer").find("input, textarea, select").each(function() {
-                    var fieldName = $(this).attr("name");
-                    var fieldValue = $(this).val();
-                    if (fieldValue === undefined || fieldValue === null) {
-                        fieldValue = ""; // 如果未填写值,则传递空字符串
+                requiredWavFields.forEach(function(fieldName) {
+                    var fieldValue = $('#' + fieldName).val();
+                    if (!fieldValue || fieldValue.trim() === "") {
+                        missingFields.push(fieldName);
                     }
-                    dynamicFields[fieldName] = fieldValue;
                 });
 
-                // 如果 intentionTipsContainer 不显示,则强制清空 intentionTips
-                if (_hideIntentionTipsContainer) {
-                    $("textarea[name='intentionTips']").val("");
+                if (missingFields.length > 0) {
+                    var fieldNameMap = {
+                        'openingRemarksWav': '开场白',
+                        'customerNoVoiceTipsWav': '客户无声音提示',
+                        'hangupTipsWav': '挂机提示',
+                        'transferToAgentTipsWav': '转人工提示'
+                    };
+                    var missingNames = missingFields.map(function(f) { return fieldNameMap[f] || f; });
+                    top.layer.msg("以下录音文件必须上传:" + missingNames.join("、"), {icon: 2});
+                    return;
                 }
+            }
+
+            // 收集动态字段
+            var dynamicFields = {};
+            $("#dynamicFieldsContainer").find("input, textarea, select").each(function() {
+                var fieldName = $(this).attr("name");
+                var fieldValue = $(this).val();
+                if (fieldValue === undefined || fieldValue === null) {
+                    fieldValue = ""; // 如果未填写值,则传递空字符串
+                }
+                dynamicFields[fieldName] = fieldValue;
+            });
 
-                // 将 JSON 字符串添加到表单数据中
-                var formData = $('#form-account-edit').serializeArray();
-                formData.push({"name": "accountJson", "value": JSON.stringify(dynamicFields)})
-                $.operate.save(prefix + "/edit", formData);
+            // 收集Wav字段(从隐藏的input中获取)
+            var wavFieldMapping = {
+                'openingRemarksWav': $('#openingRemarksWav').val(),
+                'customerNoVoiceTipsWav': $('#customerNoVoiceTipsWav').val(),
+                'hangupTipsWav': $('#hangupTipsWav').val(),
+                'transferToAgentTipsWav': $('#transferToAgentTipsWav').val()
+            };
+            console.log($('#transferToAgentTipsWav').val())
+
+            Object.keys(wavFieldMapping).forEach(function(fieldName) {
+                var fieldValue = wavFieldMapping[fieldName];
+                if (fieldValue === undefined || fieldValue === null) {
+                    fieldValue = "";
+                }
+                dynamicFields[fieldName] = fieldValue;
+                console.log(fieldName)
+                console.log(fieldValue)
+            });
+
+            // 如果 intentionTipsContainer 不显示,则强制清空 intentionTips
+            if (_hideIntentionTipsContainer) {
+                $("textarea[name='intentionTips']").val("");
             }
+
+            // 将 JSON 字符串添加到表单数据中
+            var formData = $('#form-account-edit').serializeArray();
+            formData.push({"name": "accountJson", "value": JSON.stringify(dynamicFields)})
+            $.operate.save(prefix + "/edit", formData);
         }
-        function toggleInterruptFields(value) {
-            if (value === "1") {
-                document.getElementById('interruptKeywordsContainer').style.display = 'block';
-                document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'block';
-            } else {
-                document.getElementById('interruptKeywordsContainer').style.display = 'none';
-                document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'none';
-            }
+    }
+    function toggleInterruptFields(value) {
+        if (value === "1") {
+            document.getElementById('interruptKeywordsContainer').style.display = 'block';
+            document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'block';
+        } else {
+            document.getElementById('interruptKeywordsContainer').style.display = 'none';
+            document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'none';
         }
+    }
 
-        function initDynamicFieldsDiv(_accountJson){
-            let dynamicFieldsDiv = {};
-            // serverUrl
-            dynamicFieldsDiv["serverUrl"] = `<div class="form-group">
+    function initDynamicFieldsDiv(_accountJson){
+        let dynamicFieldsDiv = {};
+        // serverUrl
+        dynamicFieldsDiv["serverUrl"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.serverUrl")}</label>
                 <div class="col-sm-8">
                     <input name="serverUrl" value="${_accountJson.serverUrl || ''}" class="form-control" type="text" required>
                 </div>
             </div>`;
 
-            // apiKey
-            dynamicFieldsDiv["apiKey"] = `<div class="form-group">
+        // apiKey
+        dynamicFieldsDiv["apiKey"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.apiKey")}</label>
                 <div class="col-sm-8">
                     <input name="apiKey" value="${_accountJson.apiKey || ''}" class="form-control" type="text" required>
                 </div>
             </div>`;
 
-            // modelName
-            dynamicFieldsDiv["modelName"] = `<div class="form-group">
+        // modelName
+        dynamicFieldsDiv["modelName"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.modelName")}</label>
                 <div class="col-sm-8">
                     <input name="modelName" value="${_accountJson.modelName || ''}" class="form-control" type="text" required>
                 </div>
             </div>`;
 
-            // botId
-            dynamicFieldsDiv["botId"] = `<div class="form-group">
+        // botId
+        dynamicFieldsDiv["botId"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.botId")}</label>
                 <div class="col-sm-8">
                     <input name="botId" value="${_accountJson.botId || ''}" class="form-control" type="text" required>
                 </div>
             </div>`;
 
-            // tokenType
-            dynamicFieldsDiv["tokenType"] = `<div class="form-group">
+        // tokenType
+        dynamicFieldsDiv["tokenType"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.tokenType")}</label>
                 <div class="col-sm-8">
                     <select name="tokenType" value="${_accountJson.tokenType || ''}" class="form-control" required id="tokenTypeSelect">
@@ -403,8 +520,8 @@
                 </div>
             </div>`;
 
-            // tokenTypeFields
-            dynamicFieldsDiv["tokenTypeFields"] = `<div id="tokenTypeFields">
+        // tokenTypeFields
+        dynamicFieldsDiv["tokenTypeFields"] = `<div id="tokenTypeFields">
                 <div class="form-group oauthFields" style="display:none;">
                     <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.oauthClientId")}</label>
                     <div class="col-sm-8">
@@ -431,23 +548,23 @@
                 </div>
             </div>`;
 
-            // llmTips
-            dynamicFieldsDiv["llmTips"] = `<div class="form-group">
+        // llmTips
+        dynamicFieldsDiv["llmTips"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.llmTips")}</label>
                 <div class="col-sm-8">
                     <textarea name="llmTips" class="form-control" rows="30" required>${_accountJson.llmTips || ''}</textarea>
                 </div>
             </div>`;
 
-            // faqContext
-            dynamicFieldsDiv["faqContext"] = `<div class="form-group">
+        // faqContext
+        dynamicFieldsDiv["faqContext"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.faqContext")}</label>
                 <div class="col-sm-8">
                     <textarea name="faqContext" class="form-control" rows="30" required>${_accountJson.faqContext || ''}</textarea>
                 </div>
             </div>`;
 
-            dynamicFieldsDiv["kbCatId"] = `<div class="form-group">
+        dynamicFieldsDiv["kbCatId"] = `<div class="form-group">
                 <label class="col-sm-3 control-label">${i18n("llmAcount.form.kbCatId")}</label>
                 <div class="col-sm-8">
                     <select name="kbCatId" class="form-control" id="kbCatIdSelect">
@@ -456,72 +573,334 @@
                 </div>
             </div>`;
 
-            // transferToAgentTips
-            dynamicFieldsDiv["transferToAgentTips"] = `<div class="form-group">
+        // transferToAgentTips
+        dynamicFieldsDiv["transferToAgentTips"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.transferToAgentTips")}</label>
                 <div class="col-sm-8">
                     <textarea name="transferToAgentTips" class="form-control" rows="3" required>${_accountJson.transferToAgentTips || ''}</textarea>
                 </div>
             </div>`;
 
-            // hangupTips
-            dynamicFieldsDiv["hangupTips"] = `<div class="form-group">
+        // hangupTips
+        dynamicFieldsDiv["hangupTips"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.hangupTips")}</label>
                 <div class="col-sm-8">
                     <textarea name="hangupTips" class="form-control" rows="3" required>${_accountJson.hangupTips || ''}</textarea>
                 </div>
             </div>`;
 
-            // customerNoVoiceTips
-            dynamicFieldsDiv["customerNoVoiceTips"] = `<div class="form-group">
+        // customerNoVoiceTips
+        dynamicFieldsDiv["customerNoVoiceTips"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.customerNoVoiceTips")}</label>
                 <div class="col-sm-8">
                     <textarea name="customerNoVoiceTips" class="form-control" rows="3" required>${_accountJson.customerNoVoiceTips || ''}</textarea>
                 </div>
             </div>`;
 
-            // openingRemarks
-            dynamicFieldsDiv["openingRemarks"] = `<div class="form-group">
+        // openingRemarks
+        dynamicFieldsDiv["openingRemarks"] = `<div class="form-group">
                 <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.openingRemarks")}</label>
                 <div class="col-sm-8">
                     <textarea name="openingRemarks" class="form-control" rows="3" required>${_accountJson.openingRemarks || ''}</textarea>
                 </div>
             </div>`;
 
-            return dynamicFieldsDiv;
+        // openingRemarksWav - 带录音上传按钮
+        dynamicFieldsDiv["openingRemarksWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.openingRemarks")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="openingRemarks" class="form-control" rows="3" required>${_accountJson.openingRemarks || ''}</textarea>
+                        <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
+                        <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="openingRemarks" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="openingRemarksWavHidden" value="${_accountJson.openingRemarksWav || ''}">
+                </div>
+            </div>
+            <div class="form-group openingRemarksAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.openingRemarksFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
 
-        }
+        // customerNoVoiceTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["customerNoVoiceTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.customerNoVoiceTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="customerNoVoiceTips" class="form-control" rows="3" required>${_accountJson.customerNoVoiceTips || ''}</textarea>
+                        <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                        <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="customerNoVoiceTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="customerNoVoiceTipsWavHidden" value="${_accountJson.customerNoVoiceTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group customerNoVoiceTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.customerNoVoiceTipsFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
 
-        // 加载知识库分类下拉框数据
-        function loadKbCatOptions() {
-            $.ajax({
-                url: ctx + "aicall/kbcat/all",
-                type: "GET",
-                success: function(response) {
-                    if (response.code === 0 || response.code === 200) {
-                        var cats = response.data || [];
-                        var select = $("#kbCatIdSelect");
-                        select.empty();
-                        select.append('<option value="">请选择</option>');
-
-                        cats.forEach(function(item) {
-                            select.append($("<option>", {
-                                value: item.id,
-                                text: item.cat
-                            }));
-                        });
-
-                        // 回显已保存的值
-                        if (_accountJson && _accountJson.kbCatId) {
-                            select.val(_accountJson.kbCatId);
-                        }
+        // hangupTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["hangupTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.hangupTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="hangupTips" class="form-control" rows="3" required>${_accountJson.hangupTips || ''}</textarea>
+                        <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
+                        <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="hangupTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="hangupTipsWavHidden" value="${_accountJson.hangupTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group hangupTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.hangupTipsFileUrlv || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
+        // transferToAgentTipsWav - 带录音上传按钮
+        dynamicFieldsDiv["transferToAgentTipsWav"] = `<div class="form-group">
+                <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.transferToAgentTips")}</label>
+                <div class="col-sm-8">
+                    <div class="input-group">
+                        <textarea name="transferToAgentTips" class="form-control" rows="3" required>${_accountJson.transferToAgentTips || ''}</textarea>
+                        <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
+                        <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn-info btn-file-upload" data-field="transferToAgentTips" data-upload-type="voice" title="上传录音">
+                                <i class="fa fa-upload"></i> <span>上传</span>
+                            </button>
+                        </span>
+                    </div>
+                    <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
+                    <input type="hidden" id="transferToAgentTipsWavHidden" value="${_accountJson.transferToAgentTipsWav || ''}">
+                </div>
+            </div>
+            <div class="form-group transferToAgentTipsAudioPreview" style="display:none;">
+                <label class="col-sm-3 control-label">录音预览</label>
+                <div class="col-sm-8">
+                    <audio controls class="form-control" style="height: 40px;">
+                        <source src="${_accountJson.transferToAgentTipsFileUrl || ''}" type="audio/wav">
+                        您的浏览器不支持音频播放。
+                    </audio>
+                </div>
+            </div>`;
+
+        return dynamicFieldsDiv;
+
+    }
+
+    // 加载知识库分类下拉框数据
+    function loadKbCatOptions() {
+        $.ajax({
+            url: ctx + "aicall/kbcat/all",
+            type: "GET",
+            success: function(response) {
+                if (response.code === 0 || response.code === 200) {
+                    var cats = response.data || [];
+                    var select = $("#kbCatIdSelect");
+                    select.empty();
+                    select.append('<option value="">请选择</option>');
+
+                    cats.forEach(function(item) {
+                        select.append($("<option>", {
+                            value: item.id,
+                            text: item.cat
+                        }));
+                    });
+
+                    // 回显已保存的值
+                    if (_accountJson && _accountJson.kbCatId) {
+                        select.val(_accountJson.kbCatId);
                     }
-                },
-                error: function(xhr, status, error) {
-                    console.error("获取知识库分类数据失败:", error);
+                }
+            },
+            error: function(xhr, status, error) {
+                console.error("获取知识库分类数据失败:", error);
+            }
+        });
+    }
+
+    // ========== 文件上传功能 ==========
+
+    // 打开文件上传模态框
+    function openUploadModal(fieldId, uploadType) {
+        var modalHtml = `
+            <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel">
+                <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                        <div class="modal-header">
+                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                                <span aria-hidden="true">&times;</span>
+                            </button>
+                            <h4 class="modal-title" id="uploadModalLabel">上传录音文件</h4>
+                        </div>
+                        <div class="modal-body">
+                            <form id="uploadForm" enctype="multipart/form-data">
+                                <div class="form-group">
+                                    <label>选择文件</label>
+                                    <input type="file" id="fileInput" name="file" accept=".wav"
+                                           class="form-control" style="height:auto;">
+                                    <span class="help-block">仅支持wav格式,最大50MB</span>
+                                </div>
+                                <div class="form-group" id="uploadProgress" style="display:none;">
+                                    <label>上传进度</label>
+                                    <div class="progress">
+                                        <div class="progress-bar" role="progressbar" style="width: 0%;">
+                                            0%
+                                        </div>
+                                    </div>
+                                </div>
+                            </form>
+                        </div>
+                        <div class="modal-footer">
+                            <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                            <button type="button" class="btn btn-primary" id="confirmUpload">确认上传</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            `;
+
+        $('#uploadModal').remove();
+        $('body').append(modalHtml);
+
+        var $modal = $('#uploadModal');
+        var $fileInput = $('#fileInput');
+
+        $modal.modal('show');
+
+        $('#confirmUpload').off('click').on('click', function() {
+            var file = $fileInput[0].files[0];
+            if (!file) {
+                top.layer.msg("请选择文件", {icon: 2});
+                return;
+            }
+
+            if (!validateAudioFile(file)) {
+                return;
+            }
+
+            var rootId = $('#rootId').val();
+            uploadFileWithProgress(file, fieldId, rootId, function(success, result) {
+                $modal.modal('hide');
+                if (success) {
+                    // 更新对应的Wav隐藏字段(使用Hidden ID)
+                    var wavHiddenId = '#' + fieldId + 'WavHidden';
+                    $(wavHiddenId).val(result.filePath);
+                    $('#' + fieldId + 'Wav').val(result.filePath);
+                    // 显示音频预览
+                    showAudioPreview(fieldId, result.fileUrl);
+                    top.layer.msg("上传成功", {icon: 1});
+                } else {
+                    top.layer.msg("上传失败:" + result, {icon: 2});
                 }
             });
+        });
+
+        $modal.off('hidden.bs.modal').on('hidden.bs.modal', function() {
+            $modal.remove();
+        });
+    }
+
+    // 验证音频文件
+    function validateAudioFile(file) {
+        if (!file.name.toLowerCase().endsWith('.wav')) {
+            top.layer.msg("仅支持wav格式文件", {icon: 2});
+            return false;
+        }
+
+        var maxSize = 50 * 1024 * 1024; // 50MB
+        if (file.size > maxSize) {
+            top.layer.msg("文件大小不能超过50MB", {icon: 2});
+            return false;
         }
-    </script>
+
+        return true;
+    }
+
+    // 带进度条上传
+    function uploadFileWithProgress(file, fieldId, rootId, callback) {
+        var formData = new FormData();
+        formData.append('file', file);
+        formData.append('rootId', rootId);
+
+        $('#uploadProgress').show();
+
+        $.ajax({
+            url: ctx + 'cc/ivr/uploadVoice', // 复用IVR的上传接口
+            type: 'POST',
+            data: formData,
+            processData: false,
+            contentType: false,
+            xhr: function() {
+                var xhr = $.ajaxSettings.xhr();
+                if (xhr.upload) {
+                    xhr.upload.addEventListener('progress', function(e) {
+                        if (e.lengthComputable) {
+                            var percent = Math.floor((e.loaded / e.total) * 100);
+                            $('.progress-bar').css('width', percent + '%').text(percent + '%');
+                        }
+                    }, false);
+                }
+                return xhr;
+            },
+            success: function(rsp) {
+                if (rsp.code === 0 || rsp.code === 200) {
+                    callback(true, rsp.data);
+                } else {
+                    callback(false, rsp.msg);
+                }
+            },
+            error: function(xhr, status, error) {
+                callback(false, "网络错误:" + error);
+            }
+        });
+    }
+
+    // 显示音频预览
+    function showAudioPreview(fieldId, fileUrl) {
+        var previewClass = '.' + fieldId + 'AudioPreview';
+        var $preview = $(previewClass);
+
+        if ($preview.length && fileUrl) {
+            $preview.find('audio source').attr('src', ctx + fileUrl);
+            $preview.find('audio')[0].load();
+            $preview.show();
+        }
+    }
+
+</script>
 </body>
 </html>

+ 26 - 23
ruoyi-admin/src/main/resources/templates/aicall/callPhone/callPhone.html

@@ -225,14 +225,14 @@
                         <li>
                             <label th:text="#{callPhone.query.callTask}">外呼任务</label>
                             <select name="batchId" id="callTaskSelect">
-                                <option value="">全部</option>
+                                <option value="" th:text="#{options.all}"></option>
                             </select>
                         </li>
                         <!-- 客户意向下拉框 -->
                         <li>
                             <label th:text="#{callPhone.query.intent}">客户意向</label>
                             <select name="intent">
-                                <option value="">全部</option>
+                                <option value="" th:text="#{options.all}"></option>
                                 <option value="A">A</option>
                                 <option value="B">B</option>
                                 <option value="C">C</option>
@@ -416,11 +416,11 @@
                         }
                     }
                 },
-                // {
-                //     field: 'intent',
-                //     title: i18n('callPhone.table.intent'),
-                //     width: 60,
-                // },
+                {
+                    field: 'intent',
+                    title: i18n('callPhone.table.intent'),
+                    width: 60,
+                },
                 {
                     field: 'calloutTime',
                     title: i18n('callPhone.table.calloutTime'),
@@ -510,9 +510,9 @@
                         const seconds = timeLen % 60;
                         // 返回格式化后的时间字符串
                         if (minutes > 0) {
-                            return `${minutes}分${seconds}秒`;
+                            return `${minutes}` + i18n("common.time.minute") + `${seconds}` + i18n("common.time.second");
                         } else {
-                            return `${seconds}`;
+                            return `${seconds}`  + i18n("common.time.second");
                         }
                     }
                 },
@@ -525,11 +525,11 @@
                         return `<div class="hangup-cause-cell" data-toggle="tooltip" data-placement="top" title="${value}" data-full-text="${value}">${value}</div>`;
                     }
                 },
-                {
-                    field: 'ivrDtmfDigits',
-                    title: i18n('callPhone.table.ivrDtmfDigits'),
-                    width: 80,
-                },
+                // {
+                //     field: 'ivrDtmfDigits',
+                //     title: i18n('callPhone.table.ivrDtmfDigits'),
+                //     width: 80,
+                // },
                 {
                     title: i18n('callPhone.table.opra'),
                     align: 'center',
@@ -566,7 +566,7 @@
                     // 优先 ClipboardAPI
                     if (navigator.clipboard && window.isSecureContext) {
                         navigator.clipboard.writeText(text)
-                            .then(() => $.modal.msgSuccess('已复制到剪贴板'))
+                            .then(() => $.modal.msgSuccess(i18n("common.msg.copy.success")))
                             .catch(() => fallbackCopy(text));
                     } else {
                         fallbackCopy(text);
@@ -582,9 +582,9 @@
                         textArea.select();
                         try {
                             document.execCommand('copy');
-                            $.modal.msgSuccess('已复制到剪贴板');
+                            $.modal.msgSuccess(i18n("common.msg.copy.success"));
                         } catch (err) {
-                            $.modal.msgError('复制失败,请手动复制');
+                            $.modal.msgError(i18n("common.msg.copy.fail"));
                         }
                         document.body.removeChild(textArea);
                     }
@@ -609,6 +609,9 @@
                 html += '<option value="' + task.batchId + '">' + task.batchName + '</option>';
             });
             $('#callTaskSelect').html(html);
+
+            // 下拉框加载完成后,自动触发查询
+            $.table.search();
         });
 
 
@@ -657,7 +660,7 @@
                 openDialog(escapedDialogContent);
             }
         }else{
-            $.modal.msgError('不支持的媒体格式');
+            $.modal.msgError(i18n("common.msg.unknown.media"));
         }
     }
 
@@ -693,12 +696,12 @@
                     window.location.href = url;
                 } else {
                     // 文件不存在,显示错误消息
-                    $.modal.msgError('文件不存在!');
+                    $.modal.msgError(i18n("common.msg.file.notexists"));
                 }
             })
             .catch(() => {
                 // 网络错误或文件不存在
-                $.modal.msgError('文件不存在!');
+                $.modal.msgError(i18n("common.msg.file.notexists"));
             });
     }
 
@@ -795,7 +798,7 @@
                 contentDiv.appendChild(bubble);
                 roleIconElement.style.paddingLeft = "12px";
                 contentDiv.appendChild(roleIconElement);
-                roleElement.textContent = "客户";
+                roleElement.textContent = i18n("tips.role.cust");
                 contentDiv.appendChild(roleElement);
             } else if (role === 'assistant') {
                 contentDiv.appendChild(roleIconElement);
@@ -806,14 +809,14 @@
                 contentDiv.appendChild(bubble);
             } else if (role === 'agent') {
                 contentDiv.appendChild(roleIconElement);
-                roleElement.textContent = "坐席";
+                roleElement.textContent = i18n("tips.role.agent");
                 roleElement.style.paddingRight = "12px";
                 contentDiv.appendChild(roleElement);
                 bubble.appendChild(contentSpan);
                 contentDiv.appendChild(bubble);
             } else {
                 contentDiv.appendChild(roleIconElement);
-                roleElement.textContent = "知识库查询";
+                roleElement.textContent = i18n("tips.role.kb");
                 roleElement.style.paddingRight = "12px";
                 contentDiv.appendChild(roleElement);
                 bubble.appendChild(contentSpan);

+ 767 - 316
ruoyi-admin/src/main/resources/templates/aicall/callTask/add.html

@@ -1,419 +1,870 @@
 <!DOCTYPE html>
 <html lang="zh" xmlns:th="http://www.thymeleaf.org" >
 <head>
-    <th:block th:include="include :: header('新增外呼任务')" />
+    <th:block th:include="include :: header('修改外呼任务')" />
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-callTask-add">
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.batchName}"></label>
-                    <div class="col-sm-8">
-                        <input name="batchName" class="form-control" type="text" required>
-                    </div>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-callTask-add" th:object="${ccCallTask}">
+        <input name="batchId" th:field="*{batchId}" type="hidden">
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.batchName}"></label>
+                <div class="col-sm-8">
+                    <input name="batchName" th:field="*{batchName}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.taskType}"></label>
-                    <div class="col-sm-6">
-                        <select name="taskType" id="taskType" class="form-control" required>
-                            <option value="0" th:text="#{callTask.form.taskType0}"></option>
-                            <option value="1" th:text="#{callTask.form.taskType1}"></option>
-                            <option value="2" th:text="#{callTask.form.taskType2}"></option>
-                            <option value="3" th:text="#{callTask.form.taskType3}"></option>
-                        </select>
-                    </div>
-                    <div class="col-sm-2">
-                        <!-- 添加下载模板的链接 -->
-                        <a href="javascript:void(0);" onclick="downloadTemplate()">
-                            <span th:text="#{callTask.form.downloadTemplate}">下载模板</span>
-                        </a>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.threadNum}"></label>
+                <div class="col-sm-8">
+                    <input name="threadNum" th:field="*{threadNum}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.conntectRate}"></label>
-                    <div class="col-sm-8">
-                        <input name="conntectRate" class="form-control" type="number" min="0" max="100" step="1" required>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.taskType}"></label>
+                <div class="col-sm-6">
+                    <select name="taskType" id="taskType"  th:field="*{taskType}" class="form-control" required>
+                        <option value="0" th:text="#{callTask.form.taskType0}"></option>
+                        <option value="1" th:text="#{callTask.form.taskType1}"></option>
+                        <option value="2" th:text="#{callTask.form.taskType2}"></option>
+                        <option value="3" th:text="#{callTask.form.taskType3}"></option>
+                    </select>
+                </div>
+                <div class="col-sm-2">
+                    <!-- 添加下载模板的链接 -->
+                    <a href="javascript:void(0);" onclick="downloadTemplate()">
+                        <span th:text="#{callTask.form.downloadTemplate}">下载模板</span>
+                    </a>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgRingTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgRingTimeLen" class="form-control" type="text" required>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.autoStop}"></label>
+                <div class="col-sm-8">
+                    <select name="autoStop" th:field="*{autoStop}" class="form-control" required>
+                        <option value="1" th:text="#{callTask.form.autoStop1}"></option>
+                        <option value="0" th:text="#{callTask.form.autoStop0}"></option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallTalkTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgCallTalkTimeLen" class="form-control" type="text" required>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.conntectRate}"></label>
+                <div class="col-sm-8">
+                    <input name="conntectRate" th:field="*{conntectRate}" class="form-control" type="number" min="0" max="100" step="1" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallEndProcessTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgCallEndProcessTimeLen" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgRingTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgRingTimeLen" th:field="*{avgRingTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.threadNum}"></label>
-                    <div class="col-sm-8">
-                        <input name="threadNum" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallTalkTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgCallTalkTimeLen" th:field="*{avgCallTalkTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.gatewayId}"></label>
-                    <div class="col-sm-8">
-                        <select name="gatewayId" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.gatewayId.empty}"></option>
-                            <!-- 网关选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallEndProcessTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgCallEndProcessTimeLen" th:field="*{avgCallEndProcessTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-
-
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.autoStop}"></label>
-                    <div class="col-sm-8">
-                        <select name="autoStop" class="form-control" required>
-                            <option value="1" th:text="#{callTask.form.autoStop1}"></option>
-                            <option value="0" th:text="#{callTask.form.autoStop0}"></option>
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.gatewayId}"></label>
+                <div class="col-sm-8">
+                    <select name="gatewayId" th:field="*{gatewayId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.gatewayId.empty}"></option>
+                        <!-- 网关选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.llmAccountId}"></label>
-                    <div class="col-sm-8">
-                        <select name="llmAccountId" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.llmAccountId.empty}"></option>
-                            <!-- 大模型底座选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.llmAccountId}"></label>
+                <div class="col-sm-8">
+                    <select name="llmAccountId" id="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.llmAccountId.empty}"></option>
+                        <!-- 大模型底座选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceSource}"></label>
-                    <div class="col-sm-8">
-                        <select name="voiceSource" class="form-control" id="voiceSourceSelect" required>
-                            <option value="" th:text="#{callTask.form.voiceSource.empty}"></option>
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12 voice-config">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceSource}"></label>
+                <div class="col-sm-8">
+                    <select name="voiceSource" id="voiceSource" th:field="*{voiceSource}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.voiceSource.empty}"></option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceCode}"></label>
-                    <div class="col-sm-8">
-                        <select name="voiceCode" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.voiceCode.empty}" ></option>
-                            <!-- 音色选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+
+        <!-- 新增:TTS语言选择 -->
+        <div class="col-xs-12 voice-config" id="ttsLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ttsLanguageCode}">TTS语言</label>
+                <div class="col-sm-8">
+                    <select name="ttsLanguageCode" id="ttsLanguageCode" th:field="*{ttsLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ttsLanguageCode.empty}">请选择TTS语言</option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrProvider}"></label>
-                    <div class="col-sm-8">
-                        <select name="asrProvider" class="form-control" id="asrProviderSelect" required>
-                            <option value="" th:text="#{callTask.form.asrProvider.empty}"></option>
-                        </select>
-                    </div>
+        </div>
+
+        <!-- 新增:TTS模型选择 -->
+        <div class="col-xs-12 voice-config" id="ttsModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ttsModels}">TTS模型</label>
+                <div class="col-sm-8">
+                    <select name="ttsModels" id="ttsModels" th:field="*{ttsModels}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ttsModels.empty}">请选择TTS模型</option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.playTimes}"></label>
-                    <div class="col-sm-8">
-                        <input name="playTimes" class="form-control" type="text" required>
-                    </div>
+        </div>
+
+        <div class="col-xs-12 voice-config">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceCode}"></label>
+                <div class="col-sm-8">
+                    <select name="voiceCode" id="voiceCode" th:field="*{voiceCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.voiceCode.empty}"></option>
+                        <!-- 音色选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrProvider}"></label>
+                <div class="col-sm-8">
+                    <select name="asrProvider" id="asrProvider" th:field="*{asrProvider}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrProvider.empty}"></option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR语言选择 -->
+        <div class="col-xs-12" id="asrLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrLanguageCode}">ASR语言</label>
+                <div class="col-sm-8">
+                    <select name="asrLanguageCode" id="asrLanguageCode" th:field="*{asrLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrLanguageCode.empty}">请选择ASR语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR模型选择 -->
+        <div class="col-xs-12" id="asrModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrModels}">ASR模型</label>
+                <div class="col-sm-8">
+                    <select name="asrModels" id="asrModels" th:field="*{asrModels}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrModels.empty}">请选择ASR模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
 
-
-            <div class="col-xs-12" id="aiTransferTypeDiv">
-                <div class="form-group" style="display: none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferType}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferType" id="aiTransferTypeSelect" class="form-control" required>
-                            <option value="acd" th:text="#{callTask.form.aiTransferTypeACD}"></option>
-                            <option value="extension" th:text="#{callTask.form.aiTransferTypeExtension}"></option>
-                            <option value="gateway" th:text="#{callTask.form.aiTransferTypeGateway}"></option>
-                        </select>
-                    </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.playTimes}"></label>
+                <div class="col-sm-8">
+                    <input name="playTimes" th:field="*{playTimes}" class="form-control" type="text" required>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-xs-12" id="aiTransferTypeDiv" >
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferType}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferType" th:field="*{aiTransferType}" id="aiTransferTypeSelect" class="form-control" required>
+                        <option value="acd" th:text="#{callTask.form.aiTransferTypeACD}"></option>
+                        <option value="extension" th:text="#{callTask.form.aiTransferTypeExtension}"></option>
+                        <option value="gateway" th:text="#{callTask.form.aiTransferTypeGateway}"></option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field acd-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGroupId}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferGroupId" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.aiTransferGroupId.empty}"></option>
-                            <!-- 业务组选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field acd-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGroupId}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferGroupId" th:field="*{aiTransferGroupId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.aiTransferGroupId.empty}"></option>
+                        <!-- 业务组选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayId}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferGatewayId" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.aiTransferGatewayId.empty}"></option>
-                            <!-- 业务组选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayId}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferGatewayId" th:field="*{aiTransferGatewayId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.aiTransferGatewayId.empty}"></option>
+                        <!-- 业务组选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayDestNumber}"></label>
-                    <div class="col-sm-8">
-                        <input name="aiTransferGatewayDestNumber" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayDestNumber}"></label>
+                <div class="col-sm-8">
+                    <input name="aiTransferGatewayDestNumber" th:field="*{aiTransferGatewayDestNumber}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field extension-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferExtNumber}"></label>
-                    <div class="col-sm-8">
-                        <input name="aiTransferExtNumber" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field extension-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferExtNumber}"></label>
+                <div class="col-sm-8">
+                    <input name="aiTransferExtNumber" th:field="*{aiTransferExtNumber}" class="form-control" type="text" required>
                 </div>
             </div>
+        </div>
 
 
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ivrId}"></label>
-                    <div class="col-sm-8">
-                        <select name="ivrId" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.ivrId.empty}"></option>
-                            <!-- IVR选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ivrId}"></label>
+                <div class="col-sm-8">
+                    <select name="ivrId" th:field="*{ivrId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ivrId.empty}"></option>
+                        <!-- IVR选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
+        </div>
+
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+<script th:inline="javascript">
+
+    var prefix = ctx + "aicall/callTask";
+    // 存储llmAccount数据,用于判断providerClassName
+    var llmAccountMap = {};
+
+    $(function() {
+        // 加载网关下拉框数据
+        $.ajax({
+            url: ctx + "cc/gateways/outbound",
+            success: function(rsp) {
+                var gatewaySelect = $('select[name="gatewayId"]');
+                gatewaySelect.empty();
+                gatewaySelect.append('<option value="">' + i18n('callTask.form.gatewayId.empty') + '</option>');
+                rsp.data.forEach(function(gateway) {
+                    gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+                });
+                // 设置网关下拉框的选中项
+                var gatewayId = /*[[${ccCallTask.gatewayId}]]*/ '';
+                if (gatewayId) {
+                    gatewaySelect.val(gatewayId);
+                }
 
-        </form>
-    </div>
-    <th:block th:include="include :: footer" />
-    <script th:inline="javascript">
-        var prefix = ctx + "aicall/callTask";
 
-        $(function() {
-            // 加载网关下拉框数据
-            $.ajax({
-                url: ctx + "cc/gateways/outbound",
-                success: function(rsp) {
-                    var gatewaySelect = $('select[name="gatewayId"]');
-                    gatewaySelect.empty();
-                    gatewaySelect.append('<option value="">' + i18n('callTask.form.gatewayId.empty') + '</option>');
-                    rsp.data.forEach(function(gateway) {
-                        gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+
+                var gatewaySelect = $('select[name="aiTransferGatewayId"]');
+                gatewaySelect.empty();
+                gatewaySelect.append('<option value="">' + i18n('callTask.form.aiTransferGatewayId.empty') + '</option>');
+                rsp.data.forEach(function(gateway) {
+                    gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+                });
+                // 回显已保存的aiTransferGatewayId
+                var aiTransferGatewayId = /*[[${ccCallTask.aiTransferGatewayId}]]*/ '';
+                if (aiTransferGatewayId) {
+                    gatewaySelect.val(aiTransferGatewayId);
+                }
+            }
+        });
+
+        // 加载TTS提供商下拉框数据
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/tts/voiceSource/all",
+            success: function(rsp) {
+                var voiceSourceSelect = $('#voiceSource');
+                voiceSourceSelect.empty();
+                // 遍历 map 对象,key 作为 value,value 作为显示文本
+                Object.entries(rsp.data).forEach(function([key, value]) {
+                    voiceSourceSelect.append($("<option>").attr("value", key).text(value));
+                });
+                // 回显已保存的voiceSource并加载对应语言和音色
+                var savedVoiceSource = /*[[${ccCallTask.voiceSource}]]*/ '';
+                if (savedVoiceSource) {
+                    voiceSourceSelect.val(savedVoiceSource);
+                    loadTtsLanguages(savedVoiceSource, function() {
+                        var savedTtsLanguageCode = /*[[${ccCallTask.ttsLanguageCode}]]*/ '';
+                        if (savedTtsLanguageCode) {
+                            $('#ttsLanguageCode').val(savedTtsLanguageCode);
+                            loadTtsModels(savedVoiceSource, savedTtsLanguageCode, function() {
+                                var savedTtsModels = /*[[${ccCallTask.ttsModels}]]*/ '';
+                                if (savedTtsModels) {
+                                    $('#ttsModels').val(savedTtsModels);
+                                }
+                                loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, savedTtsModels);
+                            });
+                        }
                     });
                 }
-            });
+            }
+        });
+
+
+        // 加载ASR提供商下拉框数据
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/provider/all",
+            success: function(rsp) {
+                var providerSelect = $('#asrProvider');
+                providerSelect.empty();
+                // 遍历 map 对象,key 作为 value,value 作为显示文本
+                Object.entries(rsp.data).forEach(function([key, value]) {
+                    providerSelect.append($("<option>").attr("value", key).text(value));
+                });
+                // 回显已保存的asrprovider并加载对应语言
+                var savedProvider = /*[[${ccCallTask.asrProvider}]]*/ '';
+                if (savedProvider) {
+                    providerSelect.val(savedProvider);
+                    loadAsrLanguages(savedProvider, function() {
+                        var savedAsrLanguageCode = /*[[${ccCallTask.asrLanguageCode}]]*/ '';
+                        if (savedAsrLanguageCode) {
+                            $('#asrLanguageCode').val(savedAsrLanguageCode);
+                            loadAsrModels(savedProvider, savedAsrLanguageCode, function() {
+                                var savedAsrModels = /*[[${ccCallTask.asrModels}]]*/ '';
+                                if (savedAsrModels) {
+                                    $('#asrModels').val(savedAsrModels);
+                                }
+                            });
+                        }
+                    });
+                }
+            }
+        });
+
+
+        // 根据TTS厂商加载语言列表
+        function loadTtsLanguages(voiceSource, callback) {
+            var languageSelect = $('#ttsLanguageCode');
+            var languageDiv = $('#ttsLanguageCodeDiv');
+            var modelsSelect = $('#ttsModels');
+            var modelsDiv = $('#ttsModelsDiv');
+
+            if (!voiceSource) {
+                languageSelect.empty().append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                languageDiv.hide();
+                // 清空并隐藏模型下拉框
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载音色提供商下拉框数据
             $.ajax({
-                url: ctx + "aicall/ttsAliyun/tts/voiceSource/all",
+                url: ctx + "aicall/ttsAliyun/tts/language",
+                data: { voiceSource: voiceSource },
                 success: function(rsp) {
-                    var voiceSourceSelect = $('#voiceSourceSelect');
-                    voiceSourceSelect.empty();
-                    voiceSourceSelect.append('<option value="">' + i18n('callTask.form.voiceSource.empty') + '</option>');
-                    // 遍历 map 对象,key 作为 value,value 作为显示文本
-                    Object.entries(rsp.data).forEach(function([key, value]) {
-                        voiceSourceSelect.append($("<option>").attr("value", key).text(value));
-                    });
+                    languageSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var langCode = item.code || item.key || item.value;
+                            languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                            languageSelect.val(langCode);
+                            languageDiv.hide();
+                            // 单条数据默认选中时也要加载models
+                            loadTtsModels(voiceSource, langCode, callback);
+                        } else {
+                            languageSelect.append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            languageDiv.show();
+                            // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                            modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                            modelsDiv.hide();
+                            if (callback) callback();
+                        }
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                        languageDiv.show();
+                        // 无数据时隐藏模型下拉框
+                        modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
                 }
             });
+        }
+
+        // 根据ASR厂商加载语言列表
+        function loadAsrLanguages(asrProvider, callback) {
+            var languageSelect = $('#asrLanguageCode');
+            var languageDiv = $('#asrLanguageCodeDiv');
+            var modelsSelect = $('#asrModels');
+            var modelsDiv = $('#asrModelsDiv');
+
+            if (!asrProvider) {
+                languageSelect.empty().append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                languageDiv.hide();
+                // 清空并隐藏模型下拉框
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载语音识别提供商下拉框数据
             $.ajax({
-                url: ctx + "aicall/ttsAliyun/asr/provider/all",
+                url: ctx + "aicall/ttsAliyun/asr/language",
+                data: { asrProvider: asrProvider },
                 success: function(rsp) {
-                    var providerSelect = $('#asrProviderSelect');
-                    providerSelect.empty();
-                    providerSelect.append('<option value="">' + i18n('callTask.form.asrProvider.empty') + '</option>');
-                    // 遍历 map 对象,key 作为 value,value 作为显示文本
-                    Object.entries(rsp.data).forEach(function([key, value]) {
-                        providerSelect.append($("<option>").attr("value", key).text(value));
-                    });
+                    languageSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var langCode = item.code || item.key || item.value;
+                            languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                            languageSelect.val(langCode);
+                            languageDiv.hide();
+                            // 单条数据默认选中时也要加载models
+                            loadAsrModels(asrProvider, langCode, callback);
+                        } else {
+                            languageSelect.append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            languageDiv.show();
+                            // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                            modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                            modelsDiv.hide();
+                            if (callback) callback();
+                        }
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                        languageDiv.show();
+                        // 无数据时隐藏模型下拉框
+                        modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
                 }
             });
+        }
 
-            // 根据提供商加载音色下拉框数据
-            $(document).on('change', '#voiceSourceSelect', function() {
-                var voiceSource = $(this).val();
-                var voiceCodeSelect = $('select[name="voiceCode"]');
+        // 根据TTS厂商和语言加载TTS模型
+        function loadTtsModels(voiceSource, ttsLanguageCode, callback) {
+            var modelsSelect = $('#ttsModels');
+            var modelsDiv = $('#ttsModelsDiv');
 
-                if (!voiceSource) {
-                    voiceCodeSelect.empty();
-                    voiceCodeSelect.append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
-                    $('#voiceSource').val('');
-                    return;
-                }
+            if (!voiceSource || !ttsLanguageCode) {
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-                $.ajax({
-                    url: ctx + "aicall/ttsAliyun/getByVoiceSource",
-                    data: { voiceSource: voiceSource },
-                    success: function(rsp) {
-                        voiceCodeSelect.empty();
-                        rsp.data.forEach(function(voice) {
-                            voiceCodeSelect.append($("<option>")
-                                .attr("value", voice.voiceCode)
-                                .attr("data-source", voice.voiceSource)
-                                .text(voice.voiceName));
-                        });
-                        $('#voiceSource').val('');
+            $.ajax({
+                url: ctx + "aicall/ttsAliyun/tts/models",
+                data: {
+                    voiceSource: voiceSource,
+                    ttsLanguageCode: ttsLanguageCode
+                },
+                success: function(rsp) {
+                    modelsSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var modelValue = item.code || item.key || item.value;
+                            modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                            modelsSelect.val(modelValue);
+                            modelsDiv.hide(); // 单值时隐藏
+                        } else {
+                            modelsSelect.append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            modelsDiv.show(); // 多值时显示
+                        }
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide(); // 无数据时隐藏
                     }
-                });
+
+                    if (callback) callback();
+                }
             });
+        }
+
+        // 根据ASR厂商和语言加载ASR模型
+        function loadAsrModels(asrProvider, asrLanguageCode, callback) {
+            var modelsSelect = $('#asrModels');
+            var modelsDiv = $('#asrModelsDiv');
+
+            if (!asrProvider || !asrLanguageCode) {
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载大模型底座下拉框数据
             $.ajax({
-                url: ctx + "aicall/account/all",
+                url: ctx + "aicall/ttsAliyun/asr/models",
+                data: {
+                    asrProvider: asrProvider,
+                    asrLanguageCode: asrLanguageCode
+                },
                 success: function(rsp) {
-                    var llmAccountSelect = $('select[name="llmAccountId"]');
-                    llmAccountSelect.empty();
-                    llmAccountSelect.append('<option value="">' + i18n('callTask.form.llmAccountId.empty') + '</option>');
-                    rsp.data.forEach(function(llmAccount) {
-                        llmAccountSelect.append($("<option>").attr("value", llmAccount.id).text(llmAccount.name));
-                    });
+                    modelsSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var modelValue = item.code || item.key || item.value;
+                            modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                            modelsSelect.val(modelValue);
+                            modelsDiv.hide(); // 单值时隐藏
+                        } else {
+                            modelsSelect.append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            modelsDiv.show(); // 多值时显示
+                        }
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide(); // 无数据时隐藏
+                    }
+
+                    if (callback) callback();
                 }
             });
+        }
 
-            // 加载IVR下拉框数据
+        // 根据TTS厂商、语言和模型加载音色
+        function loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels) {
+            var voiceSelect = $('select[name="voiceCode"]');
+            if (!voiceSource || !ttsLanguageCode) {
+                voiceSelect.empty().append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
+                return;
+            }
+            var requestData = {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode
+            };
+            if (ttsModels) {
+                requestData.ttsModels = ttsModels;
+            }
             $.ajax({
-                url: ctx + "cc/ivr/all",
+                url: ctx + "aicall/ttsAliyun/getByLanguageCode",
+                data: requestData,
                 success: function(rsp) {
-                    var ivrIdSelect = $('select[name="ivrId"]');
-                    ivrIdSelect.empty();
-                    ivrIdSelect.append('<option value="">' + i18n('callTask.form.ivrId.empty') + '</option>');
-                    rsp.data.forEach(function(ccIvr) {
-                        ivrIdSelect.append($("<option>").attr("value", ccIvr.id).text(ccIvr.ivrNodeName));
+                    voiceSelect.empty();
+                    rsp.data.forEach(function(voice) {
+                        voiceSelect.append($("<option>")
+                            .attr("value", voice.voiceCode)
+                            .attr("data-source", voice.voiceSource)
+                            .text(voice.voiceName));
                     });
+                    // 回显已保存的音色
+                    var voiceCode = /*[[${ccCallTask.voiceCode}]]*/ '';
+                    if (voiceCode) {
+                        voiceSelect.val(voiceCode);
+                    }
                 }
             });
+        }
 
-            // 加载业务组下拉框数据
-            $.ajax({
-                url: ctx + "cc/bizgroup/all",
-                success: function(rsp) {
-                    var groupSelect = $('select[name="aiTransferGroupId"]');
-                    groupSelect.empty();
-                    groupSelect.append('<option value="">' + i18n('callTask.form.aiTransferGroupId.empty') + '</option>');
-                    rsp.data.forEach(function(bizGroup) {
-                        groupSelect.append($("<option>").attr("value", bizGroup.groupId).text(bizGroup.bizGroupName));
+        // TTS厂商变更事件
+        $(document).on('change', '#voiceSource', function() {
+            var voiceSource = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#ttsModels').empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+            $('#ttsModelsDiv').hide();
+
+            loadTtsLanguages(voiceSource, function() {
+                // 语言加载完成后,自动加载模型和音色(如果只有一个语言选项)
+                var ttsLanguageCode = $('#ttsLanguageCode').val();
+                if (ttsLanguageCode) {
+                    loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                        var ttsModels = $('#ttsModels').val();
+                        loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
                     });
+                } else {
+                    $('select[name="voiceCode"]').empty().append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
                 }
             });
+        });
 
-            // 监听 taskType 的变化
-            $('select[name="taskType"]').change(function() {
-                var taskType = $(this).val();
-                // 隐藏所有字段
-                $('select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], input[name="playTimes"], select[name="aiTransferType"], select[name="ivrId"]').parent().parent().hide();
-
-                // 根据 taskType 显示对应的字段
-                if (taskType === "0") {
-                    $('input[name="threadNum"], select[name="gatewayId"], select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], select[name="aiTransferType"]').parent().parent().show();
-                } else if (taskType === "1") {
-                    $('input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], select[name="aiTransferType"]').parent().parent().show();
-                } else if (taskType === "2") {
-                    $('input[name="threadNum"], select[name="gatewayId"], input[name="playTimes"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], select[name="aiTransferType"]').parent().parent().show();
-                } else if (taskType === "3") {
-                    $('input[name="threadNum"], select[name="gatewayId"], select[name="ivrId"]').parent().parent().show();
-                }
+        // TTS语言变更事件
+        $(document).on('change', '#ttsLanguageCode', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $(this).val();
+            loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                var ttsModels = $('#ttsModels').val();
+                loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+            });
+        });
+
+        // TTS模型变更事件
+        $(document).on('change', '#ttsModels', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $('#ttsLanguageCode').val();
+            var ttsModels = $(this).val();
+            loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+        });
 
-                toggleTransferFields();
+        // ASR厂商变更事件
+        $(document).on('change', '#asrProvider', function() {
+            var asrProvider = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#asrModels').empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+            $('#asrModelsDiv').hide();
+
+            loadAsrLanguages(asrProvider, function() {
+                // 语言加载完成后,自动加载模型(如果只有一个语言选项)
+                var asrLanguageCode = $('#asrLanguageCode').val();
+                if (asrLanguageCode) {
+                    loadAsrModels(asrProvider, asrLanguageCode);
+                }
             });
+        });
 
-            // 默认选中AI外呼
-            $("select[name='taskType']").val("1");
-            // 初始化时根据默认值显示字段
-            $('select[name="taskType"]').trigger('change');
+        // ASR语言变更事件
+        $(document).on('change', '#asrLanguageCode', function() {
+            var asrProvider = $('#asrProvider').val();
+            var asrLanguageCode = $(this).val();
+            loadAsrModels(asrProvider, asrLanguageCode);
+        });
 
+        // 加载大模型底座下拉框数据
+        $.ajax({
+            url: ctx + "aicall/account/all",
+            success: function(rsp) {
+                var llmAccountSelect = $('select[name="llmAccountId"]');
+                llmAccountSelect.empty();
+                llmAccountSelect.append('<option value="">' + i18n('callTask.form.llmAccountId.empty') + '</option>');
+                rsp.data.forEach(function(llmAccount) {
+                    llmAccountSelect.append($("<option>")
+                        .attr("value", llmAccount.id)
+                        .attr("data-provider", llmAccount.providerClassName)
+                        .text(llmAccount.name));
+                    llmAccountMap[llmAccount.id] = llmAccount;
+                });
+                var llmAccountId = /*[[${ccCallTask.llmAccountId}]]*/ '';
+                if (llmAccountId) {
+                    llmAccountSelect.val(llmAccountId);
+                    checkAndToggleVoiceFields(llmAccountId);
+                }
+            }
+        });
 
+        // 监听大模型底座变更
+        $(document).on('change', '#llmAccountId', function() {
+            checkAndToggleVoiceFields($(this).val());
+        });
 
-            // 绑定AI转接类型改变事件
-            $(document).on('change', '#aiTransferTypeSelect', function() {
-                toggleTransferFields();
-            });
+        // 加载IVR下拉框数据
+        $.ajax({
+            url: ctx + "cc/ivr/all",
+            success: function(rsp) {
+                var ivrIdSelect = $('select[name="ivrId"]');
+                ivrIdSelect.empty();
+                ivrIdSelect.append('<option value="">' + i18n('callTask.form.ivrId.empty') + '</option>');
+                rsp.data.forEach(function(ccIvr) {
+                    ivrIdSelect.append($("<option>").attr("value", ccIvr.id).text(ccIvr.ivrNodeName));
+                });
+                var ivrId = /*[[${ccCallTask.ivrId}]]*/ '';
+                if (ivrId) {
+                    ivrIdSelect.val(ivrId);
+                }
+            }
+        });
 
+        // 加载业务组下拉框数据
+        $.ajax({
+            url: ctx + "cc/bizgroup/all",
+            success: function(rsp) {
+                var groupSelect = $('select[name="aiTransferGroupId"]');
+                groupSelect.empty();
+                groupSelect.append('<option value="">' + i18n('callTask.form.aiTransferGroupId.empty') + '</option>');
+                rsp.data.forEach(function(bizGroup) {
+                    groupSelect.append($("<option>").attr("value", bizGroup.groupId).text(bizGroup.bizGroupName));
+                });
+                // 设置音色下拉框的选中项
+                var groupId = /*[[${ccCallTask.aiTransferGroupId}]]*/ '';
+                if (groupId) {
+                    groupSelect.val(groupId);
+                }
+            }
+        });
 
-            // 初始化转接字段显示状态
+        // 监听 taskType 的变化
+        $('select[name="taskType"]').change(function() {
+            var taskType = $(this).val();
+            changeTaskType(taskType);
             toggleTransferFields();
-
         });
 
-        // 表单验证
-        $("#form-callTask-add").validate({
-            focusCleanup: true
+        // 初始化时根据默认值显示字段
+        $('select[name="taskType"]').trigger('change');
+
+
+
+        // 绑定AI转接类型改变事件
+        $(document).on('change', '#aiTransferTypeSelect', function() {
+            toggleTransferFields();
         });
 
-        // 提交处理
-        function submitHandler() {
-            if ($.validate.form()) {
-                $.operate.save(prefix + "/add", $('#form-callTask-add').serialize());
-            }
+
+        // 初始化转接字段显示状态
+        toggleTransferFields();
+
+    });
+
+    $("#form-callTask-add").validate({
+        focusCleanup: true
+    });
+
+    function changeTaskType(taskType) {
+
+        console.log("=========taskType:" + taskType);
+
+        // 隐藏所有字段
+        $('select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], input[name="playTimes"], select[name="aiTransferType"], select[name="ivrId"], #ttsLanguageCode, #asrLanguageCode, #ttsModels, #asrModels').parent().parent().hide();
+
+        // 根据 taskType 显示对应的字段
+        if (taskType === "0") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], select[name="aiTransferType"]').parent().parent().show();
+        } else if (taskType === "1") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], #ttsLanguageCode, #ttsModels, select[name="asrProvider"], #asrLanguageCode, #asrModels, select[name="aiTransferType"]').parent().parent().show();
+            checkAndToggleVoiceFields($('#llmAccountId').val());
+        } else if (taskType === "2") {
+            $('input[name="threadNum"], select[name="gatewayId"], input[name="playTimes"], select[name="voiceCode"], select[name="voiceSource"], #ttsLanguageCode, #ttsModels, select[name="asrProvider"], #asrLanguageCode, #asrModels, select[name="aiTransferType"]').parent().parent().show();
+        } else if (taskType === "3") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="ivrId"]').parent().parent().show();
+        }
+
+        // 根据当前选项数量重新判断语言和模型下拉框是否显示
+        var ttsLanguageCount = $('#ttsLanguageCode option').length;
+        var asrLanguageCount = $('#asrLanguageCode option').length;
+        var ttsModelsCount = $('#ttsModels option').length;
+        var asrModelsCount = $('#asrModels option').length;
+
+        if (ttsLanguageCount <= 1) {
+            $('#ttsLanguageCodeDiv').hide();
         }
+        if (asrLanguageCount <= 1) {
+            $('#asrLanguageCodeDiv').hide();
+        }
+        if (ttsModelsCount <= 1) {
+            $('#ttsModelsDiv').hide();
+        }
+        if (asrModelsCount <= 1) {
+            $('#asrModelsDiv').hide();
+        }
+    }
 
-        // 下载模板
-        function downloadTemplate () {
-            var taskType = $("select[name='taskType']").val();
-            var templateUrl = prefix + "/downloadTemplate?taskType=" + taskType; // 假设模板文件放在静态资源目录下
-            window.location.href = templateUrl;
-        };
-
-        function toggleTransferFields() {
-            var taskType = $("select[name='taskType']").val();
-            var aiTransferType = $('#aiTransferTypeSelect').val();
-
-            // 先隐藏所有转接相关字段
-            $('#aiTransferTypeDiv').hide();
-            $('.transfer-field').hide();
-
-            // 显示AI转接类型
-            if (taskType === "1") {
-                $('#aiTransferTypeDiv').show();
-
-                // 根据转接类型显示对应字段
-                if (aiTransferType === 'acd') {
-                    $('.acd-transfer').show();
-                } else if (aiTransferType === 'gateway') {
-                    $('.gateway-transfer').show();
-                } else if (aiTransferType === 'extension') {
-                    $('.extension-transfer').show();
+    function submitHandler() {
+        if ($.validate.form()) {
+            $.operate.save(prefix + "/add", $('#form-callTask-add').serialize());
+        }
+    }
+
+    // 下载模板
+    function downloadTemplate () {
+        var taskType = $("select[name='taskType']").val();
+        var templateUrl = prefix + "/downloadTemplate?taskType=" + taskType; // 假设模板文件放在静态资源目录下
+        window.location.href = templateUrl;
+    };
+
+    function toggleTransferFields() {
+        var taskType = $("select[name='taskType']").val();
+        var aiTransferType = $('#aiTransferTypeSelect').val();
+
+        // 先隐藏所有转接相关字段
+        $('#aiTransferTypeDiv').hide();
+        $('.transfer-field').hide();
+
+        // 显示AI转接类型
+        if (taskType === "1") {
+            $('#aiTransferTypeDiv').show();
+
+            // 根据转接类型显示对应字段
+            if (aiTransferType === 'acd') {
+                $('.acd-transfer').show();
+            } else if (aiTransferType === 'gateway') {
+                $('.gateway-transfer').show();
+            } else if (aiTransferType === 'extension') {
+                $('.extension-transfer').show();
+            }
+        }
+    }
+
+    function checkAndToggleVoiceFields(llmAccountId) {
+        var llmAccount = llmAccountMap[llmAccountId];
+        if (llmAccount && llmAccount.providerClassName === 'LocalWavFile') {
+            // LocalWavFile时隐藏TTS相关字段,保留ASR
+            $('.voice-config').hide();
+            $('.voice-config select').prop('required', false);
+            // 清空TTS相关值
+            $('#voiceSource').val('');
+            $('#ttsLanguageCode').val('');
+            $('#ttsModels').val('');
+            $('#voiceCode').val('');
+        } else {
+            // 非LocalWavFile时显示TTS相关字段
+            var taskType = $('#taskType').val();
+            if (taskType === "1" || taskType === "2") {
+                $('.voice-config').show();
+                $('.voice-config select').prop('required', true);
+                // 根据当前选项数量重新判断语言和模型下拉框是否显示
+                var ttsLanguageCount = $('#ttsLanguageCode option').length;
+                var ttsModelsCount = $('#ttsModels option').length;
+                if (ttsLanguageCount <= 1) {
+                    $('#ttsLanguageCodeDiv').hide();
+                }
+                if (ttsModelsCount <= 1) {
+                    $('#ttsModelsDiv').hide();
                 }
             }
         }
-    </script>
+    }
+</script>
 </body>
 </html>

+ 9 - 9
ruoyi-admin/src/main/resources/templates/aicall/callTask/callTask.html

@@ -95,7 +95,7 @@
             success: function(rsp) {
                 var gatewaySelect = $("#gatewayIdSelect");
                 gatewaySelect.empty();
-                gatewaySelect.append($("<option>").attr("value", "").text("全部"))
+                gatewaySelect.append($("<option>").attr("value", "").text(i18n("options.all")))
                 rsp.data.forEach(function(gateway) {
                     gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
                     gatewayMap[gateway.id] = gateway.gwDesc;
@@ -108,7 +108,7 @@
             success: function(rsp) {
                 var voiceSelect = $("#voiceCodeSelect");
                 voiceSelect.empty();
-                voiceSelect.append($("<option>").attr("value", "").text("全部"));
+                voiceSelect.append($("<option>").attr("value", "").text(i18n("options.all")));
                 rsp.data.forEach(function(voice) {
                     voiceSelect.append($("<option>").attr("value", voice.voiceCode).text(voice.voiceName));
                     voiceMap[voice.voiceCode] = voice.voiceName;
@@ -252,7 +252,7 @@
             type: "POST",
             success: function(data) {
                 if (data.code == 0) {
-                    $.modal.msgSuccess("操作成功!");
+                    $.modal.msgSuccess(i18n("common.tip.success"));
                 } else if (data.code == 500) {
                     $.modal.msgError(data.msg);
                 }
@@ -267,7 +267,7 @@
             url: prefix + "/pause/" + id,
             type: "POST",
             success: function() {
-                $.modal.msgSuccess("操作成功!");
+                $.modal.msgSuccess(i18n("common.tip.success"));
                 $.table.refresh();
             }
         });
@@ -305,11 +305,11 @@
                     processData: false, // 告诉 jQuery 不要去处理发送的数据
                     contentType: false, // 告诉 jQuery 不要去设置 Content-Type 请求头
                     success: function(response) {
-                        $.modal.msgSuccess("文件导入成功!");
+                        $.modal.msgSuccess(i18n("common.tip.upload.success"));
                         $.table.refresh();
                     },
                     error: function() {
-                        $.modal.msgError("文件导入失败,请检查文件格式是否正确!");
+                        $.modal.msgError(i18n("common.tip.upload.fail"));
                     }
                 });
             }
@@ -326,7 +326,7 @@
 
     function customRemove(id) {
         // 自定义确认框内容
-        var confirmMsg = "确定要删除该外呼任务和关联的通话记录吗?<br><span style='color:red;font-size:12px;'>删除后数据将无法恢复,请谨慎操作!</span>";
+        var confirmMsg = i18n("common.msg.callRecords.del.confirm") + "<br><span style='color:red;font-size:12px;'>" + i18n("common.msg.callRecords.del.warning") + "</span>";
         $.modal.confirm(confirmMsg, function() {
             $.ajax({
                 url: prefix + "/remove",
@@ -334,14 +334,14 @@
                 data: { "ids": id },
                 success: function(result) {
                     if (result.code == 0) {
-                        $.modal.msgSuccess("删除成功!");
+                        $.modal.msgSuccess(i18n("common.tip.del.success"));
                         $.table.refresh();
                     } else {
                         $.modal.msgError(result.msg);
                     }
                 },
                 error: function() {
-                    $.modal.msgError("删除失败,请稍后重试!");
+                    $.modal.msgError(i18n("common.tip.del.fail"));
                 }
             });
         });

+ 753 - 351
ruoyi-admin/src/main/resources/templates/aicall/callTask/edit.html

@@ -4,465 +4,867 @@
     <th:block th:include="include :: header('修改外呼任务')" />
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-callTask-edit" th:object="${ccCallTask}">
-            <input name="batchId" th:field="*{batchId}" type="hidden">
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.batchName}"></label>
-                    <div class="col-sm-8">
-                        <input name="batchName" th:field="*{batchName}" class="form-control" type="text" required>
-                    </div>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-callTask-edit" th:object="${ccCallTask}">
+        <input name="batchId" th:field="*{batchId}" type="hidden">
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.batchName}"></label>
+                <div class="col-sm-8">
+                    <input name="batchName" th:field="*{batchName}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.threadNum}"></label>
-                    <div class="col-sm-8">
-                        <input name="threadNum" th:field="*{threadNum}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.threadNum}"></label>
+                <div class="col-sm-8">
+                    <input name="threadNum" th:field="*{threadNum}" class="form-control" type="text" required>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.taskType}"></label>
-                    <div class="col-sm-6">
-                        <select name="taskType" id="taskType"  th:field="*{taskType}" class="form-control" required>
-                            <option value="0" th:text="#{callTask.form.taskType0}"></option>
-                            <option value="1" th:text="#{callTask.form.taskType1}"></option>
-                            <option value="2" th:text="#{callTask.form.taskType2}"></option>
-                            <option value="3" th:text="#{callTask.form.taskType3}"></option>
-                        </select>
-                    </div>
-                    <div class="col-sm-2">
-                        <!-- 添加下载模板的链接 -->
-                        <a href="javascript:void(0);" onclick="downloadTemplate()">
-                            <span th:text="#{callTask.form.downloadTemplate}">下载模板</span>
-                        </a>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.taskType}"></label>
+                <div class="col-sm-6">
+                    <select name="taskType" id="taskType"  th:field="*{taskType}" class="form-control" required>
+                        <option value="0" th:text="#{callTask.form.taskType0}"></option>
+                        <option value="1" th:text="#{callTask.form.taskType1}"></option>
+                        <option value="2" th:text="#{callTask.form.taskType2}"></option>
+                        <option value="3" th:text="#{callTask.form.taskType3}"></option>
+                    </select>
+                </div>
+                <div class="col-sm-2">
+                    <!-- 添加下载模板的链接 -->
+                    <a href="javascript:void(0);" onclick="downloadTemplate()">
+                        <span th:text="#{callTask.form.downloadTemplate}">下载模板</span>
+                    </a>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.autoStop}"></label>
-                    <div class="col-sm-8">
-                        <select name="autoStop" th:field="*{autoStop}" class="form-control" required>
-                            <option value="1" th:text="#{callTask.form.autoStop1}"></option>
-                            <option value="0" th:text="#{callTask.form.autoStop0}"></option>
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.autoStop}"></label>
+                <div class="col-sm-8">
+                    <select name="autoStop" th:field="*{autoStop}" class="form-control" required>
+                        <option value="1" th:text="#{callTask.form.autoStop1}"></option>
+                        <option value="0" th:text="#{callTask.form.autoStop0}"></option>
+                    </select>
                 </div>
             </div>
+        </div>
 
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.conntectRate}"></label>
-                    <div class="col-sm-8">
-                        <input name="conntectRate" th:field="*{conntectRate}" class="form-control" type="number" min="0" max="100" step="1" required>
-                    </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.conntectRate}"></label>
+                <div class="col-sm-8">
+                    <input name="conntectRate" th:field="*{conntectRate}" class="form-control" type="number" min="0" max="100" step="1" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgRingTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgRingTimeLen" th:field="*{avgRingTimeLen}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgRingTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgRingTimeLen" th:field="*{avgRingTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallTalkTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgCallTalkTimeLen" th:field="*{avgCallTalkTimeLen}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallTalkTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgCallTalkTimeLen" th:field="*{avgCallTalkTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallEndProcessTimeLen}"></label>
-                    <div class="col-sm-8">
-                        <input name="avgCallEndProcessTimeLen" th:field="*{avgCallEndProcessTimeLen}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.avgCallEndProcessTimeLen}"></label>
+                <div class="col-sm-8">
+                    <input name="avgCallEndProcessTimeLen" th:field="*{avgCallEndProcessTimeLen}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.gatewayId}"></label>
-                    <div class="col-sm-8">
-                        <select name="gatewayId" th:field="*{gatewayId}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.gatewayId.empty}"></option>
-                            <!-- 网关选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.gatewayId}"></label>
+                <div class="col-sm-8">
+                    <select name="gatewayId" th:field="*{gatewayId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.gatewayId.empty}"></option>
+                        <!-- 网关选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.llmAccountId}"></label>
-                    <div class="col-sm-8">
-                        <select name="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.llmAccountId.empty}"></option>
-                            <!-- 大模型底座选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.llmAccountId}"></label>
+                <div class="col-sm-8">
+                    <select name="llmAccountId" id="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.llmAccountId.empty}"></option>
+                        <!-- 大模型底座选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceSource}"></label>
-                    <div class="col-sm-8">
-                        <select name="voiceSource" id="voiceSource" th:field="*{voiceSource}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.voiceSource.empty}"></option>
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12 voice-config">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceSource}"></label>
+                <div class="col-sm-8">
+                    <select name="voiceSource" id="voiceSource" th:field="*{voiceSource}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.voiceSource.empty}"></option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceCode}"></label>
-                    <div class="col-sm-8">
-                        <select name="voiceCode" th:field="*{voiceCode}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.voiceCode.empty}"></option>
-                            <!-- 音色选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+
+        <!-- 新增:TTS语言选择 -->
+        <div class="col-xs-12 voice-config" id="ttsLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ttsLanguageCode}">TTS语言</label>
+                <div class="col-sm-8">
+                    <select name="ttsLanguageCode" id="ttsLanguageCode" th:field="*{ttsLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ttsLanguageCode.empty}">请选择TTS语言</option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrProvider}"></label>
-                    <div class="col-sm-8">
-                        <select name="asrProvider" id="asrProvider" th:field="*{asrProvider}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.asrProvider.empty}"></option>
-                        </select>
-                    </div>
+        </div>
+
+        <!-- 新增:TTS模型选择 -->
+        <div class="col-xs-12 voice-config" id="ttsModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ttsModels}">TTS模型</label>
+                <div class="col-sm-8">
+                    <select name="ttsModels" id="ttsModels" th:field="*{ttsModels}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ttsModels.empty}">请选择TTS模型</option>
+                    </select>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.playTimes}"></label>
-                    <div class="col-sm-8">
-                        <input name="playTimes" th:field="*{playTimes}" class="form-control" type="text" required>
-                    </div>
+        </div>
+
+        <div class="col-xs-12 voice-config">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.voiceCode}"></label>
+                <div class="col-sm-8">
+                    <select name="voiceCode" id="voiceCode" th:field="*{voiceCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.voiceCode.empty}"></option>
+                        <!-- 音色选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
+        </div>
+
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrProvider}"></label>
+                <div class="col-sm-8">
+                    <select name="asrProvider" id="asrProvider" th:field="*{asrProvider}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrProvider.empty}"></option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR语言选择 -->
+        <div class="col-xs-12" id="asrLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrLanguageCode}">ASR语言</label>
+                <div class="col-sm-8">
+                    <select name="asrLanguageCode" id="asrLanguageCode" th:field="*{asrLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrLanguageCode.empty}">请选择ASR语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR模型选择 -->
+        <div class="col-xs-12" id="asrModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.asrModels}">ASR模型</label>
+                <div class="col-sm-8">
+                    <select name="asrModels" id="asrModels" th:field="*{asrModels}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.asrModels.empty}">请选择ASR模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
 
-            <div class="col-xs-12" id="aiTransferTypeDiv" >
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferType}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferType" th:field="*{aiTransferType}" id="aiTransferTypeSelect" class="form-control" required>
-                            <option value="acd" th:text="#{callTask.form.aiTransferTypeACD}"></option>
-                            <option value="extension" th:text="#{callTask.form.aiTransferTypeExtension}"></option>
-                            <option value="gateway" th:text="#{callTask.form.aiTransferTypeGateway}"></option>
-                        </select>
-                    </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required"  th:text="#{callTask.form.playTimes}"></label>
+                <div class="col-sm-8">
+                    <input name="playTimes" th:field="*{playTimes}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field acd-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGroupId}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferGroupId" th:field="*{aiTransferGroupId}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.aiTransferGroupId.empty}"></option>
-                            <!-- 业务组选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+
+        <div class="col-xs-12" id="aiTransferTypeDiv" >
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferType}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferType" th:field="*{aiTransferType}" id="aiTransferTypeSelect" class="form-control" required>
+                        <option value="acd" th:text="#{callTask.form.aiTransferTypeACD}"></option>
+                        <option value="extension" th:text="#{callTask.form.aiTransferTypeExtension}"></option>
+                        <option value="gateway" th:text="#{callTask.form.aiTransferTypeGateway}"></option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayId}"></label>
-                    <div class="col-sm-8">
-                        <select name="aiTransferGatewayId" th:field="*{aiTransferGatewayId}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.aiTransferGatewayId.empty}"></option>
-                            <!-- 业务组选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field acd-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGroupId}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferGroupId" th:field="*{aiTransferGroupId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.aiTransferGroupId.empty}"></option>
+                        <!-- 业务组选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayDestNumber}"></label>
-                    <div class="col-sm-8">
-                        <input name="aiTransferGatewayDestNumber" th:field="*{aiTransferGatewayDestNumber}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayId}"></label>
+                <div class="col-sm-8">
+                    <select name="aiTransferGatewayId" th:field="*{aiTransferGatewayId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.aiTransferGatewayId.empty}"></option>
+                        <!-- 业务组选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12 transfer-field extension-transfer" style="display: none;">
-                <div class="form-group" >
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferExtNumber}"></label>
-                    <div class="col-sm-8">
-                        <input name="aiTransferExtNumber" th:field="*{aiTransferExtNumber}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12 transfer-field gateway-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferGatewayDestNumber}"></label>
+                <div class="col-sm-8">
+                    <input name="aiTransferGatewayDestNumber" th:field="*{aiTransferGatewayDestNumber}" class="form-control" type="text" required>
                 </div>
             </div>
+        </div>
+        <div class="col-xs-12 transfer-field extension-transfer" style="display: none;">
+            <div class="form-group" >
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.aiTransferExtNumber}"></label>
+                <div class="col-sm-8">
+                    <input name="aiTransferExtNumber" th:field="*{aiTransferExtNumber}" class="form-control" type="text" required>
+                </div>
+            </div>
+        </div>
 
 
-            <div class="col-xs-12">
-                <div class="form-group" style="display:none;">
-                    <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ivrId}"></label>
-                    <div class="col-sm-8">
-                        <select name="ivrId" th:field="*{ivrId}" class="form-control" required>
-                            <option value="" th:text="#{callTask.form.ivrId.empty}"></option>
-                            <!-- IVR选项将通过接口动态加载 -->
-                        </select>
-                    </div>
+        <div class="col-xs-12">
+            <div class="form-group" style="display:none;">
+                <label class="col-sm-3 control-label is-required" th:text="#{callTask.form.ivrId}"></label>
+                <div class="col-sm-8">
+                    <select name="ivrId" th:field="*{ivrId}" class="form-control" required>
+                        <option value="" th:text="#{callTask.form.ivrId.empty}"></option>
+                        <!-- IVR选项将通过接口动态加载 -->
+                    </select>
                 </div>
             </div>
+        </div>
+
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+<script th:inline="javascript">
+
+    var prefix = ctx + "aicall/callTask";
+    // 存储llmAccount数据,用于判断providerClassName
+    var llmAccountMap = {};
+
+    $(function() {
+        // 加载网关下拉框数据
+        $.ajax({
+            url: ctx + "cc/gateways/outbound",
+            success: function(rsp) {
+                var gatewaySelect = $('select[name="gatewayId"]');
+                gatewaySelect.empty();
+                gatewaySelect.append('<option value="">' + i18n('callTask.form.gatewayId.empty') + '</option>');
+                rsp.data.forEach(function(gateway) {
+                    gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+                });
+                // 设置网关下拉框的选中项
+                var gatewayId = /*[[${ccCallTask.gatewayId}]]*/ '';
+                if (gatewayId) {
+                    gatewaySelect.val(gatewayId);
+                }
 
-        </form>
-    </div>
-    <th:block th:include="include :: footer" />
-    <script th:inline="javascript">
 
-        var prefix = ctx + "aicall/callTask";
-        // var _taskType = /*[[${ccCallTask.taskType}]]*/ '';
-        // this.changeTaskType(_taskType);
 
-        $(function() {
-            // 加载网关下拉框数据
-            $.ajax({
-                url: ctx + "cc/gateways/outbound",
-                success: function(rsp) {
-                    var gatewaySelect = $('select[name="gatewayId"]');
-                    gatewaySelect.empty();
-                    gatewaySelect.append('<option value="">' + i18n('callTask.form.gatewayId.empty') + '</option>');
-                    rsp.data.forEach(function(gateway) {
-                        gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
-                    });
-                    // 设置网关下拉框的选中项
-                    var gatewayId = /*[[${ccCallTask.gatewayId}]]*/ '';
-                    if (gatewayId) {
-                        gatewaySelect.val(gatewayId);
-                    }
+                var gatewaySelect = $('select[name="aiTransferGatewayId"]');
+                gatewaySelect.empty();
+                gatewaySelect.append('<option value="">' + i18n('callTask.form.aiTransferGatewayId.empty') + '</option>');
+                rsp.data.forEach(function(gateway) {
+                    gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+                });
+                // 回显已保存的aiTransferGatewayId
+                var aiTransferGatewayId = /*[[${ccCallTask.aiTransferGatewayId}]]*/ '';
+                if (aiTransferGatewayId) {
+                    gatewaySelect.val(aiTransferGatewayId);
+                }
+            }
+        });
 
+        // 加载TTS提供商下拉框数据
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/tts/voiceSource/all",
+            success: function(rsp) {
+                var voiceSourceSelect = $('#voiceSource');
+                voiceSourceSelect.empty();
+                // 遍历 map 对象,key 作为 value,value 作为显示文本
+                Object.entries(rsp.data).forEach(function([key, value]) {
+                    voiceSourceSelect.append($("<option>").attr("value", key).text(value));
+                });
+                // 回显已保存的voiceSource并加载对应语言和音色
+                var savedVoiceSource = /*[[${ccCallTask.voiceSource}]]*/ '';
+                if (savedVoiceSource) {
+                    voiceSourceSelect.val(savedVoiceSource);
+                    loadTtsLanguages(savedVoiceSource, function() {
+                        var savedTtsLanguageCode = /*[[${ccCallTask.ttsLanguageCode}]]*/ '';
+                        if (savedTtsLanguageCode) {
+                            $('#ttsLanguageCode').val(savedTtsLanguageCode);
+                            loadTtsModels(savedVoiceSource, savedTtsLanguageCode, function() {
+                                var savedTtsModels = /*[[${ccCallTask.ttsModels}]]*/ '';
+                                if (savedTtsModels) {
+                                    $('#ttsModels').val(savedTtsModels);
+                                }
+                                loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, savedTtsModels);
+                            });
+                        }
+                    });
+                }
+            }
+        });
 
 
-                    var gatewaySelect = $('select[name="aiTransferGatewayId"]');
-                    gatewaySelect.empty();
-                    gatewaySelect.append('<option value="">' + i18n('callTask.form.aiTransferGatewayId.empty') + '</option>');
-                    rsp.data.forEach(function(gateway) {
-                        gatewaySelect.append($("<option>").attr("value", gateway.id).text(gateway.gwDesc));
+        // 加载ASR提供商下拉框数据
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/provider/all",
+            success: function(rsp) {
+                var providerSelect = $('#asrProvider');
+                providerSelect.empty();
+                // 遍历 map 对象,key 作为 value,value 作为显示文本
+                Object.entries(rsp.data).forEach(function([key, value]) {
+                    providerSelect.append($("<option>").attr("value", key).text(value));
+                });
+                // 回显已保存的asrprovider并加载对应语言
+                var savedProvider = /*[[${ccCallTask.asrProvider}]]*/ '';
+                if (savedProvider) {
+                    providerSelect.val(savedProvider);
+                    loadAsrLanguages(savedProvider, function() {
+                        var savedAsrLanguageCode = /*[[${ccCallTask.asrLanguageCode}]]*/ '';
+                        if (savedAsrLanguageCode) {
+                            $('#asrLanguageCode').val(savedAsrLanguageCode);
+                            loadAsrModels(savedProvider, savedAsrLanguageCode, function() {
+                                var savedAsrModels = /*[[${ccCallTask.asrModels}]]*/ '';
+                                if (savedAsrModels) {
+                                    $('#asrModels').val(savedAsrModels);
+                                }
+                            });
+                        }
                     });
-                    // 回显已保存的aiTransferGatewayId
-                    var aiTransferGatewayId = /*[[${ccCallTask.aiTransferGatewayId}]]*/ '';
-                    if (aiTransferGatewayId) {
-                        gatewaySelect.val(aiTransferGatewayId);
-                    }
                 }
-            });
+            }
+        });
+
+
+        // 根据TTS厂商加载语言列表
+        function loadTtsLanguages(voiceSource, callback) {
+            var languageSelect = $('#ttsLanguageCode');
+            var languageDiv = $('#ttsLanguageCodeDiv');
+            var modelsSelect = $('#ttsModels');
+            var modelsDiv = $('#ttsModelsDiv');
+
+            if (!voiceSource) {
+                languageSelect.empty().append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                languageDiv.hide();
+                // 清空并隐藏模型下拉框
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载TTS提供商下拉框数据
             $.ajax({
-                url: ctx + "aicall/ttsAliyun/tts/voiceSource/all",
+                url: ctx + "aicall/ttsAliyun/tts/language",
+                data: { voiceSource: voiceSource },
                 success: function(rsp) {
-                    var voiceSourceSelect = $('#voiceSource');
-                    voiceSourceSelect.empty();
-                    // 遍历 map 对象,key 作为 value,value 作为显示文本
-                    Object.entries(rsp.data).forEach(function([key, value]) {
-                        voiceSourceSelect.append($("<option>").attr("value", key).text(value));
-                    });
-                    // 回显已保存的voiceSource并加载对应音色
-                    var savedVoiceSource = /*[[${ccCallTask.voiceSource}]]*/ '';
-                    if (savedVoiceSource) {
-                        voiceSourceSelect.val(savedVoiceSource);
-                        loadVoicesByVoiceSource(savedVoiceSource);
+                    languageSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var langCode = item.code || item.key || item.value;
+                            languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                            languageSelect.val(langCode);
+                            languageDiv.hide();
+                            // 单条数据默认选中时也要加载models
+                            loadTtsModels(voiceSource, langCode, callback);
+                        } else {
+                            languageSelect.append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            languageDiv.show();
+                            // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                            modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                            modelsDiv.hide();
+                            if (callback) callback();
+                        }
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('callTask.form.ttsLanguageCode.empty') + '</option>');
+                        languageDiv.show();
+                        // 无数据时隐藏模型下拉框
+                        modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
                     }
                 }
             });
+        }
 
+        // 根据ASR厂商加载语言列表
+        function loadAsrLanguages(asrProvider, callback) {
+            var languageSelect = $('#asrLanguageCode');
+            var languageDiv = $('#asrLanguageCodeDiv');
+            var modelsSelect = $('#asrModels');
+            var modelsDiv = $('#asrModelsDiv');
+
+            if (!asrProvider) {
+                languageSelect.empty().append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                languageDiv.hide();
+                // 清空并隐藏模型下拉框
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载ASR提供商下拉框数据
             $.ajax({
-                url: ctx + "aicall/ttsAliyun/asr/provider/all",
+                url: ctx + "aicall/ttsAliyun/asr/language",
+                data: { asrProvider: asrProvider },
                 success: function(rsp) {
-                    var providerSelect = $('#asrProvider');
-                    providerSelect.empty();
-                    // 遍历 map 对象,key 作为 value,value 作为显示文本
-                    Object.entries(rsp.data).forEach(function([key, value]) {
-                        providerSelect.append($("<option>").attr("value", key).text(value));
-                    });
-                    // 回显已保存的asrprovider
-                    var savedProvider = /*[[${ccCallTask.asrProvider}]]*/ '';
-                    if (savedProvider) {
-                        providerSelect.val(savedProvider);
+                    languageSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var langCode = item.code || item.key || item.value;
+                            languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                            languageSelect.val(langCode);
+                            languageDiv.hide();
+                            // 单条数据默认选中时也要加载models
+                            loadAsrModels(asrProvider, langCode, callback);
+                        } else {
+                            languageSelect.append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            languageDiv.show();
+                            // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                            modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                            modelsDiv.hide();
+                            if (callback) callback();
+                        }
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('callTask.form.asrLanguageCode.empty') + '</option>');
+                        languageDiv.show();
+                        // 无数据时隐藏模型下拉框
+                        modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
                     }
                 }
             });
+        }
 
+        // 根据TTS厂商和语言加载TTS模型
+        function loadTtsModels(voiceSource, ttsLanguageCode, callback) {
+            var modelsSelect = $('#ttsModels');
+            var modelsDiv = $('#ttsModelsDiv');
 
-            // 根据提供商加载音色
-            function loadVoicesByVoiceSource(voiceSource) {
-                var voiceSelect = $('select[name="voiceCode"]');
-                if (!voiceSource) {
-                    voiceSelect.empty().append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
-                    return;
-                }
-                $.ajax({
-                    url: ctx + "aicall/ttsAliyun/getByVoiceSource",
-                    data: { voiceSource: voiceSource },
-                    success: function(rsp) {
-                        voiceSelect.empty();
-                        rsp.data.forEach(function(voice) {
-                            voiceSelect.append($("<option>")
-                                .attr("value", voice.voiceCode)
-                                .attr("data-source", voice.voiceSource)
-                                .text(voice.voiceName));
-                        });
-                        // 回显已保存的音色
-                        var voiceCode = /*[[${ccCallTask.voiceCode}]]*/ '';
-                        if (voiceCode) {
-                            voiceSelect.val(voiceCode);
-                        }
-                    }
-                });
+            if (!voiceSource || !ttsLanguageCode) {
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
             }
 
-            // 提供商变更事件
-            $(document).on('change', '#voiceSource', function() {
-                loadVoicesByVoiceSource($(this).val());
-            });
-            
-            // 加载大模型底座下拉框数据
             $.ajax({
-                url: ctx + "aicall/account/all",
+                url: ctx + "aicall/ttsAliyun/tts/models",
+                data: {
+                    voiceSource: voiceSource,
+                    ttsLanguageCode: ttsLanguageCode
+                },
                 success: function(rsp) {
-                    var llmAccountSelect = $('select[name="llmAccountId"]');
-                    llmAccountSelect.empty();
-                    llmAccountSelect.append('<option value="">' + i18n('callTask.form.llmAccountId.empty') + '</option>');
-                    rsp.data.forEach(function(llmAccount) {
-                        llmAccountSelect.append($("<option>").attr("value", llmAccount.id).text(llmAccount.name));
-                    });
-                    var llmAccountId = /*[[${ccCallTask.llmAccountId}]]*/ '';
-                    if (llmAccountId) {
-                        llmAccountSelect.val(llmAccountId);
+                    modelsSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var modelValue = item.code || item.key || item.value;
+                            modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                            modelsSelect.val(modelValue);
+                            modelsDiv.hide(); // 单值时隐藏
+                        } else {
+                            modelsSelect.append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            modelsDiv.show(); // 多值时显示
+                        }
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide(); // 无数据时隐藏
                     }
+
+                    if (callback) callback();
                 }
             });
+        }
+
+        // 根据ASR厂商和语言加载ASR模型
+        function loadAsrModels(asrProvider, asrLanguageCode, callback) {
+            var modelsSelect = $('#asrModels');
+            var modelsDiv = $('#asrModelsDiv');
+
+            if (!asrProvider || !asrLanguageCode) {
+                modelsSelect.empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                modelsDiv.hide();
+                if (callback) callback();
+                return;
+            }
 
-            // 加载IVR下拉框数据
             $.ajax({
-                url: ctx + "cc/ivr/all",
+                url: ctx + "aicall/ttsAliyun/asr/models",
+                data: {
+                    asrProvider: asrProvider,
+                    asrLanguageCode: asrLanguageCode
+                },
                 success: function(rsp) {
-                    var ivrIdSelect = $('select[name="ivrId"]');
-                    ivrIdSelect.empty();
-                    ivrIdSelect.append('<option value="">' + i18n('callTask.form.ivrId.empty') + '</option>');
-                    rsp.data.forEach(function(ccIvr) {
-                        ivrIdSelect.append($("<option>").attr("value", ccIvr.id).text(ccIvr.ivrNodeName));
-                    });
-                    var ivrId = /*[[${ccCallTask.ivrId}]]*/ '';
-                    if (ivrId) {
-                        ivrIdSelect.val(ivrId);
+                    modelsSelect.empty();
+
+                    if (rsp.data && rsp.data.length > 0) {
+                        // 如果只有一个选项,直接选中且不显示下拉框
+                        if (rsp.data.length === 1) {
+                            var item = rsp.data[0];
+                            var modelValue = item.code || item.key || item.value;
+                            modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                            modelsSelect.val(modelValue);
+                            modelsDiv.hide(); // 单值时隐藏
+                        } else {
+                            modelsSelect.append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                            rsp.data.forEach(function(item) {
+                                modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                            });
+                            modelsDiv.show(); // 多值时显示
+                        }
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide(); // 无数据时隐藏
                     }
+
+                    if (callback) callback();
                 }
             });
+        }
 
-            // 加载业务组下拉框数据
+        // 根据TTS厂商、语言和模型加载音色
+        function loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels) {
+            var voiceSelect = $('select[name="voiceCode"]');
+            if (!voiceSource || !ttsLanguageCode) {
+                voiceSelect.empty().append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
+                return;
+            }
+            var requestData = {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode
+            };
+            if (ttsModels) {
+                requestData.ttsModels = ttsModels;
+            }
             $.ajax({
-                url: ctx + "cc/bizgroup/all",
+                url: ctx + "aicall/ttsAliyun/getByLanguageCode",
+                data: requestData,
                 success: function(rsp) {
-                    var groupSelect = $('select[name="aiTransferGroupId"]');
-                    groupSelect.empty();
-                    groupSelect.append('<option value="">' + i18n('callTask.form.aiTransferGroupId.empty') + '</option>');
-                    rsp.data.forEach(function(bizGroup) {
-                        groupSelect.append($("<option>").attr("value", bizGroup.groupId).text(bizGroup.bizGroupName));
+                    voiceSelect.empty();
+                    rsp.data.forEach(function(voice) {
+                        voiceSelect.append($("<option>")
+                            .attr("value", voice.voiceCode)
+                            .attr("data-source", voice.voiceSource)
+                            .text(voice.voiceName));
                     });
-                    // 设置音色下拉框的选中项
-                    var groupId = /*[[${ccCallTask.aiTransferGroupId}]]*/ '';
-                    if (groupId) {
-                        groupSelect.val(groupId);
+                    // 回显已保存的音色
+                    var voiceCode = /*[[${ccCallTask.voiceCode}]]*/ '';
+                    if (voiceCode) {
+                        voiceSelect.val(voiceCode);
                     }
                 }
             });
+        }
 
-            // 监听 taskType 的变化
-            $('select[name="taskType"]').change(function() {
-                var taskType = $(this).val();
-                changeTaskType(taskType);
-                toggleTransferFields();
+        // TTS厂商变更事件
+        $(document).on('change', '#voiceSource', function() {
+            var voiceSource = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#ttsModels').empty().append('<option value="">' + i18n('callTask.form.ttsModels.empty') + '</option>');
+            $('#ttsModelsDiv').hide();
+
+            loadTtsLanguages(voiceSource, function() {
+                // 语言加载完成后,自动加载模型和音色(如果只有一个语言选项)
+                var ttsLanguageCode = $('#ttsLanguageCode').val();
+                if (ttsLanguageCode) {
+                    loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                        var ttsModels = $('#ttsModels').val();
+                        loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+                    });
+                } else {
+                    $('select[name="voiceCode"]').empty().append('<option value="">' + i18n('callTask.form.voiceCode.empty') + '</option>');
+                }
             });
+        });
 
-            // 初始化时根据默认值显示字段
-            $('select[name="taskType"]').trigger('change');
-
+        // TTS语言变更事件
+        $(document).on('change', '#ttsLanguageCode', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $(this).val();
+            loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                var ttsModels = $('#ttsModels').val();
+                loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+            });
+        });
 
+        // TTS模型变更事件
+        $(document).on('change', '#ttsModels', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $('#ttsLanguageCode').val();
+            var ttsModels = $(this).val();
+            loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+        });
 
-            // 绑定AI转接类型改变事件
-            $(document).on('change', '#aiTransferTypeSelect', function() {
-                toggleTransferFields();
+        // ASR厂商变更事件
+        $(document).on('change', '#asrProvider', function() {
+            var asrProvider = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#asrModels').empty().append('<option value="">' + i18n('callTask.form.asrModels.empty') + '</option>');
+            $('#asrModelsDiv').hide();
+
+            loadAsrLanguages(asrProvider, function() {
+                // 语言加载完成后,自动加载模型(如果只有一个语言选项)
+                var asrLanguageCode = $('#asrLanguageCode').val();
+                if (asrLanguageCode) {
+                    loadAsrModels(asrProvider, asrLanguageCode);
+                }
             });
+        });
 
+        // ASR语言变更事件
+        $(document).on('change', '#asrLanguageCode', function() {
+            var asrProvider = $('#asrProvider').val();
+            var asrLanguageCode = $(this).val();
+            loadAsrModels(asrProvider, asrLanguageCode);
+        });
 
-            // 初始化转接字段显示状态
-            toggleTransferFields();
+        // 加载大模型底座下拉框数据
+        $.ajax({
+            url: ctx + "aicall/account/all",
+            success: function(rsp) {
+                var llmAccountSelect = $('select[name="llmAccountId"]');
+                llmAccountSelect.empty();
+                llmAccountSelect.append('<option value="">' + i18n('callTask.form.llmAccountId.empty') + '</option>');
+                rsp.data.forEach(function(llmAccount) {
+                    llmAccountSelect.append($("<option>")
+                        .attr("value", llmAccount.id)
+                        .attr("data-provider", llmAccount.providerClassName)
+                        .text(llmAccount.name));
+                    llmAccountMap[llmAccount.id] = llmAccount;
+                });
+                var llmAccountId = /*[[${ccCallTask.llmAccountId}]]*/ '';
+                if (llmAccountId) {
+                    llmAccountSelect.val(llmAccountId);
+                    checkAndToggleVoiceFields(llmAccountId);
+                }
+            }
+        });
 
+        // 监听大模型底座变更
+        $(document).on('change', '#llmAccountId', function() {
+            checkAndToggleVoiceFields($(this).val());
         });
 
-        $("#form-callTask-edit").validate({
-            focusCleanup: true
+        // 加载IVR下拉框数据
+        $.ajax({
+            url: ctx + "cc/ivr/all",
+            success: function(rsp) {
+                var ivrIdSelect = $('select[name="ivrId"]');
+                ivrIdSelect.empty();
+                ivrIdSelect.append('<option value="">' + i18n('callTask.form.ivrId.empty') + '</option>');
+                rsp.data.forEach(function(ccIvr) {
+                    ivrIdSelect.append($("<option>").attr("value", ccIvr.id).text(ccIvr.ivrNodeName));
+                });
+                var ivrId = /*[[${ccCallTask.ivrId}]]*/ '';
+                if (ivrId) {
+                    ivrIdSelect.val(ivrId);
+                }
+            }
         });
 
-        function changeTaskType(taskType) {
+        // 加载业务组下拉框数据
+        $.ajax({
+            url: ctx + "cc/bizgroup/all",
+            success: function(rsp) {
+                var groupSelect = $('select[name="aiTransferGroupId"]');
+                groupSelect.empty();
+                groupSelect.append('<option value="">' + i18n('callTask.form.aiTransferGroupId.empty') + '</option>');
+                rsp.data.forEach(function(bizGroup) {
+                    groupSelect.append($("<option>").attr("value", bizGroup.groupId).text(bizGroup.bizGroupName));
+                });
+                // 设置音色下拉框的选中项
+                var groupId = /*[[${ccCallTask.aiTransferGroupId}]]*/ '';
+                if (groupId) {
+                    groupSelect.val(groupId);
+                }
+            }
+        });
 
-            // 隐藏所有字段
-            $('select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], input[name="playTimes"], select[name="aiTransferType"], select[name="ivrId"]').parent().parent().hide();
+        // 监听 taskType 的变化
+        $('select[name="taskType"]').change(function() {
+            var taskType = $(this).val();
+            changeTaskType(taskType);
+            toggleTransferFields();
+        });
 
-            // 根据 taskType 显示对应的字段
-            if (taskType === "0") {
-                $('input[name="threadNum"], select[name="gatewayId"], select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], select[name="aiTransferType"]').parent().parent().show();
-            } else if (taskType === "1") {
-                $('input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], select[name="aiTransferType"]').parent().parent().show();
-            } else if (taskType === "2") {
-                $('input[name="threadNum"], select[name="gatewayId"], input[name="playTimes"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], select[name="aiTransferType"]').parent().parent().show();
-            } else if (taskType === "3") {
-                $('input[name="threadNum"], select[name="gatewayId"], select[name="ivrId"]').parent().parent().show();
-            }
+        // 初始化时根据默认值显示字段
+        $('select[name="taskType"]').trigger('change');
+
+
+
+        // 绑定AI转接类型改变事件
+        $(document).on('change', '#aiTransferTypeSelect', function() {
+            toggleTransferFields();
+        });
+
+
+        // 初始化转接字段显示状态
+        toggleTransferFields();
+
+    });
+
+    $("#form-callTask-edit").validate({
+        focusCleanup: true
+    });
+
+    function changeTaskType(taskType) {
+
+        console.log("=========taskType:" + taskType);
+
+        // 隐藏所有字段
+        $('select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], select[name="asrProvider"], input[name="playTimes"], select[name="aiTransferType"], select[name="ivrId"], #ttsLanguageCode, #asrLanguageCode, #ttsModels, #asrModels').parent().parent().hide();
+
+        // 根据 taskType 显示对应的字段
+        if (taskType === "0") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="groupId"], input[name="conntectRate"], input[name="avgRingTimeLen"], input[name="avgCallTalkTimeLen"], input[name="avgCallEndProcessTimeLen"], select[name="aiTransferType"]').parent().parent().show();
+        } else if (taskType === "1") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="llmAccountId"], select[name="voiceCode"], select[name="voiceSource"], #ttsLanguageCode, #ttsModels, select[name="asrProvider"], #asrLanguageCode, #asrModels, select[name="aiTransferType"]').parent().parent().show();
+            checkAndToggleVoiceFields($('#llmAccountId').val());
+        } else if (taskType === "2") {
+            $('input[name="threadNum"], select[name="gatewayId"], input[name="playTimes"], select[name="voiceCode"], select[name="voiceSource"], #ttsLanguageCode, #ttsModels, select[name="asrProvider"], #asrLanguageCode, #asrModels, select[name="aiTransferType"]').parent().parent().show();
+        } else if (taskType === "3") {
+            $('input[name="threadNum"], select[name="gatewayId"], select[name="ivrId"]').parent().parent().show();
         }
 
-        function submitHandler() {
-            if ($.validate.form()) {
-                $.operate.save(prefix + "/edit", $('#form-callTask-edit').serialize());
-            }
+        // 根据当前选项数量重新判断语言和模型下拉框是否显示
+        var ttsLanguageCount = $('#ttsLanguageCode option').length;
+        var asrLanguageCount = $('#asrLanguageCode option').length;
+        var ttsModelsCount = $('#ttsModels option').length;
+        var asrModelsCount = $('#asrModels option').length;
+
+        if (ttsLanguageCount <= 1) {
+            $('#ttsLanguageCodeDiv').hide();
         }
+        if (asrLanguageCount <= 1) {
+            $('#asrLanguageCodeDiv').hide();
+        }
+        if (ttsModelsCount <= 1) {
+            $('#ttsModelsDiv').hide();
+        }
+        if (asrModelsCount <= 1) {
+            $('#asrModelsDiv').hide();
+        }
+    }
 
-        // 下载模板
-        function downloadTemplate () {
-            var taskType = $("select[name='taskType']").val();
-            var templateUrl = prefix + "/downloadTemplate?taskType=" + taskType; // 假设模板文件放在静态资源目录下
-            window.location.href = templateUrl;
-        };
-
-        function toggleTransferFields() {
-            var taskType = $("select[name='taskType']").val();
-            var aiTransferType = $('#aiTransferTypeSelect').val();
-
-            // 先隐藏所有转接相关字段
-            $('#aiTransferTypeDiv').hide();
-            $('.transfer-field').hide();
-
-            // 显示AI转接类型
-            if (taskType === "1") {
-                $('#aiTransferTypeDiv').show();
-
-                // 根据转接类型显示对应字段
-                if (aiTransferType === 'acd') {
-                    $('.acd-transfer').show();
-                } else if (aiTransferType === 'gateway') {
-                    $('.gateway-transfer').show();
-                } else if (aiTransferType === 'extension') {
-                    $('.extension-transfer').show();
+    function submitHandler() {
+        if ($.validate.form()) {
+            $.operate.save(prefix + "/edit", $('#form-callTask-edit').serialize());
+        }
+    }
+
+    // 下载模板
+    function downloadTemplate () {
+        var taskType = $("select[name='taskType']").val();
+        var templateUrl = prefix + "/downloadTemplate?taskType=" + taskType; // 假设模板文件放在静态资源目录下
+        window.location.href = templateUrl;
+    };
+
+    function toggleTransferFields() {
+        var taskType = $("select[name='taskType']").val();
+        var aiTransferType = $('#aiTransferTypeSelect').val();
+
+        // 先隐藏所有转接相关字段
+        $('#aiTransferTypeDiv').hide();
+        $('.transfer-field').hide();
+
+        // 显示AI转接类型
+        if (taskType === "1") {
+            $('#aiTransferTypeDiv').show();
+
+            // 根据转接类型显示对应字段
+            if (aiTransferType === 'acd') {
+                $('.acd-transfer').show();
+            } else if (aiTransferType === 'gateway') {
+                $('.gateway-transfer').show();
+            } else if (aiTransferType === 'extension') {
+                $('.extension-transfer').show();
+            }
+        }
+    }
+
+    function checkAndToggleVoiceFields(llmAccountId) {
+        var llmAccount = llmAccountMap[llmAccountId];
+        if (llmAccount && llmAccount.providerClassName === 'LocalWavFile') {
+            // LocalWavFile时隐藏TTS相关字段,保留ASR
+            $('.voice-config').hide();
+            $('.voice-config select').prop('required', false);
+            // 清空TTS相关值
+            $('#voiceSource').val('');
+            $('#ttsLanguageCode').val('');
+            $('#ttsModels').val('');
+            $('#voiceCode').val('');
+        } else {
+            // 非LocalWavFile时显示TTS相关字段
+            var taskType = $('#taskType').val();
+            if (taskType === "1" || taskType === "2") {
+                $('.voice-config').show();
+                $('.voice-config select').prop('required', true);
+                // 根据当前选项数量重新判断语言和模型下拉框是否显示
+                var ttsLanguageCount = $('#ttsLanguageCode option').length;
+                var ttsModelsCount = $('#ttsModels option').length;
+                if (ttsLanguageCount <= 1) {
+                    $('#ttsLanguageCodeDiv').hide();
+                }
+                if (ttsModelsCount <= 1) {
+                    $('#ttsModelsDiv').hide();
                 }
             }
         }
-    </script>
+    }
+</script>
 </body>
 </html>

+ 414 - 15
ruoyi-admin/src/main/resources/templates/aicall/inboundllm/add.html

@@ -47,7 +47,7 @@
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.llmAccountId}"></label>
                 <div class="col-sm-8">
-                    <select name="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
+                    <select name="llmAccountId" id="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
                         <option value="" th:text="#{inboundllm.form.llmAccountId.empty}"></option>
                     </select>
                 </div>
@@ -66,8 +66,32 @@
             </div>
         </div>
 
-        <!-- 5. 音色来源(仅 AI 可见) -->
-        <div class="col-xs-12 ai-only">
+        <!-- ASR语言选择(仅 AI 可见) -->
+        <div class="col-xs-12 ai-only" id="asrLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.asrLanguageCode}">ASR语言</label>
+                <div class="col-sm-8">
+                    <select name="asrLanguageCode" id="asrLanguageCode" th:field="*{asrLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.asrLanguageCode.empty}">请选择ASR语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR模型选择(仅 AI 可见) -->
+        <div class="col-xs-12 ai-only" id="asrModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.asrModels}">ASR模型</label>
+                <div class="col-sm-8">
+                    <select name="asrModels" id="asrModels" th:field="*{asrModels}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.asrModels.empty}">请选择ASR模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 5. 音色来源(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config">
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.voiceSource}"></label>
                 <div class="col-sm-8">
@@ -78,12 +102,36 @@
             </div>
         </div>
 
-        <!-- 6. 音色编码(仅 AI 可见) -->
-        <div class="col-xs-12 ai-only">
+        <!-- TTS语言选择(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config" id="ttsLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.ttsLanguageCode}">TTS语言</label>
+                <div class="col-sm-8">
+                    <select name="ttsLanguageCode" id="ttsLanguageCode" th:field="*{ttsLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.ttsLanguageCode.empty}">请选择TTS语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:TTS模型选择(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config" id="ttsModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.ttsModels}">TTS模型</label>
+                <div class="col-sm-8">
+                    <select name="ttsModels" id="ttsModels" th:field="*{ttsModels}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.ttsModels.empty}">请选择TTS模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 6. 音色编码(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config">
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.voiceCode}"></label>
                 <div class="col-sm-8">
-                    <select name="voiceCode" th:field="*{voiceCode}" class="form-control" required>
+                    <select name="voiceCode" id="voiceCode" th:field="*{voiceCode}" class="form-control" required>
                         <option value="" th:text="#{inboundllm.form.voiceCode.empty}"></option>
                     </select>
                 </div>
@@ -178,6 +226,9 @@
 <th:block th:include="include :: footer"/>
 <script th:inline="javascript">
     var prefix = ctx + "aicall/inboundllm";
+    // 存储llmAccount数据,用于判断providerClassName
+    var llmAccountMap = {};
+
     $(function(){
 
         /* ---------- 通用下拉数据加载 ---------- */
@@ -190,10 +241,17 @@
                 llmAccountSelect.empty();
                 llmAccountSelect.append('<option value="">' + i18n('inboundllm.form.llmAccountId.empty') + '</option>');
                 rsp.data.forEach(function(llmAccount) {
-                    llmAccountSelect.append($("<option>").attr("value", llmAccount.id).text(llmAccount.name));
+                    llmAccountSelect.append($("<option>")
+                        .attr("value", llmAccount.id)
+                        .attr("data-provider", llmAccount.providerClassName)
+                        .text(llmAccount.name));
+                    llmAccountMap[llmAccount.id] = llmAccount;
                 });
                 var llmAccountId = /*[[${ccInboundLlmAccount.llmAccountId}]]*/ '';
-                if (llmAccountId) llmAccountSelect.val(llmAccountId);
+                if (llmAccountId) {
+                    llmAccountSelect.val(llmAccountId);
+                    checkAndToggleVoiceFields(llmAccountId);
+                }
             }
         });
 
@@ -207,7 +265,21 @@
                     providerSelect.append($("<option>").attr("value", key).text(value));
                 });
                 var savedProvider = /*[[${ccInboundLlmAccount.asrProvider}]]*/ '';
-                if (savedProvider) providerSelect.val(savedProvider);
+                if (savedProvider) {
+                    providerSelect.val(savedProvider);
+                    loadAsrLanguages(savedProvider, function() {
+                        var savedAsrLanguageCode = /*[[${ccInboundLlmAccount.asrLanguageCode}]]*/ '';
+                        if (savedAsrLanguageCode) {
+                            $('#asrLanguageCode').val(savedAsrLanguageCode);
+                            loadAsrModels(savedProvider, savedAsrLanguageCode, function() {
+                                var savedAsrModels = /*[[${ccInboundLlmAccount.asrModels}]]*/ '';
+                                if (savedAsrModels) {
+                                    $('#asrModels').val(savedAsrModels);
+                                }
+                            });
+                        }
+                    });
+                }
             }
         });
 
@@ -223,7 +295,21 @@
                 var savedVoiceSource = /*[[${ccInboundLlmAccount.voiceSource}]]*/ '';
                 if (savedVoiceSource) {
                     voiceSourceSelect.val(savedVoiceSource);
-                    loadVoicesByVoiceSource(savedVoiceSource);
+                    loadTtsLanguages(savedVoiceSource, function() {
+                        var savedTtsLanguageCode = /*[[${ccInboundLlmAccount.ttsLanguageCode}]]*/ '';
+                        if (savedTtsLanguageCode) {
+                            $('#ttsLanguageCode').val(savedTtsLanguageCode);
+                            loadTtsModels(savedVoiceSource, savedTtsLanguageCode, function() {
+                                var savedTtsModels = /*[[${ccInboundLlmAccount.ttsModels}]]*/ '';
+                                if (savedTtsModels) {
+                                    $('#ttsModels').val(savedTtsModels);
+                                    loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, savedTtsModels);
+                                } else {
+                                    loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, '');
+                                }
+                            });
+                        }
+                    });
                 }
             }
         });
@@ -290,8 +376,60 @@
 
         /* ---------- 事件绑定 ---------- */
         $(document).on('change', '#voiceSource', function() {
-            loadVoicesByVoiceSource($(this).val());
+            var voiceSource = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#ttsModels').empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            $('#ttsModelsDiv').hide();
+
+            loadTtsLanguages(voiceSource, function() {
+                var ttsLanguageCode = $('#ttsLanguageCode').val();
+                if (ttsLanguageCode) {
+                    loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                        var ttsModels = $('#ttsModels').val();
+                        loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+                    });
+                } else {
+                    $('#voiceCode').empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+                }
+            });
+        });
+
+        $(document).on('change', '#ttsLanguageCode', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $(this).val();
+            loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                var ttsModels = $('#ttsModels').val();
+                loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+            });
+        });
+
+        $(document).on('change', '#ttsModels', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $('#ttsLanguageCode').val();
+            var ttsModels = $(this).val();
+            loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+        });
+
+        $(document).on('change', '#asrProvider', function() {
+            var asrProvider = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#asrModels').empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            $('#asrModelsDiv').hide();
+
+            loadAsrLanguages(asrProvider, function() {
+                var asrLanguageCode = $('#asrLanguageCode').val();
+                if (asrLanguageCode) {
+                    loadAsrModels(asrProvider, asrLanguageCode);
+                }
+            });
+        });
+
+        $(document).on('change', '#asrLanguageCode', function() {
+            var asrProvider = $('#asrProvider').val();
+            var asrLanguageCode = $(this).val();
+            loadAsrModels(asrProvider, asrLanguageCode);
         });
+
         $(document).on('change', '#serviceTypeSelect', function() {
             toggleFields();
             // 清空satisfSurveyIvrId
@@ -301,6 +439,11 @@
             toggleFields();
         });
 
+        // 监听大模型底座变更
+        $(document).on('change', '#llmAccountId', function() {
+            checkAndToggleVoiceFields($(this).val());
+        });
+
         /* ---------- 校验 ---------- */
         $('input[name="callee"]').rules('add', {
             remote: {
@@ -330,15 +473,225 @@
     }
 
     /* ========== 工具函数 ========== */
-    function loadVoicesByVoiceSource(voiceSource) {
-        var voiceSelect = $('select[name="voiceCode"]');
+
+    // 根据TTS厂商加载语言列表
+    function loadTtsLanguages(voiceSource, callback) {
+        var languageSelect = $('#ttsLanguageCode');
+        var languageDiv = $('#ttsLanguageCodeDiv');
+        var modelsSelect = $('#ttsModels');
+        var modelsDiv = $('#ttsModelsDiv');
+
         if (!voiceSource) {
-            voiceSelect.empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+            languageSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+            languageDiv.hide();
+            // 清空并隐藏模型下拉框
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
             return;
         }
+
         $.ajax({
-            url: ctx + "aicall/ttsAliyun/getByVoiceSource",
+            url: ctx + "aicall/ttsAliyun/tts/language",
             data: { voiceSource: voiceSource },
+            success: function(rsp) {
+                languageSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var langCode = item.code || item.key || item.value;
+                        languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                        languageSelect.val(langCode);
+                        languageDiv.hide();
+                        // 单条数据默认选中时也要加载models
+                        loadTtsModels(voiceSource, langCode, callback);
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        languageDiv.show();
+                        // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                        modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
+                } else {
+                    languageSelect.append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+                    languageDiv.show();
+                    // 无数据时隐藏模型下拉框
+                    modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                    modelsDiv.hide();
+                    if (callback) callback();
+                }
+            }
+        });
+    }
+
+    // 根据ASR厂商加载语言列表
+    function loadAsrLanguages(asrProvider, callback) {
+        var languageSelect = $('#asrLanguageCode');
+        var languageDiv = $('#asrLanguageCodeDiv');
+        var modelsSelect = $('#asrModels');
+        var modelsDiv = $('#asrModelsDiv');
+
+        if (!asrProvider) {
+            languageSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+            languageDiv.hide();
+            // 清空并隐藏模型下拉框
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/language",
+            data: { asrProvider: asrProvider },
+            success: function(rsp) {
+                languageSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var langCode = item.code || item.key || item.value;
+                        languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                        languageSelect.val(langCode);
+                        languageDiv.hide();
+                        // 单条数据默认选中时也要加载models
+                        loadAsrModels(asrProvider, langCode, callback);
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        languageDiv.show();
+                        // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                        modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
+                } else {
+                    languageSelect.append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+                    languageDiv.show();
+                    // 无数据时隐藏模型下拉框
+                    modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                    modelsDiv.hide();
+                    if (callback) callback();
+                }
+            }
+        });
+    }
+
+    // 根据TTS厂商和语言加载模型列表
+    function loadTtsModels(voiceSource, ttsLanguageCode, callback) {
+        var modelsSelect = $('#ttsModels');
+        var modelsDiv = $('#ttsModelsDiv');
+
+        if (!voiceSource || !ttsLanguageCode) {
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/tts/models",
+            data: {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode
+            },
+            success: function(rsp) {
+                modelsSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var modelValue = item.code || item.key || item.value;
+                        modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                        modelsSelect.val(modelValue);
+                        modelsDiv.hide(); // 单值时隐藏
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        modelsDiv.show(); // 多值时显示
+                    }
+                } else {
+                    modelsSelect.append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                    modelsDiv.hide(); // 无数据时隐藏
+                }
+
+                if (callback) callback();
+            }
+        });
+    }
+
+    // 根据ASR厂商和语言加载模型列表
+    function loadAsrModels(asrProvider, asrLanguageCode, callback) {
+        var modelsSelect = $('#asrModels');
+        var modelsDiv = $('#asrModelsDiv');
+
+        if (!asrProvider || !asrLanguageCode) {
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/models",
+            data: {
+                asrProvider: asrProvider,
+                asrLanguageCode: asrLanguageCode
+            },
+            success: function(rsp) {
+                modelsSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var modelValue = item.code || item.key || item.value;
+                        modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                        modelsSelect.val(modelValue);
+                        modelsDiv.hide(); // 单值时隐藏
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        modelsDiv.show(); // 多值时显示
+                    }
+                } else {
+                    modelsSelect.append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                    modelsDiv.hide(); // 无数据时隐藏
+                }
+
+                if (callback) callback();
+            }
+        });
+    }
+
+    // 根据TTS厂商、语言和模型加载音色
+    function loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels) {
+        var voiceSelect = $('#voiceCode');
+        if (!voiceSource || !ttsLanguageCode) {
+            voiceSelect.empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+            return;
+        }
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/getByLanguageCode",
+            data: {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode,
+                ttsModels: ttsModels
+            },
             success: function(rsp) {
                 voiceSelect.empty();
                 rsp.data.forEach(function(voice) {
@@ -362,6 +715,22 @@
 
         if (serviceType === 'ai') {
             $('.ai-only').show();
+            // 根据当前值重新判断语言下拉框是否显示
+            var ttsLanguageCount = $('#ttsLanguageCode option').length;
+            var asrLanguageCount = $('#asrLanguageCode option').length;
+            var ttsModelsCount = $('#ttsModels option').length;
+            var asrModelsCount = $('#asrModels option').length;
+            if (ttsLanguageCount <= 1) $('#ttsLanguageCodeDiv').hide();
+            if (asrLanguageCount <= 1) $('#asrLanguageCodeDiv').hide();
+            if (ttsModelsCount <= 1) $('#ttsModelsDiv').hide();
+            if (asrModelsCount <= 1) $('#asrModelsDiv').hide();
+
+            // 检查是否需要隐藏TTS相关字段
+            var llmAccountId = $('#llmAccountId').val();
+            if (llmAccountId) {
+                checkAndToggleVoiceFields(llmAccountId);
+            }
+
             if (aiTransferType === 'acd')      $('.acd-transfer').show();
             if (aiTransferType === 'gateway')  $('.gateway-transfer').show();
             if (aiTransferType === 'extension')$('.extension-transfer').show();
@@ -371,6 +740,36 @@
             $('.ivr-only').show();
         }
     }
+
+    function checkAndToggleVoiceFields(llmAccountId) {
+        var llmAccount = llmAccountMap[llmAccountId];
+        if (llmAccount && llmAccount.providerClassName === 'LocalWavFile') {
+            // LocalWavFile时隐藏TTS相关字段(voiceSource、ttsLanguageCode、ttsModels、voiceCode),保留ASR
+            $('.voice-config').hide();
+            $('.voice-config select').prop('required', false);
+            // 清空TTS相关值
+            $('#voiceSource').val('');
+            $('#ttsLanguageCode').val('');
+            $('#ttsModels').val('');
+            $('#voiceCode').val('');
+        } else {
+            // 非LocalWavFile时显示TTS相关字段
+            var serviceType = $('#serviceTypeSelect').val();
+            if (serviceType === 'ai') {
+                $('.voice-config').show();
+                $('.voice-config select').prop('required', true);
+                // 根据当前值重新判断语言下拉框是否显示
+                var ttsLanguageCount = $('#ttsLanguageCode option').length;
+                var ttsModelsCount = $('#ttsModels option').length;
+                if (ttsLanguageCount <= 1) {
+                    $('#ttsLanguageCodeDiv').hide();
+                }
+                if (ttsModelsCount <= 1) {
+                    $('#ttsModelsDiv').hide();
+                }
+            }
+        }
+    }
 </script>
 </body>
 </html>

+ 414 - 15
ruoyi-admin/src/main/resources/templates/aicall/inboundllm/edit.html

@@ -47,7 +47,7 @@
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.llmAccountId}"></label>
                 <div class="col-sm-8">
-                    <select name="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
+                    <select name="llmAccountId" id="llmAccountId" th:field="*{llmAccountId}" class="form-control" required>
                         <option value="" th:text="#{inboundllm.form.llmAccountId.empty}"></option>
                     </select>
                 </div>
@@ -66,8 +66,32 @@
             </div>
         </div>
 
-        <!-- 5. 音色来源(仅 AI 可见) -->
-        <div class="col-xs-12 ai-only">
+        <!-- ASR语言选择(仅 AI 可见) -->
+        <div class="col-xs-12 ai-only" id="asrLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.asrLanguageCode}">ASR语言</label>
+                <div class="col-sm-8">
+                    <select name="asrLanguageCode" id="asrLanguageCode" th:field="*{asrLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.asrLanguageCode.empty}">请选择ASR语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:ASR模型选择(仅 AI 可见) -->
+        <div class="col-xs-12 ai-only" id="asrModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.asrModels}">ASR模型</label>
+                <div class="col-sm-8">
+                    <select name="asrModels" id="asrModels" th:field="*{asrModels}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.asrModels.empty}">请选择ASR模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 5. 音色来源(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config">
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.voiceSource}"></label>
                 <div class="col-sm-8">
@@ -78,12 +102,36 @@
             </div>
         </div>
 
-        <!-- 6. 音色编码(仅 AI 可见) -->
-        <div class="col-xs-12 ai-only">
+        <!-- TTS语言选择(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config" id="ttsLanguageCodeDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.ttsLanguageCode}">TTS语言</label>
+                <div class="col-sm-8">
+                    <select name="ttsLanguageCode" id="ttsLanguageCode" th:field="*{ttsLanguageCode}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.ttsLanguageCode.empty}">请选择TTS语言</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 新增:TTS模型选择(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config" id="ttsModelsDiv" style="display:none;">
+            <div class="form-group">
+                <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.ttsModels}">TTS模型</label>
+                <div class="col-sm-8">
+                    <select name="ttsModels" id="ttsModels" th:field="*{ttsModels}" class="form-control" required>
+                        <option value="" th:text="#{inboundllm.form.ttsModels.empty}">请选择TTS模型</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+
+        <!-- 6. 音色编码(仅 AI 可见,LocalWavFile 时隐藏) -->
+        <div class="col-xs-12 ai-only voice-config">
             <div class="form-group">
                 <label class="col-sm-3 control-label is-required" th:text="#{inboundllm.form.voiceCode}"></label>
                 <div class="col-sm-8">
-                    <select name="voiceCode" th:field="*{voiceCode}" class="form-control" required>
+                    <select name="voiceCode" id="voiceCode" th:field="*{voiceCode}" class="form-control" required>
                         <option value="" th:text="#{inboundllm.form.voiceCode.empty}"></option>
                     </select>
                 </div>
@@ -178,6 +226,9 @@
 <th:block th:include="include :: footer"/>
 <script th:inline="javascript">
     var prefix = ctx + "aicall/inboundllm";
+    // 存储llmAccount数据,用于判断providerClassName
+    var llmAccountMap = {};
+
     $(function(){
 
         /* ---------- 通用下拉数据加载 ---------- */
@@ -190,10 +241,17 @@
                 llmAccountSelect.empty();
                 llmAccountSelect.append('<option value="">' + i18n('inboundllm.form.llmAccountId.empty') + '</option>');
                 rsp.data.forEach(function(llmAccount) {
-                    llmAccountSelect.append($("<option>").attr("value", llmAccount.id).text(llmAccount.name));
+                    llmAccountSelect.append($("<option>")
+                        .attr("value", llmAccount.id)
+                        .attr("data-provider", llmAccount.providerClassName)
+                        .text(llmAccount.name));
+                    llmAccountMap[llmAccount.id] = llmAccount;
                 });
                 var llmAccountId = /*[[${ccInboundLlmAccount.llmAccountId}]]*/ '';
-                if (llmAccountId) llmAccountSelect.val(llmAccountId);
+                if (llmAccountId) {
+                    llmAccountSelect.val(llmAccountId);
+                    checkAndToggleVoiceFields(llmAccountId);
+                }
             }
         });
 
@@ -207,7 +265,21 @@
                     providerSelect.append($("<option>").attr("value", key).text(value));
                 });
                 var savedProvider = /*[[${ccInboundLlmAccount.asrProvider}]]*/ '';
-                if (savedProvider) providerSelect.val(savedProvider);
+                if (savedProvider) {
+                    providerSelect.val(savedProvider);
+                    loadAsrLanguages(savedProvider, function() {
+                        var savedAsrLanguageCode = /*[[${ccInboundLlmAccount.asrLanguageCode}]]*/ '';
+                        if (savedAsrLanguageCode) {
+                            $('#asrLanguageCode').val(savedAsrLanguageCode);
+                            loadAsrModels(savedProvider, savedAsrLanguageCode, function() {
+                                var savedAsrModels = /*[[${ccInboundLlmAccount.asrModels}]]*/ '';
+                                if (savedAsrModels) {
+                                    $('#asrModels').val(savedAsrModels);
+                                }
+                            });
+                        }
+                    });
+                }
             }
         });
 
@@ -223,7 +295,21 @@
                 var savedVoiceSource = /*[[${ccInboundLlmAccount.voiceSource}]]*/ '';
                 if (savedVoiceSource) {
                     voiceSourceSelect.val(savedVoiceSource);
-                    loadVoicesByVoiceSource(savedVoiceSource);
+                    loadTtsLanguages(savedVoiceSource, function() {
+                        var savedTtsLanguageCode = /*[[${ccInboundLlmAccount.ttsLanguageCode}]]*/ '';
+                        if (savedTtsLanguageCode) {
+                            $('#ttsLanguageCode').val(savedTtsLanguageCode);
+                            loadTtsModels(savedVoiceSource, savedTtsLanguageCode, function() {
+                                var savedTtsModels = /*[[${ccInboundLlmAccount.ttsModels}]]*/ '';
+                                if (savedTtsModels) {
+                                    $('#ttsModels').val(savedTtsModels);
+                                    loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, savedTtsModels);
+                                } else {
+                                    loadVoicesByVoiceSourceAndLanguage(savedVoiceSource, savedTtsLanguageCode, '');
+                                }
+                            });
+                        }
+                    });
                 }
             }
         });
@@ -290,8 +376,60 @@
 
         /* ---------- 事件绑定 ---------- */
         $(document).on('change', '#voiceSource', function() {
-            loadVoicesByVoiceSource($(this).val());
+            var voiceSource = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#ttsModels').empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            $('#ttsModelsDiv').hide();
+
+            loadTtsLanguages(voiceSource, function() {
+                var ttsLanguageCode = $('#ttsLanguageCode').val();
+                if (ttsLanguageCode) {
+                    loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                        var ttsModels = $('#ttsModels').val();
+                        loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+                    });
+                } else {
+                    $('#voiceCode').empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+                }
+            });
+        });
+
+        $(document).on('change', '#ttsLanguageCode', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $(this).val();
+            loadTtsModels(voiceSource, ttsLanguageCode, function() {
+                var ttsModels = $('#ttsModels').val();
+                loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+            });
+        });
+
+        $(document).on('change', '#ttsModels', function() {
+            var voiceSource = $('#voiceSource').val();
+            var ttsLanguageCode = $('#ttsLanguageCode').val();
+            var ttsModels = $(this).val();
+            loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels);
+        });
+
+        $(document).on('change', '#asrProvider', function() {
+            var asrProvider = $(this).val();
+            // 清空并隐藏模型下拉框
+            $('#asrModels').empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            $('#asrModelsDiv').hide();
+
+            loadAsrLanguages(asrProvider, function() {
+                var asrLanguageCode = $('#asrLanguageCode').val();
+                if (asrLanguageCode) {
+                    loadAsrModels(asrProvider, asrLanguageCode);
+                }
+            });
+        });
+
+        $(document).on('change', '#asrLanguageCode', function() {
+            var asrProvider = $('#asrProvider').val();
+            var asrLanguageCode = $(this).val();
+            loadAsrModels(asrProvider, asrLanguageCode);
         });
+
         $(document).on('change', '#serviceTypeSelect', function() {
             toggleFields();
             // 清空satisfSurveyIvrId
@@ -301,6 +439,11 @@
             toggleFields();
         });
 
+        // 监听大模型底座变更
+        $(document).on('change', '#llmAccountId', function() {
+            checkAndToggleVoiceFields($(this).val());
+        });
+
         /* ---------- 校验 ---------- */
         $('input[name="callee"]').rules('add', {
             remote: {
@@ -330,15 +473,225 @@
     }
 
     /* ========== 工具函数 ========== */
-    function loadVoicesByVoiceSource(voiceSource) {
-        var voiceSelect = $('select[name="voiceCode"]');
+
+    // 根据TTS厂商加载语言列表
+    function loadTtsLanguages(voiceSource, callback) {
+        var languageSelect = $('#ttsLanguageCode');
+        var languageDiv = $('#ttsLanguageCodeDiv');
+        var modelsSelect = $('#ttsModels');
+        var modelsDiv = $('#ttsModelsDiv');
+
         if (!voiceSource) {
-            voiceSelect.empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+            languageSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+            languageDiv.hide();
+            // 清空并隐藏模型下拉框
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
             return;
         }
+
         $.ajax({
-            url: ctx + "aicall/ttsAliyun/getByVoiceSource",
+            url: ctx + "aicall/ttsAliyun/tts/language",
             data: { voiceSource: voiceSource },
+            success: function(rsp) {
+                languageSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var langCode = item.code || item.key || item.value;
+                        languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                        languageSelect.val(langCode);
+                        languageDiv.hide();
+                        // 单条数据默认选中时也要加载models
+                        loadTtsModels(voiceSource, langCode, callback);
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        languageDiv.show();
+                        // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                        modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
+                } else {
+                    languageSelect.append('<option value="">' + i18n('inboundllm.form.ttsLanguageCode.empty') + '</option>');
+                    languageDiv.show();
+                    // 无数据时隐藏模型下拉框
+                    modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                    modelsDiv.hide();
+                    if (callback) callback();
+                }
+            }
+        });
+    }
+
+    // 根据ASR厂商加载语言列表
+    function loadAsrLanguages(asrProvider, callback) {
+        var languageSelect = $('#asrLanguageCode');
+        var languageDiv = $('#asrLanguageCodeDiv');
+        var modelsSelect = $('#asrModels');
+        var modelsDiv = $('#asrModelsDiv');
+
+        if (!asrProvider) {
+            languageSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+            languageDiv.hide();
+            // 清空并隐藏模型下拉框
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/language",
+            data: { asrProvider: asrProvider },
+            success: function(rsp) {
+                languageSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框,但仍需调用models接口
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var langCode = item.code || item.key || item.value;
+                        languageSelect.append($("<option>").attr("value", langCode).text(item.name || item.label || item.text || item.value));
+                        languageSelect.val(langCode);
+                        languageDiv.hide();
+                        // 单条数据默认选中时也要加载models
+                        loadAsrModels(asrProvider, langCode, callback);
+                    } else {
+                        languageSelect.append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            languageSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        languageDiv.show();
+                        // 多语言时,先清空并隐藏模型下拉框,等待语言选择后再加载
+                        modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                        modelsDiv.hide();
+                        if (callback) callback();
+                    }
+                } else {
+                    languageSelect.append('<option value="">' + i18n('inboundllm.form.asrLanguageCode.empty') + '</option>');
+                    languageDiv.show();
+                    // 无数据时隐藏模型下拉框
+                    modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                    modelsDiv.hide();
+                    if (callback) callback();
+                }
+            }
+        });
+    }
+
+    // 根据TTS厂商和语言加载模型列表
+    function loadTtsModels(voiceSource, ttsLanguageCode, callback) {
+        var modelsSelect = $('#ttsModels');
+        var modelsDiv = $('#ttsModelsDiv');
+
+        if (!voiceSource || !ttsLanguageCode) {
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/tts/models",
+            data: {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode
+            },
+            success: function(rsp) {
+                modelsSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var modelValue = item.code || item.key || item.value;
+                        modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                        modelsSelect.val(modelValue);
+                        modelsDiv.hide(); // 单值时隐藏
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        modelsDiv.show(); // 多值时显示
+                    }
+                } else {
+                    modelsSelect.append('<option value="">' + i18n('inboundllm.form.ttsModels.empty') + '</option>');
+                    modelsDiv.hide(); // 无数据时隐藏
+                }
+
+                if (callback) callback();
+            }
+        });
+    }
+
+    // 根据ASR厂商和语言加载模型列表
+    function loadAsrModels(asrProvider, asrLanguageCode, callback) {
+        var modelsSelect = $('#asrModels');
+        var modelsDiv = $('#asrModelsDiv');
+
+        if (!asrProvider || !asrLanguageCode) {
+            modelsSelect.empty().append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+            modelsDiv.hide();
+            if (callback) callback();
+            return;
+        }
+
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/asr/models",
+            data: {
+                asrProvider: asrProvider,
+                asrLanguageCode: asrLanguageCode
+            },
+            success: function(rsp) {
+                modelsSelect.empty();
+
+                if (rsp.data && rsp.data.length > 0) {
+                    // 如果只有一个选项,直接选中且不显示下拉框
+                    if (rsp.data.length === 1) {
+                        var item = rsp.data[0];
+                        var modelValue = item.code || item.key || item.value;
+                        modelsSelect.append($("<option>").attr("value", modelValue).text(item.name || item.label || item.text || item.value));
+                        modelsSelect.val(modelValue);
+                        modelsDiv.hide(); // 单值时隐藏
+                    } else {
+                        modelsSelect.append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                        rsp.data.forEach(function(item) {
+                            modelsSelect.append($("<option>").attr("value", item.code || item.key || item.value).text(item.name || item.label || item.text || item.value));
+                        });
+                        modelsDiv.show(); // 多值时显示
+                    }
+                } else {
+                    modelsSelect.append('<option value="">' + i18n('inboundllm.form.asrModels.empty') + '</option>');
+                    modelsDiv.hide(); // 无数据时隐藏
+                }
+
+                if (callback) callback();
+            }
+        });
+    }
+
+    // 根据TTS厂商、语言和模型加载音色
+    function loadVoicesByVoiceSourceAndLanguage(voiceSource, ttsLanguageCode, ttsModels) {
+        var voiceSelect = $('#voiceCode');
+        if (!voiceSource || !ttsLanguageCode) {
+            voiceSelect.empty().append('<option value="">' + i18n('inboundllm.form.voiceCode.empty') + '</option>');
+            return;
+        }
+        $.ajax({
+            url: ctx + "aicall/ttsAliyun/getByLanguageCode",
+            data: {
+                voiceSource: voiceSource,
+                ttsLanguageCode: ttsLanguageCode,
+                ttsModels: ttsModels
+            },
             success: function(rsp) {
                 voiceSelect.empty();
                 rsp.data.forEach(function(voice) {
@@ -362,6 +715,22 @@
 
         if (serviceType === 'ai') {
             $('.ai-only').show();
+            // 根据当前值重新判断语言下拉框是否显示
+            var ttsLanguageCount = $('#ttsLanguageCode option').length;
+            var asrLanguageCount = $('#asrLanguageCode option').length;
+            var ttsModelsCount = $('#ttsModels option').length;
+            var asrModelsCount = $('#asrModels option').length;
+            if (ttsLanguageCount <= 1) $('#ttsLanguageCodeDiv').hide();
+            if (asrLanguageCount <= 1) $('#asrLanguageCodeDiv').hide();
+            if (ttsModelsCount <= 1) $('#ttsModelsDiv').hide();
+            if (asrModelsCount <= 1) $('#asrModelsDiv').hide();
+
+            // 检查是否需要隐藏TTS相关字段
+            var llmAccountId = $('#llmAccountId').val();
+            if (llmAccountId) {
+                checkAndToggleVoiceFields(llmAccountId);
+            }
+
             if (aiTransferType === 'acd')      $('.acd-transfer').show();
             if (aiTransferType === 'gateway')  $('.gateway-transfer').show();
             if (aiTransferType === 'extension')$('.extension-transfer').show();
@@ -371,6 +740,36 @@
             $('.ivr-only').show();
         }
     }
+
+    function checkAndToggleVoiceFields(llmAccountId) {
+        var llmAccount = llmAccountMap[llmAccountId];
+        if (llmAccount && llmAccount.providerClassName === 'LocalWavFile') {
+            // LocalWavFile时隐藏TTS相关字段(voiceSource、ttsLanguageCode、ttsModels、voiceCode),保留ASR
+            $('.voice-config').hide();
+            $('.voice-config select').prop('required', false);
+            // 清空TTS相关值
+            $('#voiceSource').val('');
+            $('#ttsLanguageCode').val('');
+            $('#ttsModels').val('');
+            $('#voiceCode').val('');
+        } else {
+            // 非LocalWavFile时显示TTS相关字段
+            var serviceType = $('#serviceTypeSelect').val();
+            if (serviceType === 'ai') {
+                $('.voice-config').show();
+                $('.voice-config select').prop('required', true);
+                // 根据当前值重新判断语言下拉框是否显示
+                var ttsLanguageCount = $('#ttsLanguageCode option').length;
+                var ttsModelsCount = $('#ttsModels option').length;
+                if (ttsLanguageCount <= 1) {
+                    $('#ttsLanguageCodeDiv').hide();
+                }
+                if (ttsModelsCount <= 1) {
+                    $('#ttsModelsDiv').hide();
+                }
+            }
+        }
+    }
 </script>
 </body>
 </html>

+ 1 - 1
ruoyi-admin/src/main/resources/templates/cc/aliasrconf/aliasrconf.html

@@ -70,7 +70,7 @@
             contentType: 'application/json',
             data: JSON.stringify(configs),
             beforeSend: function () {
-                $.modal.loading("正在处理中,请稍候...");
+                $.modal.loading(i18n("common.tip.loading"));
                 $.modal.disable();
             },
             success: function(response) {

+ 1 - 1
ruoyi-admin/src/main/resources/templates/cc/alittsconf/alittsconf.html

@@ -70,7 +70,7 @@
             contentType: 'application/json',
             data: JSON.stringify(configs),
             beforeSend: function () {
-                $.modal.loading("正在处理中,请稍候...");
+                $.modal.loading(i18n("common.tip.loading"));
                 $.modal.disable();
             },
             success: function(response) {

+ 1 - 1
ruoyi-admin/src/main/resources/templates/cc/asrengine/asrengine.html

@@ -50,7 +50,7 @@
             type: 'GET',
             contentType: 'application/json',
             beforeSend: function () {
-                $.modal.loading("正在处理中,请稍候...");
+                $.modal.loading(i18n("common.tip.loading"));
                 $.modal.disable();
             },
             success: function(response) {

+ 87 - 0
ruoyi-admin/src/main/resources/templates/cc/awsasrconf/awsasrconf.html

@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <th:block th:include="include :: header('亚马逊ASR参数配置')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <style>
+    </style>
+</head>
+
+<body>
+<div class="main-content">
+    <div class="h4 form-header" th:text="#{switchconf.asr.aws.header}"></div>
+    <div id="baseConfigs"></div>
+
+    <!-- 保存按钮 -->
+    <div class="row" ></div>
+    <div class="row" >
+        <div class="col-sm-offset-5 col-sm-10">
+            <button id="saveConfig" type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i><span th:text="#{switchconf.btn.save}"></span></button>&nbsp;
+        </div>
+    </div>
+</div>
+
+
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<script>
+    // 页面加载完成后,发送AJAX请求获取配置数据
+    var prefix = ctx + "cc/fsconf";
+    $(function() {
+        $.ajax({
+            url: prefix + '/getAwsAsrConf',
+            type: 'GET',
+            success: function(_data) {
+                // 假设后端返回的数据格式为 [{"aliasName":"xxx", "name":"xxx", "value":"xxx"},{}]
+                var baseConfigsHtml = '';
+                var data = _data.data;
+
+                // 渲染基础配置
+                $.each(data, function(index, config) {
+                    baseConfigsHtml += '<div className="row">';
+                    baseConfigsHtml += '<div class="col-sm-12">';
+                    baseConfigsHtml += '<label class="col-sm-2 control-label">' + config.aliasName + '</label><div class="col-sm-10"><input class="config-value form-control" type="text" id="' + config.name + '" value="' + config.value + '" /></div>';
+                    baseConfigsHtml += '</div>';
+                    baseConfigsHtml += '</div>';
+                });
+                $('#baseConfigs').html(baseConfigsHtml);
+            },
+            error: function(error) {
+                console.error('Error fetching configuration:', error);
+            }
+        });
+    });
+
+    // 保存配置
+    $('#saveConfig').click(function() {
+        var configs = [];
+        $('input.config-value').each(function() {
+            var config = {
+                name: $(this).attr('id'),
+                value: $(this).val().trim()
+            };
+            configs.push(config);
+        });
+        $.ajax({
+            url: prefix + '/setAwsAsrConf', // 假设后端保存配置的接口是 /setConfig
+            type: 'POST',
+            contentType: 'application/json',
+            data: JSON.stringify(configs),
+            beforeSend: function () {
+                $.modal.loading(i18n("common.tip.loading"));
+                $.modal.disable();
+            },
+            success: function(response) {
+                processAjaxReponseJson(response);
+            },
+            error: function(error) {
+            }
+        });
+    });
+
+</script>
+
+</body>
+</html>

+ 87 - 0
ruoyi-admin/src/main/resources/templates/cc/awsttsconf/awsttsconf.html

@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <th:block th:include="include :: header('亚马逊tts参数配置')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <style>
+    </style>
+</head>
+
+<body>
+<div class="main-content">
+    <div class="h4 form-header" th:text="#{switchconf.tts.aws.header}"></div>
+    <div id="baseConfigs"></div>
+
+    <!-- 保存按钮 -->
+    <div class="row" ></div>
+    <div class="row" >
+        <div class="col-sm-offset-5 col-sm-10">
+            <button id="saveConfig" type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i><span th:text="#{switchconf.btn.save}"></span></button>&nbsp;
+        </div>
+    </div>
+</div>
+
+
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<script>
+    // 页面加载完成后,发送AJAX请求获取配置数据
+    var prefix = ctx + "cc/fsconf";
+    $(function() {
+        $.ajax({
+            url: prefix + '/getAwsTtsConf',
+            type: 'GET',
+            success: function(_data) {
+                // 假设后端返回的数据格式为 [{"aliasName":"xxx", "name":"xxx", "value":"xxx"},{}]
+                var baseConfigsHtml = '';
+                var data = _data.data;
+
+                // 渲染基础配置
+                $.each(data, function(index, config) {
+                    baseConfigsHtml += '<div className="row">';
+                    baseConfigsHtml += '<div class="col-sm-12">';
+                    baseConfigsHtml += '<label class="col-sm-2 control-label">' + config.aliasName + '</label><div class="col-sm-10"><input class="config-value form-control" type="text" id="' + config.name + '" value="' + config.value + '" /></div>';
+                    baseConfigsHtml += '</div>';
+                    baseConfigsHtml += '</div>';
+                });
+                $('#baseConfigs').html(baseConfigsHtml);
+            },
+            error: function(error) {
+                console.error('Error fetching configuration:', error);
+            }
+        });
+    });
+
+    // 保存配置
+    $('#saveConfig').click(function() {
+        var configs = [];
+        $('input.config-value').each(function() {
+            var config = {
+                name: $(this).attr('id'),
+                value: $(this).val().trim()
+            };
+            configs.push(config);
+        });
+        $.ajax({
+            url: prefix + '/setAwsTtsConf', // 假设后端保存配置的接口是 /setConfig
+            type: 'POST',
+            contentType: 'application/json',
+            data: JSON.stringify(configs),
+            beforeSend: function () {
+                $.modal.loading(i18n("common.tip.loading"));
+                $.modal.disable();
+            },
+            success: function(response) {
+                processAjaxReponseJson(response);
+            },
+            error: function(error) {
+            }
+        });
+    });
+
+</script>
+
+</body>
+</html>

+ 1 - 1
ruoyi-admin/src/main/resources/templates/cc/catlogs/catlogs.html

@@ -51,7 +51,7 @@
                 type: 'GET',
                 contentType: 'application/json',
                 beforeSend: function () {
-                    $.modal.loading("正在处理中,请稍候...");
+                    $.modal.loading(i18n("common.tip.loading"));
                     $.modal.disable();
                 },
                 success: function(_data) {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません