From b37e141c00a123cf362fae00c1e63175d41c4bbe Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 10 三月 2026 17:01:06 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/ai/llm_config.html |  189 +++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 183 insertions(+), 6 deletions(-)

diff --git a/src/main/webapp/views/ai/llm_config.html b/src/main/webapp/views/ai/llm_config.html
index e5c052e..d6e2849 100644
--- a/src/main/webapp/views/ai/llm_config.html
+++ b/src/main/webapp/views/ai/llm_config.html
@@ -47,6 +47,13 @@
       font-size: 12px;
       opacity: 0.9;
     }
+    .hero-actions {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      flex-wrap: wrap;
+      justify-content: flex-end;
+    }
     .summary-grid {
       margin-top: 10px;
       display: grid;
@@ -260,8 +267,10 @@
           <div class="sub">鏀寔澶欰PI銆佸妯″瀷銆佸Key锛岄搴﹁�楀敖鎴栨晠闅滆嚜鍔ㄥ垏鎹�</div>
         </div>
       </div>
-      <div>
+      <div class="hero-actions">
         <el-button type="primary" size="mini" @click="addRoute">鏂板璺敱</el-button>
+        <el-button size="mini" @click="exportRoutes">瀵煎嚭JSON</el-button>
+        <el-button size="mini" @click="triggerImport">瀵煎叆JSON</el-button>
         <el-button size="mini" @click="loadRoutes">鍒锋柊</el-button>
         <el-button size="mini" @click="openLogDialog">璋冪敤鏃ュ織</el-button>
       </div>
@@ -289,6 +298,7 @@
       </div>
     </div>
   </div>
+  <input ref="importFileInput" type="file" accept="application/json,.json" style="display:none;" @change="handleImportFileChange" />
 
   <div class="route-board" v-loading="loading">
     <div v-if="!routes || routes.length === 0" class="empty-shell">
@@ -299,7 +309,11 @@
       <div class="route-card" :class="routeCardClass(route)" v-for="(route, idx) in routes" :key="route.id ? ('route_' + route.id) : ('new_' + idx)">
         <div class="route-head">
           <div class="route-title">
-            <el-input v-model="route.name" size="mini" placeholder="璺敱鍚嶇О"></el-input>
+            <el-input
+              :value="displayRouteName(route)"
+              size="mini"
+              placeholder="璺敱鍚嶇О"
+              @input="handleRouteNameInput(route, $event)"></el-input>
             <div class="route-id-line">#{{ route.id || 'new' }} 路 浼樺厛绾� {{ route.priority || 0 }}</div>
           </div>
           <div class="route-state">
@@ -383,7 +397,7 @@
     </div>
   </div>
 
-  <el-dialog title="LLM璋冪敤鏃ュ織" :visible.sync="logDialogVisible" width="88%" :close-on-click-modal="false">
+  <el-dialog :title="i18n('llm.logsTitle', 'LLM璋冪敤鏃ュ織')" :visible.sync="logDialogVisible" width="88%" :close-on-click-modal="false">
     <div class="log-toolbar">
       <el-select v-model="logQuery.scene" size="mini" clearable placeholder="鍦烘櫙" style="width:180px;">
         <el-option label="chat" value="chat"></el-option>
@@ -447,7 +461,7 @@
     </div>
   </el-dialog>
 
-  <el-dialog :title="logDetailTitle || '鏃ュ織璇︽儏'" :visible.sync="logDetailVisible" width="82%" :close-on-click-modal="false" append-to-body>
+  <el-dialog :title="logDetailTitle || i18n('llm.logDetailTitle', '鏃ュ織璇︽儏')" :visible.sync="logDetailVisible" width="82%" :close-on-click-modal="false" append-to-body>
     <div class="log-detail-body">{{ logDetailText || '-' }}</div>
     <span slot="footer" class="dialog-footer">
       <el-button size="mini" @click="copyText(logDetailText)">澶嶅埗鍏ㄦ枃</el-button>
@@ -458,7 +472,7 @@
 
 <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
 <script type="text/javascript" src="../../static/vue/element/element.js"></script>
-<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/common.js?v=20260309_i18n_fix1" charset="utf-8"></script>
 <script>
   new Vue({
     el: '#app',
@@ -501,6 +515,31 @@
       }
     },
     methods: {
+      i18n: function(key, fallback, params) {
+        if (window.WCS_I18N && typeof window.WCS_I18N.t === 'function') {
+          var translated = window.WCS_I18N.t(key, params);
+          if (translated && translated !== key) {
+            return translated;
+          }
+        }
+        return fallback || key;
+      },
+      translateLegacyText: function(text) {
+        if (window.WCS_I18N && typeof window.WCS_I18N.tl === 'function') {
+          return window.WCS_I18N.tl(text);
+        }
+        return text;
+      },
+      displayRouteName: function(route) {
+        var value = route && route.name ? String(route.name) : '';
+        return this.translateLegacyText(value);
+      },
+      handleRouteNameInput: function(route, value) {
+        if (!route) {
+          return;
+        }
+        route.name = value;
+      },
       formatDateTime: function(input) {
         if (!input) return '-';
         var d = input instanceof Date ? input : new Date(input);
@@ -597,6 +636,138 @@
       authHeaders: function() {
         return { 'token': localStorage.getItem('token') };
       },
+      exportRoutes: function() {
+        var self = this;
+        fetch(baseUrl + '/ai/llm/config/export/auth', { headers: self.authHeaders() })
+          .then(function(r){ return r.json(); })
+          .then(function(res){
+            if (!res || res.code !== 200) {
+              self.$message.error((res && res.msg) ? res.msg : '瀵煎嚭澶辫触');
+              return;
+            }
+            var payload = res.data || {};
+            var text = JSON.stringify(payload, null, 2);
+            var name = 'llm_routes_' + self.buildExportTimestamp() + '.json';
+            var blob = new Blob([text], { type: 'application/json;charset=utf-8' });
+            var a = document.createElement('a');
+            a.href = URL.createObjectURL(blob);
+            a.download = name;
+            document.body.appendChild(a);
+            a.click();
+            setTimeout(function() {
+              URL.revokeObjectURL(a.href);
+              document.body.removeChild(a);
+            }, 0);
+            self.$message.success('瀵煎嚭鎴愬姛');
+          })
+          .catch(function(){
+            self.$message.error('瀵煎嚭澶辫触');
+          });
+      },
+      buildExportTimestamp: function() {
+        var d = new Date();
+        var pad = function(n) { return n < 10 ? ('0' + n) : String(n); };
+        return d.getFullYear()
+          + pad(d.getMonth() + 1)
+          + pad(d.getDate())
+          + '_'
+          + pad(d.getHours())
+          + pad(d.getMinutes())
+          + pad(d.getSeconds());
+      },
+      triggerImport: function() {
+        var input = this.$refs.importFileInput;
+        if (!input) return;
+        input.value = '';
+        input.click();
+      },
+      handleImportFileChange: function(evt) {
+        var self = this;
+        var files = evt && evt.target && evt.target.files ? evt.target.files : null;
+        var file = files && files.length > 0 ? files[0] : null;
+        if (!file) return;
+        var reader = new FileReader();
+        reader.onload = function(e) {
+          var text = e && e.target ? e.target.result : '';
+          var parsed;
+          try {
+            parsed = JSON.parse(text || '{}');
+          } catch (err) {
+            self.$message.error('JSON 鏍煎紡涓嶆纭�');
+            return;
+          }
+          var routes = self.extractImportRoutes(parsed);
+          if (!routes || routes.length === 0) {
+            self.$message.warning('鏈壘鍒板彲瀵煎叆鐨� routes');
+            return;
+          }
+          self.$confirm(
+            '璇烽�夋嫨瀵煎叆鏂瑰紡锛氳鐩栧鍏ヤ細鍏堟竻绌虹幇鏈夎矾鐢憋紱鐐瑰嚮鈥滃悎骞跺鍏モ�濆垯鎸塈D鏇存柊鎴栨柊澧炪��',
+            '瀵煎叆纭',
+            {
+              type: 'warning',
+              distinguishCancelAndClose: true,
+              confirmButtonText: '瑕嗙洊瀵煎叆',
+              cancelButtonText: '鍚堝苟瀵煎叆',
+              closeOnClickModal: false
+            }
+          ).then(function() {
+            self.doImportRoutes(routes, true);
+          }).catch(function(action) {
+            if (action === 'cancel') {
+              self.doImportRoutes(routes, false);
+            }
+          });
+        };
+        reader.onerror = function() {
+          self.$message.error('璇诲彇鏂囦欢澶辫触');
+        };
+        reader.readAsText(file, 'utf-8');
+      },
+      extractImportRoutes: function(parsed) {
+        if (Array.isArray(parsed)) return parsed;
+        if (!parsed || typeof parsed !== 'object') return [];
+        if (Array.isArray(parsed.routes)) return parsed.routes;
+        if (parsed.data && Array.isArray(parsed.data.routes)) return parsed.data.routes;
+        if (Array.isArray(parsed.data)) return parsed.data;
+        return [];
+      },
+      doImportRoutes: function(routes, replace) {
+        var self = this;
+        fetch(baseUrl + '/ai/llm/config/import/auth', {
+          method: 'POST',
+          headers: Object.assign({ 'Content-Type': 'application/json' }, self.authHeaders()),
+          body: JSON.stringify({
+            replace: replace === true,
+            routes: routes
+          })
+        })
+          .then(function(r){ return r.json(); })
+          .then(function(res){
+            if (!res || res.code !== 200) {
+              self.$message.error((res && res.msg) ? res.msg : '瀵煎叆澶辫触');
+              return;
+            }
+            var d = res.data || {};
+            var msg = '瀵煎叆瀹屾垚锛氭柊澧� ' + (d.inserted || 0)
+              + '锛屾洿鏂� ' + (d.updated || 0)
+              + '锛岃烦杩� ' + (d.skipped || 0);
+            if (d.errorCount && d.errorCount > 0) {
+              msg += '锛屽紓甯� ' + d.errorCount;
+            }
+            self.$message.success(msg);
+            if (Array.isArray(d.errors) && d.errors.length > 0) {
+              self.$alert(d.errors.join('\n'), '瀵煎叆寮傚父鏄庣粏锛堟渶澶�20鏉★級', {
+                confirmButtonText: '纭畾',
+                type: 'warning'
+              });
+            }
+            self.loadRoutes();
+          })
+          .catch(function(){
+            self.$message.error('瀵煎叆澶辫触');
+          });
+      },
       handleRouteCommand: function(command, route, idx) {
         if (command === 'test') return this.testRoute(route);
         if (command === 'save') return this.saveRoute(route);
@@ -659,7 +830,7 @@
           + '閿欒: ' + (row.errorMessage || '-') + '\n\n'
           + '璇锋眰:\n' + (row.requestContent || '-') + '\n\n'
           + '鍝嶅簲:\n' + (row.responseContent || '-');
-        this.logDetailTitle = '鏃ュ織璇︽儏 - ' + (row.traceId || row.id || '');
+        this.logDetailTitle = this.i18n('llm.logDetailPrefix', '鏃ュ織璇︽儏 - ') + (row.traceId || row.id || '');
         this.logDetailText = text;
         this.logDetailVisible = true;
       },
@@ -864,6 +1035,12 @@
       }
     },
     mounted: function() {
+      var self = this;
+      if (window.WCS_I18N && typeof window.WCS_I18N.onReady === 'function') {
+        window.WCS_I18N.onReady(function() {
+          self.$forceUpdate();
+        });
+      }
       this.loadRoutes();
     }
   });

--
Gitblit v1.9.1