File size: 16,437 Bytes
2986042
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" type="image/x-icon" href="/web/favicon.ico">
  <link rel="apple-touch-icon" href="/web/apple-touch-icon.png">
  <link rel="manifest" href="/web/manifest.json">
  <meta name="theme-color" content="#3b82f6">
  <title data-i18n="modelTest.title">模型测试 - Claude Code & Codex Proxy</title>
  <link rel="stylesheet" href="/web/assets/css/styles.css?v=__VERSION__">
  <link rel="stylesheet" href="/web/assets/css/channels.css?v=__VERSION__">
  <script defer src="/web/assets/locales/zh-CN.js?v=__VERSION__"></script>
  <script defer src="/web/assets/locales/en.js?v=__VERSION__"></script>
  <script defer src="/web/assets/js/i18n.js?v=__VERSION__"></script>
  <script defer src="/web/assets/js/template-engine.js?v=__VERSION__"></script>
  <script defer src="/web/assets/js/ui.js?v=__VERSION__"></script>
  <script defer src="/web/assets/js/model-test.js?v=__VERSION__"></script>
  <script defer src="/web/assets/js/logs-channel-editor.js?v=__VERSION__"></script>
</head>
<body>
  <div class="app-container">
    <main class="main-content">
      <div class="content-area">
        <section class="glass-card mt-2 mb-2 overflow-visible">
          <!-- 模式切换 -->
          <div class="model-test-tabs">
            <button id="modeTabChannel" type="button" class="mode-tab-btn active" data-action="set-test-mode" data-mode="channel" data-i18n="modelTest.mode.channel">按渠道测试</button>
            <button id="modeTabModel" type="button" class="mode-tab-btn" data-action="set-test-mode" data-mode="model" data-i18n="modelTest.mode.model">按模型测试</button>
          </div>

          <!-- 控制栏 -->
          <div class="model-test-toolbar">
            <div class="model-test-toolbar-section model-test-toolbar-section--filters">
              <label id="channelSelectorLabel" class="model-test-control">
                <span class="model-test-control__label" data-i18n="modelTest.channel">渠道</span>
                <div id="testChannelSelectContainer"></div>
              </label>
              <label id="modelTypeLabel" class="model-test-control model-test-control--type hidden">
                <span class="model-test-control__label" data-i18n="common.type">类型</span>
                <select id="testModelType" class="filter-select model-test-inline-select" aria-label="类型"></select>
              </label>
              <label id="modelSelectorLabel" class="model-test-control model-test-control--model hidden">
                <span class="model-test-control__label" data-i18n="common.model">模型</span>
                <div class="filter-combobox-wrapper model-test-model-combobox">
                  <input
                    id="testModelSelect"
                    class="filter-select filter-combobox"
                    type="text"
                    autocomplete="off"
                    spellcheck="false"
                    data-i18n-placeholder="modelTest.modelInputPlaceholder"
                    placeholder="输入或选择模型"
                  >
                  <div id="testModelSelectDropdown" class="filter-dropdown" role="listbox" aria-label="模型"></div>
                </div>
              </label>
              <div id="protocolTransformContainer" class="model-test-control model-test-control--protocol">
                <span class="model-test-control__label" data-i18n="modelTest.protocolTransform">协议转换</span>
                <div id="protocolTransformOptions" class="channel-editor-radio-group" role="radiogroup" aria-label="协议转换"></div>
              </div>
              <div class="model-test-toolbar-toggles">
                <label class="model-test-toggle">
                  <input type="checkbox" id="streamEnabled" class="control-checkbox control-checkbox--sm">
                  <span data-i18n="modelTest.stream">流式</span>
                </label>
                <label class="model-test-toggle">
                  <span data-i18n="modelTest.concurrency">并发</span>
                  <input type="number" id="concurrency" value="5" min="1" max="20" class="model-test-concurrency-input">
                </label>
              </div>
              <label class="model-test-control model-test-control--content">
                <span class="model-test-control__label" data-i18n="modelTest.content">内容</span>
                <input type="text" id="modelTestContent" value="" class="model-test-inline-input" data-i18n-placeholder="common.loading" placeholder="加载中...">
              </label>
            </div>
            <div class="model-test-toolbar-section model-test-toolbar-section--meta">
              <label class="model-test-control model-test-control--name-filter">
                <span class="model-test-control__label" data-i18n="common.search">搜索</span>
                <input type="text" id="modelTestMobileNameFilter" value="" class="model-test-inline-input" autocomplete="off" spellcheck="false" placeholder="搜索模型名称...">
              </label>
              <span id="testProgress" class="model-test-progress"></span>
            </div>
          </div>

          <!-- 模型测试表格 -->
          <div class="table-container model-test-table-container mobile-card-table-container">
            <table class="modern-table model-test-table mobile-card-table">
              <thead class="table-head-sticky">
                <tr id="model-test-head-row">
                  <th class="table-col-select mobile-card-select-header"><input type="checkbox" id="selectAllCheckbox" data-change-action="toggle-all-models"></th>
                  <th class="table-col-name" data-i18n="common.model" data-sort-key="name">模型</th>
                  <th class="first-byte-col table-col-duration" data-i18n="modelTest.firstByteDuration" data-sort-key="firstByteDuration">首字</th>
                  <th class="table-col-duration" data-i18n="modelTest.totalDuration" data-sort-key="duration">总耗时</th>
                  <th class="table-col-metric" data-i18n="common.input" data-sort-key="inputTokens">输入</th>
                  <th class="table-col-metric" data-i18n="common.output" data-sort-key="outputTokens">输出</th>
                  <th class="table-col-speed" data-i18n="modelTest.speed" data-sort-key="speed">速度(tok/s)</th>
                  <th class="table-col-metric" data-i18n="modelTest.cacheRead" data-sort-key="cacheRead">缓读</th>
                  <th class="table-col-metric" data-i18n="modelTest.cacheCreate" data-sort-key="cacheCreate">缓建</th>
                  <th class="table-col-cost" data-i18n="common.cost" data-sort-key="cost">费用</th>
                  <th class="table-col-response model-test-response-head" data-sort-key="response">
                    <div class="model-test-response-head-inner">
                      <div class="model-test-response-head-line">
                        <span class="model-test-response-head-label" data-i18n="modelTest.responseContent">响应内容</span>
                      </div>
                      <div class="model-test-toolbar-section model-test-toolbar-section--actions model-test-head-actions">
                        <button id="fetchModelsBtn" type="button" data-action="fetch-and-add-models" class="btn btn-secondary model-test-toolbar-btn" data-i18n="modelTest.fetchModels">获取模型</button>
                        <button id="addModelsBtn" type="button" data-action="open-add-models-modal" class="btn btn-secondary model-test-toolbar-btn hidden" data-i18n="modelTest.addModels">添加模型</button>
                        <button id="deleteModelsBtn" type="button" data-action="delete-selected-models" class="btn btn-secondary model-test-toolbar-btn model-test-toolbar-btn--danger" data-i18n="modelTest.deleteModels">删除模型</button>
                        <button id="runTestBtn" type="button" data-action="run-model-tests" class="btn btn-primary model-test-toolbar-btn" data-i18n="modelTest.startTest">开始测试</button>
                      </div>
                    </div>
                  </th>
                </tr>
              </thead>
              <tbody id="model-test-tbody">
                <tr class="model-test-empty-row"><td colspan="11" data-i18n="modelTest.selectChannelFirst">请先选择渠道</td></tr>
              </tbody>
            </table>
          </div>
        </section>
      </div>
    </main>
  </div>
  <!-- 模型行模板 -->
  <template id="tpl-model-row">
    <tr class="mobile-card-row model-test-row" data-model="{{model}}" data-channel-id="{{channelId}}" data-cost-multiplier="{{costMultiplier}}">
      <td class="model-test-col-select mobile-card-no-label" data-mobile-label="{{mobileLabelSelect}}"><input type="checkbox" class="row-checkbox model-checkbox" checked></td>
      <td class="model-test-col-name truncate-cell" title="{{model}}" data-mobile-label="{{mobileLabelName}}">
        <button type="button" class="channel-link" data-channel-id="{{channelId}}" title="{{model}}">{{displayName}}</button>
      </td>
      <td class="model-test-col-first-byte first-byte-duration" data-mobile-label="{{mobileLabelFirstByte}}">-</td>
      <td class="model-test-col-duration duration" data-mobile-label="{{mobileLabelDuration}}">-</td>
      <td class="model-test-col-input input-tokens" data-mobile-label="{{mobileLabelInput}}">-</td>
      <td class="model-test-col-output output-tokens" data-mobile-label="{{mobileLabelOutput}}">-</td>
      <td class="model-test-col-speed speed" data-mobile-label="{{mobileLabelSpeed}}">-</td>
      <td class="model-test-col-cache-read cache-read" data-mobile-label="{{mobileLabelCacheRead}}">-</td>
      <td class="model-test-col-cache-create cache-create" data-mobile-label="{{mobileLabelCacheCreate}}">-</td>
      <td class="model-test-col-cost cost" data-mobile-label="{{mobileLabelCost}}">-</td>
      <td class="model-test-col-response response" title="" data-mobile-label="{{mobileLabelResponse}}">-</td>
    </tr>
  </template>

  <!-- 按模型测试时的渠道行模板 -->
  <template id="tpl-channel-row-by-model">
    <tr class="mobile-card-row model-test-row" data-channel-id="{{channelId}}" data-model="{{model}}" data-cost-multiplier="{{costMultiplier}}">
      <td class="model-test-col-select mobile-card-no-label" data-mobile-label="{{mobileLabelSelect}}"><input type="checkbox" class="row-checkbox channel-checkbox" checked></td>
      <td class="model-test-col-name truncate-cell" data-mobile-label="{{mobileLabelName}}">
        <button type="button" class="channel-link" data-channel-id="{{channelId}}" title="{{channelName}}">{{channelName}}</button>
      </td>
      <td class="model-test-col-priority channel-priority" data-mobile-label="{{mobileLabelPriority}}">{{channelPriority}}</td>
      <td class="model-test-col-first-byte first-byte-duration" data-mobile-label="{{mobileLabelFirstByte}}">-</td>
      <td class="model-test-col-duration duration" data-mobile-label="{{mobileLabelDuration}}">-</td>
      <td class="model-test-col-input input-tokens" data-mobile-label="{{mobileLabelInput}}">-</td>
      <td class="model-test-col-output output-tokens" data-mobile-label="{{mobileLabelOutput}}">-</td>
      <td class="model-test-col-speed speed" data-mobile-label="{{mobileLabelSpeed}}">-</td>
      <td class="model-test-col-cache-read cache-read" data-mobile-label="{{mobileLabelCacheRead}}">-</td>
      <td class="model-test-col-cache-create cache-create" data-mobile-label="{{mobileLabelCacheCreate}}">-</td>
      <td class="model-test-col-cost cost" data-mobile-label="{{mobileLabelCost}}">-</td>
      <td class="model-test-col-response response" title="" data-mobile-label="{{mobileLabelResponse}}">-</td>
    </tr>
  </template>

  <!-- 空状态模板 -->
  <template id="tpl-empty-row">
    <tr class="model-test-empty-row"><td colspan="{{colspan}}">{{message}}</td></tr>
  </template>

  <!-- 上游请求/响应详情弹窗 -->
  <div id="upstreamDetailModal" class="modal">
    <div class="modal-content upstream-detail-modal-content">
      <div class="modal-header">
        <h2 class="modal-title" data-i18n="channels.test.upstreamDetail">上游请求/响应详情</h2>
        <button type="button" class="close-btn" onclick="closeUpstreamDetailModal()">&times;</button>
      </div>
      <div class="upstream-detail-tabs">
        <button type="button" class="upstream-tab active" data-tab="request" data-i18n="channels.test.tabRequest">Request</button>
        <button type="button" class="upstream-tab" data-tab="response" data-i18n="channels.test.tabResponse">Response</button>
        <button type="button" class="upstream-copy-btn upstream-copy-btn--tabs" data-copy-target="upstreamReqRaw" data-i18n="common.copy">复制</button>
      </div>
      <div id="upstreamTabRequest" class="upstream-tab-panel active">
        <div class="upstream-field upstream-field--full">
          <pre id="upstreamReqRaw" class="upstream-pre upstream-pre--full"></pre>
        </div>
      </div>
      <div id="upstreamTabResponse" class="upstream-tab-panel">
        <div class="upstream-field upstream-field--full">
          <pre id="upstreamRespRaw" class="upstream-pre upstream-pre--full"></pre>
        </div>
      </div>
    </div>
  </div>

  <!-- 批量添加模型弹窗 -->
  <div id="addModelsModal" class="modal">
    <div class="modal-content modal-content--lg">
      <div class="modal-header modal-header--compact">
        <h2 class="modal-title" data-i18n="modelTest.addModelsTitle">批量添加模型</h2>
        <button id="addModelsCloseBtn" class="close-btn" aria-label="Close">&times;</button>
      </div>
      <label class="model-test-add-field">
        <span class="model-test-add-label" data-i18n="modelTest.addModelsInputLabel">输入模型名称(支持逗号或换行分隔)</span>
        <textarea
          id="addModelsTextarea"
          class="model-test-batch-textarea"
          spellcheck="false"
          data-i18n-placeholder="modelTest.addModelsPlaceholder"
          placeholder="gpt-4o,gpt-4o-mini&#10;claude-3-5-sonnet-20241022&#10;claude-3-5-haiku-latest"
        ></textarea>
      </label>
      <div class="model-test-add-help">
        <div class="model-test-add-help-title">
          <span class="model-test-add-help-icon" aria-hidden="true">!</span>
          <strong data-i18n="modelTest.addModelsHelpTitle">使用说明:</strong>
        </div>
        <ul>
          <li><span data-i18n="modelTest.addModelsHelpComma">支持逗号分隔:</span> <code>model1,model2,model3</code></li>
          <li data-i18n="modelTest.addModelsHelpLine">支持换行分隔:每行一个模型</li>
          <li data-i18n="modelTest.addModelsHelpDedupe">自动去除空格、空行和重复模型</li>
        </ul>
      </div>
      <div class="confirm-actions confirm-actions--end">
        <button id="addModelsCancelBtn" class="btn btn-secondary" data-i18n="common.cancel">取消</button>
        <button id="addModelsConfirmBtn" class="btn btn-primary" data-i18n="modelTest.addModelsConfirm">确认添加</button>
      </div>
    </div>
  </div>

  <!-- 删除预览确认弹窗 -->
  <div id="deletePreviewModal" class="modal">
    <div class="modal-content modal-content--lg">
      <div class="modal-header modal-header--compact">
        <h2 class="modal-title" data-i18n="modelTest.deletePreviewTitle">确认删除模型</h2>
        <button id="deletePreviewCloseBtn" class="close-btn" aria-label="Close">&times;</button>
      </div>
      <p class="modal-description" data-i18n="modelTest.deletePreviewDesc">将按以下分组删除,请确认:</p>
      <pre id="deletePreviewContent" class="delete-preview-text">-</pre>
      <p id="deletePreviewProgress" class="model-test-delete-preview-progress hidden">-</p>
      <pre id="deletePreviewRuntimeLog" class="delete-preview-text model-test-delete-preview-log hidden">-</pre>
      <div class="confirm-actions confirm-actions--end">
        <button id="deletePreviewCancelBtn" class="btn btn-secondary" data-i18n="common.cancel">取消</button>
        <button id="deletePreviewConfirmBtn" class="btn btn-danger" data-i18n="modelTest.deletePreviewConfirm">确认删除</button>
      </div>
    </div>
  </div>

</body>
</html>