#
Junjie
19 小时以前 cbb00d4729243e4949b3c921fc2f94cb03ca8aaa
src/main/webapp/views/ai/llm_config.html
@@ -309,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">
@@ -393,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>
@@ -431,6 +435,12 @@
      </el-table-column>
      <el-table-column prop="httpStatus" label="状态码" width="90"></el-table-column>
      <el-table-column prop="latencyMs" label="耗时(ms)" width="95"></el-table-column>
      <el-table-column label="Tokens" width="140">
        <template slot-scope="scope">
          <div>{{ logTotalTokens(scope.row) }}</div>
          <div style="color:#909399;font-size:12px;">输{{ logPromptTokens(scope.row) }} / 出{{ logCompletionTokens(scope.row) }}</div>
        </template>
      </el-table-column>
      <el-table-column prop="traceId" label="TraceId" width="230"></el-table-column>
      <el-table-column label="错误" min-width="220">
        <template slot-scope="scope">
@@ -457,7 +467,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>
@@ -511,6 +521,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);
@@ -797,13 +832,35 @@
          + '模型: ' + (row.model || '-') + '\n'
          + '状态码: ' + (row.httpStatus != null ? row.httpStatus : '-') + '\n'
          + '耗时: ' + (row.latencyMs != null ? row.latencyMs : '-') + ' ms\n'
          + 'Tokens: ' + this.logTotalTokens(row) + '(输' + this.logPromptTokens(row) + ' / 出' + this.logCompletionTokens(row) + ')\n'
          + '结果: ' + (row.success === 1 ? '成功' : '失败') + '\n'
          + '错误: ' + (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;
      },
      parseLogExtra: function(row) {
        if (!row || !row.extra) return {};
        if (typeof row.extra === 'object') return row.extra;
        try {
          return JSON.parse(row.extra);
        } catch (e) {
          return {};
        }
      },
      logPromptTokens: function(row) {
        var extra = this.parseLogExtra(row);
        return extra && extra.promptTokens != null ? extra.promptTokens : '-';
      },
      logCompletionTokens: function(row) {
        var extra = this.parseLogExtra(row);
        return extra && extra.completionTokens != null ? extra.completionTokens : '-';
      },
      logTotalTokens: function(row) {
        var extra = this.parseLogExtra(row);
        return extra && extra.totalTokens != null ? extra.totalTokens : '-';
      },
      deleteLog: function(row) {
        var self = this;
@@ -1006,6 +1063,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();
    }
  });