CoolFace
Apppublic

devin15/cursor2api-rust

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
logs.html721 linesDownload Raw Back to static
1<!DOCTYPE html>2<html lang="zh">3 4<head>5  <meta charset="UTF-8">6  <link rel="icon" type="image/x-icon" href="data:image/x-icon;,">7  <meta name="viewport" content="width=device-width, initial-scale=1.0">8  <title>请求日志查看</title>9  <!-- 引入共享样式 -->10  <link rel="stylesheet" href="/static/shared-styles.css">11  <script src="/static/shared.js"></script>12  <style>13    /* 创建正确的堆叠上下文 */14    .stats-grid {15      display: grid;16      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));17      gap: 20px;18      margin-bottom: var(--spacing);19    }20 21    .stat-card {22      background: var(--card-background);23      padding: 20px;24      border-radius: var(--border-radius);25      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);26      transition: all var(--transition-fast);27      border: 1px solid var(--border-color);28    }29 30    .stat-card:hover {31      transform: translateY(-2px);32      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);33    }34 35    .stat-card h4 {36      margin: 0 0 8px 0;37      color: var(--primary-color);38    }39 40    .stat-value {41      font-size: 28px;42      font-weight: 600;43      color: var(--primary-color);44      margin-top: 4px;45    }46 47    .refresh-container {48      display: flex;49      justify-content: space-between;50      align-items: center;51    }52 53    .auto-refresh {54      display: flex;55      align-items: center;56      gap: 8px;57      background: var(--card-background);58      padding: 8px 16px;59      border-radius: var(--border-radius);60      border: 1px solid var(--border-color);61    }62 63    .modal {64      display: none;65      position: fixed;66      z-index: 1000;67      left: 0;68      top: 0;69      width: 100%;70      height: 100%;71      background-color: rgba(0, 0, 0, 0.4);72      overflow-y: hidden;73    }74 75    .modal-content {76      background-color: var(--card-background);77      margin: 5% auto;78      padding: 20px;79      border-radius: var(--border-radius);80      width: 90%;81      max-width: 800px;82      max-height: 85vh;83      overflow-y: auto;84      border: 1px solid var(--border-color);85      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);86    }87 88    .close {89      float: right;90      cursor: pointer;91      font-size: 28px;92    }93 94    .info-button {95      padding: 6px 12px;96      font-size: 14px;97      border-radius: var(--border-radius);98      transition: all var(--transition-fast);99      background: var(--primary-color-alpha);100      color: var(--primary-color);101      border: 1px solid var(--primary-color);102    }103 104    .info-button:hover {105      background: var(--primary-color);106      color: white;107    }108 109    .message-table {110      width: 100%;111      border-collapse: collapse;112      margin-top: 10px;113      margin: 0;114      border: 1px solid var(--border-color);115    }116 117    .message-table th,118    .message-table td {119      padding: 12px 16px;120      border-bottom: 1px solid var(--border-color);121      transition: background-color var(--transition-fast);122    }123 124    .message-table td {125      word-break: break-word;126    }127 128    .message-table td:nth-child(2) {129      max-width: 600px;130    }131 132    .message-table td:first-child {133      width: 80px;134      white-space: nowrap;135    }136 137    .modal-header {138      display: flex;139      justify-content: space-between;140      align-items: center;141      margin-bottom: 15px;142    }143 144    .modal-header h3 {145      margin: 0;146    }147 148    .close {149      font-size: 24px;150      font-weight: bold;151      cursor: pointer;152      padding: 5px 10px;153    }154 155    .close:hover {156      color: var(--primary-color);157    }158 159    .usage-progress-container {160      margin: 16px 0;161      height: 8px;162      background-color: var(--border-color);163      border-radius: 4px;164      overflow: hidden;165    }166 167    .usage-progress-bar {168      height: 100%;169      width: 0%;170      transition: width 0.3s ease;171      background-color: var(--primary-color);172      border-radius: 4px;173    }174 175    /* 根据使用比例改变颜色 */176    .usage-progress-bar.low {177      background-color: #4caf50;178      /* 绿色 */179    }180 181    .usage-progress-bar.medium {182      background-color: #ff9800;183      /* 橙色 */184    }185 186    .usage-progress-bar.high {187      background-color: #f44336;188      /* 红色 */189    }190 191    /* Token 信息和对话预览的通用样式 */192    .token-info-tooltip {193      position: relative;194      display: inline-block;195    }196 197    .token-info-tooltip .tooltip-content {198      visibility: hidden;199      position: absolute;200      z-index: 1002;201      background-color: var(--card-background);202      padding: 12px 15px;203      border-radius: var(--border-radius);204      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);205      width: 280px;206      left: 50%;207      transform: translateX(-50%);208      bottom: calc(100% + 8px);209      opacity: 0;210      transition: opacity 0.3s, visibility 0.3s;211      text-align: left;212      line-height: 1.6;213      border: 1px solid var(--border-color);214      pointer-events: none;215    }216 217    .token-info-tooltip:hover .tooltip-content {218      visibility: visible;219      opacity: 1;220    }221 222    /* 添加小三角形指示器 */223    .token-info-tooltip .tooltip-content::after {224      content: '';225      position: absolute;226      top: 100%;227      left: 50%;228      margin-left: -8px;229      border-width: 8px;230      border-style: solid;231      border-color: var(--card-background) transparent transparent transparent;232    }233 234    /* 添加不可见的连接区域 */235    .token-info-tooltip::after {236      content: '';237      position: absolute;238      left: 50%;239      transform: translateX(-50%);240      bottom: 100%;241      width: 100%;242      height: 10px;243      background: transparent;244    }245 246    /* Token 信息特定样式 */247    .token-info-tooltip .tooltip-info-row {248      display: flex;249      justify-content: space-between;250      margin: 2px 0;251    }252 253    .token-info-tooltip .tooltip-info-row .label {254      color: var(--text-secondary);255      margin-right: 10px;256    }257 258    .token-info-tooltip .tooltip-info-row .value {259      font-weight: 500;260      word-break: break-word;261    }262 263    /* 对话预览特定样式 */264    .prompt-preview .tooltip-content {265      width: 320px;266      max-height: 200px;267      overflow-y: auto;268      overflow-x: hidden;269      white-space: pre-wrap;270      word-break: break-word;271    }272 273    .prompt-preview .tooltip-content .message-meta {274      font-size: 0.8em;275      color: var(--text-secondary);276      padding: 0;277      margin: 0 0 4px 0;278    }279 280    .prompt-preview .tooltip-content .last-message {281      font-size: 0.9em;282      line-height: 1.5;283      color: var(--text-primary);284      margin: 0;285      padding: 0;286      white-space: pre-wrap;287      word-break: break-word;288    }289 290    /* 优化滚动条样式 */291    .prompt-preview .tooltip-content::-webkit-scrollbar {292      width: 6px;293    }294 295    .prompt-preview .tooltip-content::-webkit-scrollbar-thumb {296      background-color: var(--border-color);297      border-radius: 3px;298    }299 300    .prompt-preview .tooltip-content::-webkit-scrollbar-track {301      background-color: var(--card-background);302    }303 304    /* 优化表格样式 */305    .table-container {306      border-radius: var(--border-radius);307      overflow: hidden;308      border: 1px solid var(--border-color);309    }310 311    #logsTable {312      position: relative;313      z-index: 1;314    }315 316    #logsTable th {317      position: sticky;318      top: 0;319      z-index: 2;320      background: var(--primary-color);321      white-space: nowrap;322      transition: background-color 0.2s ease;323    }324 325    #logsTable td {326      padding: 12px 16px;327      border-bottom: 1px solid var(--border-color);328      transition: background-color var(--transition-fast);329    }330 331    /* 响应式优化 */332    @media (max-width: 768px) {333      .stats-grid {334        grid-template-columns: repeat(2, 1fr);335        gap: 12px;336      }337 338      .stat-card {339        padding: 16px;340      }341 342      .stat-value {343        font-size: 24px;344      }345 346      .modal-content {347        margin: 2% auto;348        width: 95%;349        padding: 16px;350      }351    }352 353    /* 优化表格悬停效果 */354    #logsTable tr:hover td {355      background-color: var(--hover-color, rgba(0, 0, 0, 0.02));356    }357  </style>358</head>359 360<body>361  <h1>请求日志查看</h1>362 363  <div class="container">364    <div class="form-group">365      <label>认证令牌:</label>366      <input type="password" id="authToken" placeholder="输入 AUTH_TOKEN">367    </div>368    <div class="refresh-container">369      <div class="button-group">370        <button onclick="fetchLogs()">刷新日志</button>371      </div>372      <div class="auto-refresh">373        <input type="checkbox" id="autoRefresh" checked>374        <label for="autoRefresh">自动刷新 (60秒)</label>375      </div>376    </div>377  </div>378 379  <div class="container">380    <div class="stats-grid">381      <div class="stat-card">382        <h4>总请求数</h4>383        <div id="totalRequests" class="stat-value">-</div>384      </div>385      <div class="stat-card">386        <h4>活跃请求数</h4>387        <div id="activeRequests" class="stat-value">-</div>388      </div>389      <div class="stat-card">390        <h4>错误请求数</h4>391        <div id="errorRequests" class="stat-value">-</div>392      </div>393      <div class="stat-card">394        <h4>最后更新</h4>395        <div id="lastUpdate" class="stat-value">-</div>396      </div>397    </div>398 399    <div class="table-container">400      <table id="logsTable">401        <thead>402          <tr>403            <th>序</th>404            <th>时间</th>405            <th>模型</th>406            <th>Token信息</th>407            <th>Prompt</th>408            <th>用时/首字</th>409            <th>流式响应</th>410            <th>状态</th>411            <th>错误信息</th>412          </tr>413        </thead>414        <tbody id="logsBody"></tbody>415      </table>416    </div>417  </div>418 419  <div id="message"></div>420 421  <!-- 添加弹窗组件 -->422  <div id="tokenModal" class="modal">423    <div class="modal-content">424      <div class="modal-header">425        <h3>Token 详细信息</h3>426        <span class="close">&times;</span>427      </div>428      <table class="message-table">429        <tr>430          <td>Token:</td>431          <td id="modalToken"></td>432        </tr>433        <tr>434          <td>校验和:</td>435          <td id="modalChecksum"></td>436        </tr>437        <tr>438          <td colspan="2" style="text-align: center; font-weight: bold; background-color: var(--border-color);">439            用户信息440          </td>441        </tr>442        <tr>443          <td>邮箱:</td>444          <td id="modalEmail"></td>445        </tr>446        <tr>447          <td>用户名:</td>448          <td id="modalName"></td>449        </tr>450        <tr>451          <td>用户ID:</td>452          <td id="modalId"></td>453        </tr>454        <tr>455          <td>更新时间:</td>456          <td id="modalUpdatedAt"></td>457        </tr>458        <tr>459          <td colspan="2" style="text-align: center; font-weight: bold; background-color: var(--border-color);">460            会员信息461          </td>462        </tr>463        <tr>464          <td>会员类型:</td>465          <td id="modalMemberType"></td>466        </tr>467        <tr>468          <td>支付ID:</td>469          <td id="modalPaymentId"></td>470        </tr>471        <tr>472          <td>试用剩余:</td>473          <td id="modalTrialDays"></td>474        </tr>475        <tr>476          <td colspan="2" style="text-align: center; font-weight: bold; background-color: var(--border-color);">477            使用量统计 (最近30天)478          </td>479        </tr>480        <tr>481          <td>Premium models:</td>482          <td id="modalPremiumUsage"></td>483        </tr>484        <tr>485          <td>Standard models:</td>486          <td id="modalStandardUsage"></td>487        </tr>488        <tr>489          <td>Unknown models:</td>490          <td id="modalUnknownUsage"></td>491        </tr>492      </table>493      <div id="usageProgressContainer"></div>494    </div>495  </div>496 497  <div id="promptModal" class="modal">498    <div class="modal-content">499      <div class="modal-header">500        <h3>对话内容</h3>501        <span class="close">&times;</span>502      </div>503      <div id="promptContent"></div>504    </div>505  </div>506 507  <script>508    let refreshInterval;509 510    function updateStats(data) {511      document.getElementById('totalRequests').textContent = data.total || 0;512      document.getElementById('activeRequests').textContent = data.active || 0;513      if (data.error) {514        document.getElementById('errorRequests').textContent = data.error;515        document.getElementById('errorRequests').parentElement.style.display = '';516      } else {517        document.getElementById('errorRequests').parentElement.style.display = 'none';518      }519      document.getElementById('lastUpdate').textContent =520        new Date(data.timestamp).toLocaleTimeString();521    }522 523    function getProgressBarClass(percentage) {524      if (percentage <= 60) return 'low';525      if (percentage <= 85) return 'medium';526      return 'high';527    }528 529    function formatMembershipType(type) {530      if (!type) return '-';531 532      switch (type) {533        case 'free_trial': return 'Pro Trial';534        case 'pro': return 'Pro';535        case 'free': return 'Free';536        case 'enterprise': return 'Business';537        default: return type538          .split('_')539          .map(word => word.charAt(0).toUpperCase() + word.slice(1))540          .join(' ');541      }542    }543 544    function showTokenModal(tokenInfo) {545      const modal = document.getElementById('tokenModal');546      document.getElementById('modalToken').textContent = tokenInfo.token || '-';547      document.getElementById('modalChecksum').textContent = tokenInfo.checksum || '-';548 549      if (tokenInfo.profile) {550        const { user, stripe, usage } = tokenInfo.profile;551 552        // 设置用户信息553        document.getElementById('modalEmail').textContent = user.email || '-';554        document.getElementById('modalName').textContent = user.name || '-';555        document.getElementById('modalId').textContent = user.id || '-';556        document.getElementById('modalUpdatedAt').textContent = user.updated_at ? new Date(user.updated_at).toLocaleString() : '-';557 558        // 设置会员信息559        document.getElementById('modalMemberType').textContent =560          formatMembershipType(stripe.membership_type);561        document.getElementById('modalPaymentId').textContent = stripe.payment_id || '-';562        document.getElementById('modalTrialDays').textContent =563          stripe.days_remaining_on_trial > 0 ? `${stripe.days_remaining_on_trial}天` : '-';564 565        // 处理使用量信息566        const container = document.getElementById('usageProgressContainer');567        container.innerHTML = '';568 569        const models = {570          'modalPremiumUsage': usage.premium,571          'modalStandardUsage': usage.standard,572          'modalUnknownUsage': usage.unknown573        };574 575        Object.entries(models).forEach(([elementId, modelData]) => {576          const element = document.getElementById(elementId);577          if (modelData) {578            const { requests, tokens, max_requests } = modelData;579 580            if (max_requests) {581              const percentage = (requests / max_requests * 100).toFixed(1);582              element.textContent = `${requests}/${max_requests} requests (${percentage}%), ${tokens} tokens`;583 584              const progressDiv = document.createElement('div');585              progressDiv.className = 'usage-progress-container';586              const colorClass = getProgressBarClass(parseFloat(percentage));587              progressDiv.innerHTML = `<div class="usage-progress-bar ${colorClass}" style="width: ${percentage}%"></div>`;588              container.appendChild(progressDiv);589            } else {590              element.textContent = `${requests} requests, ${tokens} tokens`;591            }592          } else {593            element.textContent = '-';594          }595        });596      } else {597        // 如果没有 profile 信息,清空所有字段598        [599          'modalEmail',600          'modalName',601          'modalId',602          'modalUpdatedAt',603          'modalMemberType',604          'modalPaymentId',605          'modalTrialDays',606          'modalPremiumUsage',607          'modalStandardUsage',608          'modalUnknownUsage'609        ].forEach(id => document.getElementById(id).textContent = '-');610        document.getElementById('usageProgressContainer').innerHTML = '';611      }612 613      modal.style.display = 'block';614    }615 616    function formatSimpleTokenInfo(tokenInfo) {617      if (!tokenInfo.profile) return '无用户信息';618 619      const { user, stripe, usage } = tokenInfo.profile;620      const premiumUsage = usage.premium ?621        `${usage.premium.requests}/${usage.premium.max_requests}` : '-';622 623      const rows = [624        ['邮箱', user.email || '-'],625        ...(user.name ? [['用户名', user.name]] : []),626        ['会员', formatMembershipType(stripe.membership_type)],627        ['Premium', premiumUsage]628      ];629 630      return rows.map(([label, value]) => `<div class="tooltip-info-row"><span class="label">${label}:</span><span class="value">${value}</span></div>`).join('');631    }632 633    function updateTable(data) {634      const tbody = document.getElementById('logsBody');635      updateStats(data);636 637      tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ?`<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` :'-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');638    }639 640    function formatTiming(total, first) {641      const formattedTotal = total.toFixed(2);642      const formattedFirst = first !== null && first !== undefined ? `${first.toFixed(2)}s` : '-';643      return `${formattedTotal}s / ${formattedFirst}`;644    }645 646    function formatPromptPreview(promptStr) {647      try {648        const messages = parsePrompt(promptStr);649        if (!messages || messages.length === 0) {650          return '无对话内容';651        }652 653        // 获取最后一条消息654        const lastMessage = messages[messages.length - 1];655        const roleLabels = {656          'system': '系统',657          'user': '用户',658          'assistant': '助手'659        };660 661        return `<div class="message-meta">最后一条消息 (${roleLabels[lastMessage.role] || lastMessage.role}):</div><div class="last-message">${lastMessage.content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>')}</div>`;662      } catch (e) {663        console.error('预览对话内容失败:', e);664        return '无法解析对话内容';665      }666    }667 668    async function fetchLogs() {669      const data = await makeAuthenticatedRequest('/logs');670      if (data) {671        updateTable(data);672        showGlobalMessage('日志获取成功');673      }674    }675 676    // 自动刷新控制677    document.getElementById('autoRefresh').addEventListener('change', function (e) {678      if (e.target.checked) {679        refreshInterval = setInterval(fetchLogs, 60000);680      } else {681        clearInterval(refreshInterval);682      }683    });684 685    // 页面加载完成后自动获取日志686    document.addEventListener('DOMContentLoaded', () => {687      const authToken = getAuthToken();688      if (authToken) {689        document.getElementById('authToken').value = authToken;690        fetchLogs();691      }692      // 启动自动刷新693      refreshInterval = setInterval(fetchLogs, 60000);694    });695 696    // 初始化 token 处理697    initializeTokenHandling('authToken');698 699    // 添加清理逻辑700    window.addEventListener('beforeunload', () => {701      if (refreshInterval) {702        clearInterval(refreshInterval);703      }704    });705 706    // 添加模态框关闭逻辑707    document.querySelectorAll('.modal .close').forEach(closeBtn => {708      closeBtn.onclick = function () {709        this.closest('.modal').style.display = 'none';710      }711    });712 713    window.onclick = function (event) {714      if (event.target.classList.contains('modal')) {715        event.target.style.display = 'none';716      }717    }718  </script>719</body>720 721</html>