edit.html 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. <!DOCTYPE html>
  2. <html lang="zh" xmlns:th="http://www.thymeleaf.org" >
  3. <head>
  4. <th:block th:include="include :: header('修改机器人参数配置')" />
  5. <base target="_blank">
  6. </head>
  7. <body class="white-bg">
  8. <div class="wrapper wrapper-content animated fadeInRight ibox-content">
  9. <form class="form-horizontal m" id="form-account-edit" th:object="${ccLlmAgentAccount}">
  10. <input name="id" th:field="*{id}" type="hidden">
  11. <input name="rootId" id="rootId" type="hidden">
  12. <input name="openingRemarksWav" id="openingRemarksWav" type="hidden">
  13. <input name="customerNoVoiceTipsWav" id="customerNoVoiceTipsWav" type="hidden">
  14. <input name="hangupTipsWav" id="hangupTipsWav" type="hidden">
  15. <input name="transferToAgentTipsWav" id="transferToAgentTipsWav" type="hidden">
  16. <div class="col-xs-12">
  17. <div class="form-group">
  18. <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.name}"></label>
  19. <div class="col-sm-8">
  20. <input name="name" th:field="*{name}" class="form-control" required type="text">
  21. </div>
  22. </div>
  23. </div>
  24. <div class="col-xs-12">
  25. <div class="form-group">
  26. <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.providerClassName}"></label>
  27. <div class="col-sm-8">
  28. <select name="providerClassName" th:field="*{providerClassName}" class="form-control" required id="providerClassNameSelect">
  29. <!-- 选项将通过 JavaScript 动态填充 -->
  30. </select>
  31. </div>
  32. </div>
  33. </div>
  34. <div class="col-xs-12">
  35. <div class="form-group">
  36. <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.concurrentNum}"></label>
  37. <div class="col-sm-8">
  38. <input name="concurrentNum" th:field="*{concurrentNum}" class="form-control" required type="text">
  39. </div>
  40. </div>
  41. </div>
  42. <div class="col-xs-12" id="dynamicFieldsContainer">
  43. <!-- 动态字段将在这里显示 -->
  44. </div>
  45. <div class="col-xs-12">
  46. <div class="form-group">
  47. <label class="col-sm-3 control-label is-required" th:text="#{llmAcount.form.interruptFlag}"></label>
  48. <div class="col-sm-8">
  49. <select name="interruptFlag" class="form-control" required id="interruptFlagSelect" th:field="*{interruptFlag}">
  50. <option value="0" th:text="#{llmAcount.form.interruptFlag0}"></option>
  51. <option value="1" th:text="#{llmAcount.form.interruptFlag1}"></option>
  52. <option value="2" th:text="#{llmAcount.form.interruptFlag2}"></option>
  53. </select>
  54. </div>
  55. </div>
  56. </div>
  57. <div class="col-xs-12" id="interruptKeywordsContainer" style="display: none;">
  58. <div class="form-group">
  59. <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptKeywords}"></label>
  60. <div class="col-sm-8">
  61. <textarea name="interruptKeywords" class="form-control" rows="5" th:field="*{interruptKeywords}"></textarea>
  62. </div>
  63. </div>
  64. </div>
  65. <div class="col-xs-12" id="interruptIgnoreKeywordsContainer" style="display: none;">
  66. <div class="form-group">
  67. <label class="col-sm-3 control-label " th:text="#{llmAcount.form.interruptIgnoreKeywords}"></label>
  68. <div class="col-sm-8">
  69. <textarea name="interruptIgnoreKeywords" class="form-control" rows="5" th:field="*{interruptIgnoreKeywords}"></textarea>
  70. </div>
  71. </div>
  72. </div>
  73. <div class="col-xs-12">
  74. <div class="form-group">
  75. <label class="col-sm-3 control-label" th:text="#{llmAcount.form.transferManualDigit}"></label>
  76. <div class="col-sm-8">
  77. <input name="transferManualDigit" class="form-control" type="text" maxlength="1" placeholder="例如: 1" th:field="*{transferManualDigit}">
  78. </div>
  79. </div>
  80. </div>
  81. <div class="col-xs-12" id="intentionTipsContainer" >
  82. <div class="form-group">
  83. <label class="col-sm-3 control-label" th:text="#{llmAcount.form.intentionTips}"></label>
  84. <div class="col-sm-8">
  85. <textarea name="intentionTips" class="form-control" rows="5" th:field="*{intentionTips}"></textarea>
  86. </div>
  87. </div>
  88. </div>
  89. </form>
  90. </div>
  91. <th:block th:include="include :: footer" />
  92. <script th:inline="javascript">
  93. var prefix = ctx + "aicall/account"
  94. var _hideIntentionTipsContainer = true;
  95. var _accountJson = JSON.parse([[${ccLlmAgentAccount.accountJson}]]);
  96. var _providerClassName = [[${ccLlmAgentAccount.providerClassName}]];
  97. var _dynamicFieldsDiv = initDynamicFieldsDiv(_accountJson);
  98. var _errorMsg = [[${errorMsg}]];
  99. $(document).ready(function() {
  100. // 获取实现类下拉框数据
  101. $.ajax({
  102. url: ctx + "aicall/provider/all", // 接口地址
  103. type: "GET",
  104. success: function(response) {
  105. // 假设返回的数据是一个数组,例如:[{providerClassName: "Provider1"}, {providerClassName: "Provider2"}]
  106. var providers = response.data;
  107. var select = $("#providerClassNameSelect");
  108. select.empty(); // 清空之前的选项
  109. providers.forEach(function(provider) {
  110. select.append($("<option>", {
  111. value: provider.providerClassName,
  112. text: provider.providerClassName
  113. }));
  114. });
  115. select.val(_providerClassName);
  116. // 初始化动态字段
  117. updateDynamicFields(select.val());
  118. },
  119. error: function(xhr, status, error) {
  120. console.error("获取实现类数据失败:", error);
  121. }
  122. });
  123. // 监听下拉框变化
  124. $("#providerClassNameSelect").change(function() {
  125. updateDynamicFields($(this).val());
  126. });
  127. // 监听下拉框变化
  128. $("#providerClassNameSelect").change(function() {
  129. updateDynamicFields($(this).val());
  130. });
  131. //
  132. $("#interruptFlagSelect").change(function() {
  133. toggleInterruptFields($(this).val());
  134. });
  135. // 初始化时根据当前值设置显示状态
  136. toggleInterruptFields($("#interruptFlagSelect").val());
  137. // 转人工数字按键:只允许输入 0-9 的单个数字
  138. $('input[name="transferManualDigit"]').on('input propertychange paste', function () {
  139. let v = this.value;
  140. // 只保留0-9的数字
  141. v = v.replace(/[^0-9]/g, '');
  142. // 限制为单个数字
  143. if (v.length > 1) v = v.substring(0, 1);
  144. this.value = v;
  145. });
  146. // 绑定文件上传按钮事件(动态生成的元素使用事件委托)
  147. $(document).on('click', '.btn-file-upload', function() {
  148. var fieldId = $(this).data('field');
  149. var uploadType = $(this).data('upload-type') || 'voice';
  150. openUploadModal(fieldId, uploadType);
  151. });
  152. // 初始化Wav隐藏字段值(从accountJson中获取)
  153. $('#openingRemarksWav').val(_accountJson.openingRemarksWav || '');
  154. $('#customerNoVoiceTipsWav').val(_accountJson.customerNoVoiceTipsWav || '');
  155. $('#hangupTipsWav').val(_accountJson.hangupTipsWav || '');
  156. $('#transferToAgentTipsWav').val(_accountJson.transferToAgentTipsWav || '');
  157. $('#rootId').val(_accountJson.rootId || '123');
  158. // 错误处理:如果存在错误消息,则显示错误并禁用所有操作
  159. if (_errorMsg && _errorMsg !== '' && _errorMsg !== null) {
  160. // // 禁用表单内所有输入元素
  161. // $('#form-account-edit').find('input, select, textarea').prop('disabled', true);
  162. // 禁用确定
  163. $(window.parent.document).find('.btn-confirm, .layui-layer-btn0').hide();
  164. // 显示错误消息
  165. $.modal.alertError(_errorMsg);
  166. }
  167. // 音频预览回显 - 如果有已上传的录音文件则显示预览
  168. setTimeout(function() {
  169. // 从_accountJson获取文件URL(accountJson的属性)
  170. var fileUrlMapping = {
  171. 'openingRemarks': _accountJson.openingRemarksFileUrl || '',
  172. 'customerNoVoiceTips': _accountJson.customerNoVoiceTipsFileUrl || '',
  173. 'hangupTips': _accountJson.hangupTipsFileUrl || '',
  174. 'transferToAgentTips': _accountJson.transferToAgentTipsFileUrl || ''
  175. };
  176. Object.keys(fileUrlMapping).forEach(function(fieldName) {
  177. var fileUrl = fileUrlMapping[fieldName];
  178. if (fileUrl && fileUrl.endsWith('.wav')) {
  179. showAudioPreview(fieldName, fileUrl);
  180. }
  181. });
  182. }, 500); // 延迟执行,确保动态字段已渲染
  183. });
  184. // 根据选择的实现类动态更新表单字段
  185. function updateDynamicFields(providerClassName) {
  186. var container = $("#dynamicFieldsContainer");
  187. container.empty(); // 清空之前的动态字段
  188. $("#intentionTipsContainer").hide(); // 客户意向提示词默认隐藏
  189. _hideIntentionTipsContainer = true;
  190. if (["DeepSeekChat", "ChatGPT", "ClaudeChat", "JiutianChat"].includes(providerClassName)) {
  191. // 显示serverUrl、apiKey、modelName、
  192. // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  193. container.append(_dynamicFieldsDiv["serverUrl"]);
  194. container.append(_dynamicFieldsDiv["apiKey"]);
  195. container.append(_dynamicFieldsDiv["modelName"]);
  196. container.append(_dynamicFieldsDiv["llmTips"]);
  197. container.append(_dynamicFieldsDiv["faqContext"]);
  198. container.append(_dynamicFieldsDiv["kbCatId"]);
  199. loadKbCatOptions(); // 加载下拉数据
  200. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  201. container.append(_dynamicFieldsDiv["hangupTips"]);
  202. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  203. container.append(_dynamicFieldsDiv["openingRemarks"]);
  204. // intentionTipsContainer
  205. $("#intentionTipsContainer").show();
  206. _hideIntentionTipsContainer = false;
  207. }
  208. if (["XingWenChat"].includes(providerClassName)) {
  209. // 显示serverUrl、apiKey、modelName、
  210. // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  211. container.append(_dynamicFieldsDiv["serverUrl"]);
  212. // container.append(_dynamicFieldsDiv["apiKey"]);
  213. container.append(_dynamicFieldsDiv["modelName"]);
  214. // container.append(_dynamicFieldsDiv["llmTips"]);
  215. // container.append(_dynamicFieldsDiv["faqContext"]);
  216. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  217. container.append(_dynamicFieldsDiv["hangupTips"]);
  218. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  219. container.append(_dynamicFieldsDiv["openingRemarks"]);
  220. // // intentionTipsContainer
  221. // $("#intentionTipsContainer").show();
  222. // _hideIntentionTipsContainer = false;
  223. }
  224. if (["LocalLlmChat"].includes(providerClassName)) {
  225. // 显示serverUrl、apiKey、modelName、
  226. // llmTips、faqContext、transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  227. container.append(_dynamicFieldsDiv["serverUrl"]);
  228. container.append(_dynamicFieldsDiv["apiKey"]);
  229. container.append(_dynamicFieldsDiv["modelName"]);
  230. container.append(_dynamicFieldsDiv["llmTips"]);
  231. container.append(_dynamicFieldsDiv["faqContext"]);
  232. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  233. container.append(_dynamicFieldsDiv["hangupTips"]);
  234. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  235. container.append(_dynamicFieldsDiv["openingRemarks"]);
  236. }
  237. if (["LocalWavFile"].includes(providerClassName)) {
  238. // 同LocalLlmChat显示的元素,但使用带上传按钮的录音字段
  239. container.append(_dynamicFieldsDiv["serverUrl"]);
  240. container.append(_dynamicFieldsDiv["apiKey"]);
  241. container.append(_dynamicFieldsDiv["modelName"]);
  242. container.append(_dynamicFieldsDiv["llmTips"]);
  243. container.append(_dynamicFieldsDiv["faqContext"]);
  244. container.append(_dynamicFieldsDiv["transferToAgentTipsWav"]);
  245. container.append(_dynamicFieldsDiv["hangupTipsWav"]);
  246. container.append(_dynamicFieldsDiv["customerNoVoiceTipsWav"]);
  247. container.append(_dynamicFieldsDiv["openingRemarksWav"]);
  248. }
  249. if (["LocalNlpChat"].includes(providerClassName)) {
  250. container.append(_dynamicFieldsDiv["serverUrl"]);
  251. container.append(_dynamicFieldsDiv["botId"]);
  252. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  253. container.append(_dynamicFieldsDiv["hangupTips"]);
  254. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  255. }
  256. if (["Coze"].includes(providerClassName)) {
  257. // 显示 serverUrl、botId、tokenType、tokenTypeFields、
  258. // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  259. container.append(_dynamicFieldsDiv["serverUrl"]);
  260. container.append(_dynamicFieldsDiv["botId"]);
  261. container.append(_dynamicFieldsDiv["tokenType"]);
  262. container.append(_dynamicFieldsDiv["tokenTypeFields"]);
  263. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  264. container.append(_dynamicFieldsDiv["hangupTips"]);
  265. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  266. container.append(_dynamicFieldsDiv["openingRemarks"]);
  267. // 监听 tokenType 下拉框变化
  268. $("#tokenTypeSelect").change(function() {
  269. var selectedTokenType = $(this).val();
  270. updateTokenTypeSelect(selectedTokenType);
  271. });
  272. $("#tokenTypeSelect").val(_accountJson.tokenType);
  273. var selectedTokenType = $("#tokenTypeSelect").val();
  274. updateTokenTypeSelect(selectedTokenType);
  275. }
  276. if (["MaxKB"].includes(providerClassName)) {
  277. // 显示serverUrl、apiKey、
  278. // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  279. container.append(_dynamicFieldsDiv["serverUrl"]);
  280. container.append(_dynamicFieldsDiv["apiKey"]);
  281. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  282. container.append(_dynamicFieldsDiv["hangupTips"]);
  283. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  284. container.append(_dynamicFieldsDiv["openingRemarks"]);
  285. }
  286. if (["Dify"].includes(providerClassName)) {
  287. // 显示serverUrl、apiKey、
  288. // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  289. container.append(_dynamicFieldsDiv["serverUrl"]);
  290. container.append(_dynamicFieldsDiv["apiKey"]);
  291. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  292. container.append(_dynamicFieldsDiv["hangupTips"]);
  293. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  294. container.append(_dynamicFieldsDiv["openingRemarks"]);
  295. }
  296. if (["JiutianWorkflow", "JiutianAgent"].includes(providerClassName)) {
  297. // 显示serverUrl、apiKey、
  298. // transferToAgentTips、hangupTips、customerNoVoiceTips、openingRemarks
  299. container.append(_dynamicFieldsDiv["serverUrl"]);
  300. container.append(_dynamicFieldsDiv["apiKey"]);
  301. container.append(_dynamicFieldsDiv["botId"]);
  302. container.append(_dynamicFieldsDiv["transferToAgentTips"]);
  303. container.append(_dynamicFieldsDiv["hangupTips"]);
  304. container.append(_dynamicFieldsDiv["customerNoVoiceTips"]);
  305. container.append(_dynamicFieldsDiv["openingRemarks"]);
  306. }
  307. }
  308. function updateTokenTypeSelect(selectedTokenType){
  309. console.log(selectedTokenType)
  310. if (selectedTokenType === "oauth") {
  311. $(".oauthFields").show();
  312. $(".patFields").hide();
  313. } else if (selectedTokenType === "pat") {
  314. $(".patFields").show();
  315. $(".oauthFields").hide();
  316. }
  317. }
  318. // 实时限制:只能输 0-200 的整数
  319. $('input[name="concurrentNum"]').on('input propertychange paste', function () {
  320. let v = this.value;
  321. // 去掉非数字
  322. v = v.replace(/[^0-9]/g, '');
  323. // 去掉前导 0
  324. v = v.replace(/^0+(\d)/, '$1');
  325. // 上限 200
  326. if (v > 200) v = 200;
  327. this.value = v;
  328. });
  329. // 自定义校验方法:0-200 整数
  330. $.validator.addMethod('range0_200', function (value, element) {
  331. return this.optional(element) || (/^\d+$/.test(value) && value >= 0 && value <= 200);
  332. }, '请输入 0-200 之间的整数');
  333. $.validator.addMethod('singleDigit', function (value, element) {
  334. return this.optional(element) || (/^[0-9]$/.test(value));
  335. }, '请输入单个数字 0-9');
  336. // LocalWavFile录音文件必填校验
  337. $.validator.addMethod('wavFileRequired', function (value, element) {
  338. var providerClassName = $("#providerClassNameSelect").val();
  339. if (providerClassName === "LocalWavFile") {
  340. return value && value.trim() !== "";
  341. }
  342. return true;
  343. }, '请上传录音文件');
  344. $("#form-account-edit").validate({
  345. focusCleanup: true,
  346. rules: {
  347. concurrentNum: {
  348. required: true,
  349. range0_200: true
  350. },
  351. transferManualDigit: {
  352. singleDigit: true
  353. }
  354. },
  355. messages: {
  356. concurrentNum: {
  357. required: '必填',
  358. range0_200: '请输入 0-200 之间的整数'
  359. },
  360. transferManualDigit: {
  361. singleDigit: '请输入单个数字 0-9'
  362. }
  363. }
  364. });
  365. function submitHandler() {
  366. if ($("#form-account-edit").valid()) {
  367. var providerClassName = $("#providerClassNameSelect").val();
  368. // LocalWavFile时必须上传录音文件
  369. if (providerClassName === "LocalWavFile") {
  370. var requiredWavFields = ['openingRemarksWav', 'customerNoVoiceTipsWav', 'hangupTipsWav', 'transferToAgentTipsWav'];
  371. var missingFields = [];
  372. requiredWavFields.forEach(function(fieldName) {
  373. var fieldValue = $('#' + fieldName).val();
  374. if (!fieldValue || fieldValue.trim() === "") {
  375. missingFields.push(fieldName);
  376. }
  377. });
  378. if (missingFields.length > 0) {
  379. var fieldNameMap = {
  380. 'openingRemarksWav': '开场白',
  381. 'customerNoVoiceTipsWav': '客户无声音提示',
  382. 'hangupTipsWav': '挂机提示',
  383. 'transferToAgentTipsWav': '转人工提示'
  384. };
  385. var missingNames = missingFields.map(function(f) { return fieldNameMap[f] || f; });
  386. top.layer.msg("以下录音文件必须上传:" + missingNames.join("、"), {icon: 2});
  387. return;
  388. }
  389. }
  390. // 收集动态字段
  391. var dynamicFields = {};
  392. $("#dynamicFieldsContainer").find("input, textarea, select").each(function() {
  393. var fieldName = $(this).attr("name");
  394. var fieldValue = $(this).val();
  395. if (fieldValue === undefined || fieldValue === null) {
  396. fieldValue = ""; // 如果未填写值,则传递空字符串
  397. }
  398. dynamicFields[fieldName] = fieldValue;
  399. });
  400. // 收集Wav字段(从隐藏的input中获取)
  401. var wavFieldMapping = {
  402. 'openingRemarksWav': $('#openingRemarksWav').val(),
  403. 'customerNoVoiceTipsWav': $('#customerNoVoiceTipsWav').val(),
  404. 'hangupTipsWav': $('#hangupTipsWav').val(),
  405. 'transferToAgentTipsWav': $('#transferToAgentTipsWav').val()
  406. };
  407. console.log($('#transferToAgentTipsWav').val())
  408. Object.keys(wavFieldMapping).forEach(function(fieldName) {
  409. var fieldValue = wavFieldMapping[fieldName];
  410. if (fieldValue === undefined || fieldValue === null) {
  411. fieldValue = "";
  412. }
  413. dynamicFields[fieldName] = fieldValue;
  414. console.log(fieldName)
  415. console.log(fieldValue)
  416. });
  417. // 如果 intentionTipsContainer 不显示,则强制清空 intentionTips
  418. if (_hideIntentionTipsContainer) {
  419. $("textarea[name='intentionTips']").val("");
  420. }
  421. // 将 JSON 字符串添加到表单数据中
  422. var formData = $('#form-account-edit').serializeArray();
  423. formData.push({"name": "accountJson", "value": JSON.stringify(dynamicFields)})
  424. $.operate.save(prefix + "/edit", formData);
  425. }
  426. }
  427. function toggleInterruptFields(value) {
  428. if (value === "1") {
  429. document.getElementById('interruptKeywordsContainer').style.display = 'block';
  430. document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'block';
  431. } else {
  432. document.getElementById('interruptKeywordsContainer').style.display = 'none';
  433. document.getElementById('interruptIgnoreKeywordsContainer').style.display = 'none';
  434. }
  435. }
  436. function initDynamicFieldsDiv(_accountJson){
  437. let dynamicFieldsDiv = {};
  438. // serverUrl
  439. dynamicFieldsDiv["serverUrl"] = `<div class="form-group">
  440. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.serverUrl")}</label>
  441. <div class="col-sm-8">
  442. <input name="serverUrl" value="${_accountJson.serverUrl || ''}" class="form-control" type="text" required>
  443. </div>
  444. </div>`;
  445. // apiKey
  446. dynamicFieldsDiv["apiKey"] = `<div class="form-group">
  447. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.apiKey")}</label>
  448. <div class="col-sm-8">
  449. <input name="apiKey" value="${_accountJson.apiKey || ''}" class="form-control" type="text" required>
  450. </div>
  451. </div>`;
  452. // modelName
  453. dynamicFieldsDiv["modelName"] = `<div class="form-group">
  454. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.modelName")}</label>
  455. <div class="col-sm-8">
  456. <input name="modelName" value="${_accountJson.modelName || ''}" class="form-control" type="text" required>
  457. </div>
  458. </div>`;
  459. // botId
  460. dynamicFieldsDiv["botId"] = `<div class="form-group">
  461. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.botId")}</label>
  462. <div class="col-sm-8">
  463. <input name="botId" value="${_accountJson.botId || ''}" class="form-control" type="text" required>
  464. </div>
  465. </div>`;
  466. // tokenType
  467. dynamicFieldsDiv["tokenType"] = `<div class="form-group">
  468. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.tokenType")}</label>
  469. <div class="col-sm-8">
  470. <select name="tokenType" value="${_accountJson.tokenType || ''}" class="form-control" required id="tokenTypeSelect">
  471. <option value="oauth">oauth</option>
  472. <option value="pat">pat</option>
  473. </select>
  474. </div>
  475. </div>`;
  476. // tokenTypeFields
  477. dynamicFieldsDiv["tokenTypeFields"] = `<div id="tokenTypeFields">
  478. <div class="form-group oauthFields" style="display:none;">
  479. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.oauthClientId")}</label>
  480. <div class="col-sm-8">
  481. <input name="oauthClientId" value="${_accountJson.oauthClientId || ''}" class="form-control" type="text" required>
  482. </div>
  483. </div>
  484. <div class="form-group oauthFields" style="display:none;">
  485. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.oauthPrivateKey")}</label>
  486. <div class="col-sm-8">
  487. <input name="oauthPrivateKey" value="${_accountJson.oauthPrivateKey || ''}" class="form-control" type="text" required>
  488. </div>
  489. </div>
  490. <div class="form-group oauthFields" style="display:none;">
  491. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.oauthPublicKeyId")}</label>
  492. <div class="col-sm-8">
  493. <input name="oauthPublicKeyId" value="${_accountJson.oauthPublicKeyId || ''}" class="form-control" type="text" required>
  494. </div>
  495. </div>
  496. <div class="form-group patFields" style="display:none;">
  497. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.patToken")}</label>
  498. <div class="col-sm-8">
  499. <textarea name="patToken" class="form-control" rows="3" required>${_accountJson.patToken || ''}</textarea>
  500. </div>
  501. </div>
  502. </div>`;
  503. // llmTips
  504. dynamicFieldsDiv["llmTips"] = `<div class="form-group">
  505. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.llmTips")}</label>
  506. <div class="col-sm-8">
  507. <textarea name="llmTips" class="form-control" rows="30" required>${_accountJson.llmTips || ''}</textarea>
  508. </div>
  509. </div>`;
  510. // faqContext
  511. dynamicFieldsDiv["faqContext"] = `<div class="form-group">
  512. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.faqContext")}</label>
  513. <div class="col-sm-8">
  514. <textarea name="faqContext" class="form-control" rows="30" required>${_accountJson.faqContext || ''}</textarea>
  515. </div>
  516. </div>`;
  517. dynamicFieldsDiv["kbCatId"] = `<div class="form-group">
  518. <label class="col-sm-3 control-label">${i18n("llmAcount.form.kbCatId")}</label>
  519. <div class="col-sm-8">
  520. <select name="kbCatId" class="form-control" id="kbCatIdSelect">
  521. <option value="">${i18n("llmAcount.form.kbCatId.empty")}</option>
  522. </select>
  523. </div>
  524. </div>`;
  525. // transferToAgentTips
  526. dynamicFieldsDiv["transferToAgentTips"] = `<div class="form-group">
  527. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.transferToAgentTips")}</label>
  528. <div class="col-sm-8">
  529. <textarea name="transferToAgentTips" class="form-control" rows="3" required>${_accountJson.transferToAgentTips || ''}</textarea>
  530. </div>
  531. </div>`;
  532. // hangupTips
  533. dynamicFieldsDiv["hangupTips"] = `<div class="form-group">
  534. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.hangupTips")}</label>
  535. <div class="col-sm-8">
  536. <textarea name="hangupTips" class="form-control" rows="3" required>${_accountJson.hangupTips || ''}</textarea>
  537. </div>
  538. </div>`;
  539. // customerNoVoiceTips
  540. dynamicFieldsDiv["customerNoVoiceTips"] = `<div class="form-group">
  541. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.customerNoVoiceTips")}</label>
  542. <div class="col-sm-8">
  543. <textarea name="customerNoVoiceTips" class="form-control" rows="3" required>${_accountJson.customerNoVoiceTips || ''}</textarea>
  544. </div>
  545. </div>`;
  546. // openingRemarks
  547. dynamicFieldsDiv["openingRemarks"] = `<div class="form-group">
  548. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.openingRemarks")}</label>
  549. <div class="col-sm-8">
  550. <textarea name="openingRemarks" class="form-control" rows="3" required>${_accountJson.openingRemarks || ''}</textarea>
  551. </div>
  552. </div>`;
  553. // openingRemarksWav - 带录音上传按钮
  554. dynamicFieldsDiv["openingRemarksWav"] = `<div class="form-group">
  555. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.openingRemarks")}</label>
  556. <div class="col-sm-8">
  557. <div class="input-group">
  558. <textarea name="openingRemarks" class="form-control" rows="3" required>${_accountJson.openingRemarks || ''}</textarea>
  559. <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
  560. <input type="hidden" name="openingRemarksWav" value="${_accountJson.openingRemarksWav || ''}">
  561. <span class="input-group-btn">
  562. <button type="button" class="btn btn-info btn-file-upload" data-field="openingRemarks" data-upload-type="voice" title="上传录音">
  563. <i class="fa fa-upload"></i> <span>上传</span>
  564. </button>
  565. </span>
  566. </div>
  567. <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
  568. <input type="hidden" id="openingRemarksWavHidden" value="${_accountJson.openingRemarksWav || ''}">
  569. </div>
  570. </div>
  571. <div class="form-group openingRemarksAudioPreview" style="display:none;">
  572. <label class="col-sm-3 control-label">录音预览</label>
  573. <div class="col-sm-8">
  574. <audio controls class="form-control" style="height: 40px;">
  575. <source src="${_accountJson.openingRemarksFileUrl || ''}" type="audio/wav">
  576. 您的浏览器不支持音频播放。
  577. </audio>
  578. </div>
  579. </div>`;
  580. // customerNoVoiceTipsWav - 带录音上传按钮
  581. dynamicFieldsDiv["customerNoVoiceTipsWav"] = `<div class="form-group">
  582. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.customerNoVoiceTips")}</label>
  583. <div class="col-sm-8">
  584. <div class="input-group">
  585. <textarea name="customerNoVoiceTips" class="form-control" rows="3" required>${_accountJson.customerNoVoiceTips || ''}</textarea>
  586. <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
  587. <input type="hidden" name="customerNoVoiceTipsWav" value="${_accountJson.customerNoVoiceTipsWav || ''}">
  588. <span class="input-group-btn">
  589. <button type="button" class="btn btn-info btn-file-upload" data-field="customerNoVoiceTips" data-upload-type="voice" title="上传录音">
  590. <i class="fa fa-upload"></i> <span>上传</span>
  591. </button>
  592. </span>
  593. </div>
  594. <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
  595. <input type="hidden" id="customerNoVoiceTipsWavHidden" value="${_accountJson.customerNoVoiceTipsWav || ''}">
  596. </div>
  597. </div>
  598. <div class="form-group customerNoVoiceTipsAudioPreview" style="display:none;">
  599. <label class="col-sm-3 control-label">录音预览</label>
  600. <div class="col-sm-8">
  601. <audio controls class="form-control" style="height: 40px;">
  602. <source src="${_accountJson.customerNoVoiceTipsFileUrl || ''}" type="audio/wav">
  603. 您的浏览器不支持音频播放。
  604. </audio>
  605. </div>
  606. </div>`;
  607. // hangupTipsWav - 带录音上传按钮
  608. dynamicFieldsDiv["hangupTipsWav"] = `<div class="form-group">
  609. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.hangupTips")}</label>
  610. <div class="col-sm-8">
  611. <div class="input-group">
  612. <textarea name="hangupTips" class="form-control" rows="3" required>${_accountJson.hangupTips || ''}</textarea>
  613. <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
  614. <input type="hidden" name="hangupTipsWav" value="${_accountJson.hangupTipsWav || ''}">
  615. <span class="input-group-btn">
  616. <button type="button" class="btn btn-info btn-file-upload" data-field="hangupTips" data-upload-type="voice" title="上传录音">
  617. <i class="fa fa-upload"></i> <span>上传</span>
  618. </button>
  619. </span>
  620. </div>
  621. <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
  622. <input type="hidden" id="hangupTipsWavHidden" value="${_accountJson.hangupTipsWav || ''}">
  623. </div>
  624. </div>
  625. <div class="form-group hangupTipsAudioPreview" style="display:none;">
  626. <label class="col-sm-3 control-label">录音预览</label>
  627. <div class="col-sm-8">
  628. <audio controls class="form-control" style="height: 40px;">
  629. <source src="${_accountJson.hangupTipsFileUrlv || ''}" type="audio/wav">
  630. 您的浏览器不支持音频播放。
  631. </audio>
  632. </div>
  633. </div>`;
  634. // transferToAgentTipsWav - 带录音上传按钮
  635. dynamicFieldsDiv["transferToAgentTipsWav"] = `<div class="form-group">
  636. <label class="col-sm-3 control-label is-required">${i18n("llmAcount.form.transferToAgentTips")}</label>
  637. <div class="col-sm-8">
  638. <div class="input-group">
  639. <textarea name="transferToAgentTips" class="form-control" rows="3" required>${_accountJson.transferToAgentTips || ''}</textarea>
  640. <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
  641. <input type="hidden" name="transferToAgentTipsWav" value="${_accountJson.transferToAgentTipsWav || ''}">
  642. <span class="input-group-btn">
  643. <button type="button" class="btn btn-info btn-file-upload" data-field="transferToAgentTips" data-upload-type="voice" title="上传录音">
  644. <i class="fa fa-upload"></i> <span>上传</span>
  645. </button>
  646. </span>
  647. </div>
  648. <span class="help-block m-b-none">支持上传wav格式的录音文件</span>
  649. <input type="hidden" id="transferToAgentTipsWavHidden" value="${_accountJson.transferToAgentTipsWav || ''}">
  650. </div>
  651. </div>
  652. <div class="form-group transferToAgentTipsAudioPreview" style="display:none;">
  653. <label class="col-sm-3 control-label">录音预览</label>
  654. <div class="col-sm-8">
  655. <audio controls class="form-control" style="height: 40px;">
  656. <source src="${_accountJson.transferToAgentTipsFileUrl || ''}" type="audio/wav">
  657. 您的浏览器不支持音频播放。
  658. </audio>
  659. </div>
  660. </div>`;
  661. return dynamicFieldsDiv;
  662. }
  663. // 加载知识库分类下拉框数据
  664. function loadKbCatOptions() {
  665. $.ajax({
  666. url: ctx + "aicall/kbcat/all",
  667. type: "GET",
  668. success: function(response) {
  669. if (response.code === 0 || response.code === 200) {
  670. var cats = response.data || [];
  671. var select = $("#kbCatIdSelect");
  672. select.empty();
  673. select.append('<option value="">请选择</option>');
  674. cats.forEach(function(item) {
  675. select.append($("<option>", {
  676. value: item.id,
  677. text: item.cat
  678. }));
  679. });
  680. // 回显已保存的值
  681. if (_accountJson && _accountJson.kbCatId) {
  682. select.val(_accountJson.kbCatId);
  683. }
  684. }
  685. },
  686. error: function(xhr, status, error) {
  687. console.error("获取知识库分类数据失败:", error);
  688. }
  689. });
  690. }
  691. // ========== 文件上传功能 ==========
  692. // 打开文件上传模态框
  693. function openUploadModal(fieldId, uploadType) {
  694. var modalHtml = `
  695. <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel">
  696. <div class="modal-dialog" role="document">
  697. <div class="modal-content">
  698. <div class="modal-header">
  699. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  700. <span aria-hidden="true">&times;</span>
  701. </button>
  702. <h4 class="modal-title" id="uploadModalLabel">上传录音文件</h4>
  703. </div>
  704. <div class="modal-body">
  705. <form id="uploadForm" enctype="multipart/form-data">
  706. <div class="form-group">
  707. <label>选择文件</label>
  708. <input type="file" id="fileInput" name="file" accept=".wav"
  709. class="form-control" style="height:auto;">
  710. <span class="help-block">仅支持wav格式,最大50MB</span>
  711. </div>
  712. <div class="form-group" id="uploadProgress" style="display:none;">
  713. <label>上传进度</label>
  714. <div class="progress">
  715. <div class="progress-bar" role="progressbar" style="width: 0%;">
  716. 0%
  717. </div>
  718. </div>
  719. </div>
  720. </form>
  721. </div>
  722. <div class="modal-footer">
  723. <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
  724. <button type="button" class="btn btn-primary" id="confirmUpload">确认上传</button>
  725. </div>
  726. </div>
  727. </div>
  728. </div>
  729. `;
  730. $('#uploadModal').remove();
  731. $('body').append(modalHtml);
  732. var $modal = $('#uploadModal');
  733. var $fileInput = $('#fileInput');
  734. $modal.modal('show');
  735. $('#confirmUpload').off('click').on('click', function() {
  736. var file = $fileInput[0].files[0];
  737. if (!file) {
  738. top.layer.msg("请选择文件", {icon: 2});
  739. return;
  740. }
  741. if (!validateAudioFile(file)) {
  742. return;
  743. }
  744. var rootId = $('#rootId').val();
  745. uploadFileWithProgress(file, fieldId, rootId, function(success, result) {
  746. $modal.modal('hide');
  747. if (success) {
  748. // 更新对应的Wav隐藏字段(使用Hidden ID)
  749. var wavHiddenId = '#' + fieldId + 'WavHidden';
  750. $(wavHiddenId).val(result.filePath);
  751. $('#' + fieldId + 'Wav').val(result.filePath);
  752. // 显示音频预览
  753. showAudioPreview(fieldId, result.fileUrl);
  754. top.layer.msg("上传成功", {icon: 1});
  755. } else {
  756. top.layer.msg("上传失败:" + result, {icon: 2});
  757. }
  758. });
  759. });
  760. $modal.off('hidden.bs.modal').on('hidden.bs.modal', function() {
  761. $modal.remove();
  762. });
  763. }
  764. // 验证音频文件
  765. function validateAudioFile(file) {
  766. if (!file.name.toLowerCase().endsWith('.wav')) {
  767. top.layer.msg("仅支持wav格式文件", {icon: 2});
  768. return false;
  769. }
  770. var maxSize = 50 * 1024 * 1024; // 50MB
  771. if (file.size > maxSize) {
  772. top.layer.msg("文件大小不能超过50MB", {icon: 2});
  773. return false;
  774. }
  775. return true;
  776. }
  777. // 带进度条上传
  778. function uploadFileWithProgress(file, fieldId, rootId, callback) {
  779. var formData = new FormData();
  780. formData.append('file', file);
  781. formData.append('rootId', rootId);
  782. $('#uploadProgress').show();
  783. $.ajax({
  784. url: ctx + 'cc/ivr/uploadVoice', // 复用IVR的上传接口
  785. type: 'POST',
  786. data: formData,
  787. processData: false,
  788. contentType: false,
  789. xhr: function() {
  790. var xhr = $.ajaxSettings.xhr();
  791. if (xhr.upload) {
  792. xhr.upload.addEventListener('progress', function(e) {
  793. if (e.lengthComputable) {
  794. var percent = Math.floor((e.loaded / e.total) * 100);
  795. $('.progress-bar').css('width', percent + '%').text(percent + '%');
  796. }
  797. }, false);
  798. }
  799. return xhr;
  800. },
  801. success: function(rsp) {
  802. if (rsp.code === 0 || rsp.code === 200) {
  803. callback(true, rsp.data);
  804. } else {
  805. callback(false, rsp.msg);
  806. }
  807. },
  808. error: function(xhr, status, error) {
  809. callback(false, "网络错误:" + error);
  810. }
  811. });
  812. }
  813. // 显示音频预览
  814. function showAudioPreview(fieldId, fileUrl) {
  815. var previewClass = '.' + fieldId + 'AudioPreview';
  816. var $preview = $(previewClass);
  817. if ($preview.length && fileUrl) {
  818. $preview.find('audio source').attr('src', ctx + fileUrl);
  819. $preview.find('audio')[0].load();
  820. $preview.show();
  821. }
  822. }
  823. </script>
  824. </body>
  825. </html>