ec-web 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,427 +1,427 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN" data-theme="light">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>EC Web | EvolCore</title>
7
- <link rel="icon" href="data:,">
8
- <script>
9
- (function () {
10
- var pref = localStorage.getItem('ecTheme') || 'light';
11
- if (pref === 'auto') pref = 'system';
12
- if (pref !== 'light' && pref !== 'dark' && pref !== 'system') pref = 'light';
13
- var theme = pref === 'system' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : (pref === 'dark' ? 'dark' : 'light');
14
- document.documentElement.setAttribute('data-theme', theme);
15
- document.documentElement.setAttribute('data-theme-pref', pref);
16
- })();
17
- </script>
18
- <link rel="stylesheet" href="style.css">
19
- <script defer src="vendor/echarts-5.6.0.min.js"></script>
20
- <script defer src="app.js"></script>
21
- <script defer src="basic-settings.js"></script>
22
- </head>
23
- <body>
24
- <!-- 配对页 -->
25
- <div id="pair-page" class="pair-page">
26
- <div class="pair-box">
27
- <h1 data-i18n="pair.title">EC Web</h1>
28
- <p class="pair-hint" data-i18n="pair.hint">输入终端显示的 6 位配对码</p>
29
- <input id="pair-input" type="text" inputmode="numeric" maxlength="6" placeholder="000000" data-i18n="pair.placeholder" autocomplete="off">
30
- <button id="pair-btn" data-i18n="action.pair">配对</button>
31
- <div id="pair-error" class="pair-error"></div>
32
- </div>
33
- </div>
34
-
35
- <!-- 主面板 -->
36
- <div id="app" class="app" style="display:none">
37
- <header class="topbar">
38
- <span class="brand">EC Web</span>
39
- <nav class="tabs">
40
- <button class="tab active" data-view="agents" data-i18n="tab.agents">Agents</button>
41
- <button class="tab" data-view="msg" data-i18n="tab.messages">Messages</button>
42
- <button class="tab" data-view="session" data-i18n="tab.sessions">Sessions</button>
43
- <button class="tab" data-view="triggers" data-i18n="tab.triggers">Triggers</button>
44
- <button class="tab" data-view="cache" data-i18n="tab.cache">Cache</button>
45
- <button class="tab" data-view="system" data-i18n="tab.system">System</button>
46
- <button class="tab" data-view="logs" data-i18n="tab.logs">Logs</button>
47
- <button class="tab" data-view="usage" data-i18n="tab.usage">Usage</button>
48
- <button class="tab" data-view="roleManagement" data-i18n="tab.roleManagement">Roles</button>
49
-
50
- </nav>
51
- <span style="flex:1"></span>
52
- <span class="build-ts" style="font-size:10px;color:var(--dim)" data-i18n-title="common.buildTime" title="构建时间">2026-09-15 06:49</span>
53
- <span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
54
- <button id="lang-btn" class="theme-btn" data-i18n-title="language.buttonTitle" title="切换语言">🌐</button>
55
- <button id="theme-btn" class="theme-btn" data-i18n-title="theme.buttonTitle" title="主题">🌗</button>
56
- <button id="basic-settings-btn" class="theme-btn basic-settings-launch" data-i18n-title="basicSettings.openTitle" title="打开基础设置" aria-label="打开基础设置" aria-haspopup="dialog"><span aria-hidden="true">⚙️</span></button>
57
- <button id="logout-btn" class="logout-btn" data-i18n="action.logout" data-i18n-title="pair.logoutTitle" title="退出配对">退出</button>
58
- </header>
59
-
60
- <main class="content">
61
- <section id="view-agents" class="view active"></section>
62
- <section id="view-msg" class="view">
63
- <div class="msg-layout">
64
- <div id="msg-aids" class="msg-col msg-aids"></div>
65
- <div id="msg-peers" class="msg-col msg-peers"></div>
66
- <div id="msg-stream" class="msg-col msg-stream"></div>
67
- </div>
68
- </section>
69
- <section id="view-session" class="view">
70
- <div class="sess-layout">
71
- <div id="sess-list" class="sess-col sess-list"></div>
72
- <div id="sess-detail" class="sess-col sess-detail"></div>
73
- </div>
74
- </section>
75
- <section id="view-triggers" class="view">
76
- <div class="trig-layout">
77
- <div id="trig-agents" class="msg-col trig-agents-col"></div>
78
- <div id="trig-table" class="trig-table-col"></div>
79
- </div>
80
- </section>
81
- <section id="view-cache" class="view"></section>
82
- <section id="view-system" class="view"></section>
83
- <section id="view-logs" class="view">
84
- <div class="log-layout">
85
- <div class="log-toolbar">
86
- <label class="log-control">
87
- <span class="visually-hidden" data-i18n="logs.source">日志源</span>
88
- <select id="log-source"></select>
89
- </label>
90
- <label class="log-control">
91
- <span class="visually-hidden" data-i18n="logs.level">级别</span>
92
- <select id="log-level">
93
- <option value="all" data-i18n="logs.level.all">全部级别</option>
94
- <option value="error">Error</option>
95
- <option value="warn">Warn</option>
96
- <option value="info">Info</option>
97
- <option value="debug">Debug</option>
98
- <option value="other" data-i18n="logs.level.other">其他</option>
99
- </select>
100
- </label>
101
- <label class="log-control">
102
- <span class="visually-hidden" data-i18n="logs.module">模块</span>
103
- <select id="log-module">
104
- <option value="all" data-i18n="logs.module.all">全部模块</option>
105
- </select>
106
- </label>
107
- <label class="log-search">
108
- <span class="visually-hidden" data-i18n="logs.search">搜索</span>
109
- <input id="log-search" type="search" data-i18n="logs.searchPlaceholder" placeholder="筛选当前日志">
110
- </label>
111
- <label class="log-control log-limit-control">
112
- <span class="visually-hidden" data-i18n="logs.lines">行数</span>
113
- <select id="log-limit">
114
- <option value="500" data-i18n="logs.lines.500" selected>500 行</option>
115
- <option value="1000" data-i18n="logs.lines.1000">1000 行</option>
116
- <option value="2000" data-i18n="logs.lines.2000">2000 行</option>
117
- </select>
118
- </label>
119
- <label class="log-follow">
120
- <input id="log-follow" type="checkbox" checked>
121
- <span data-i18n="logs.follow">跟随尾部</span>
122
- </label>
123
- </div>
124
- <div class="log-stream" id="log-stream"><div class="empty" data-i18n="common.loading">加载中…</div></div>
125
- <div class="log-meta">
126
- <span id="log-meta-info"></span>
127
- <span class="log-meta-status">
128
- <span id="log-filter-result" hidden></span>
129
- <span class="log-live"><i></i><span data-i18n="logs.live">自动刷新</span></span>
130
- </span>
131
- </div>
132
- </div>
133
- </section>
134
- <section id="view-gateway" class="view"></section>
135
- <section id="view-usage" class="view">
136
- <div class="usage-layout">
137
- <!-- 子 Tab:Overview / Explorer -->
138
- <div class="usage-subtabs">
139
- <button class="usage-subtab active" data-subview="overview" data-i18n="usage.subtab.overview">Overview</button>
140
- <button class="usage-subtab" data-subview="explorer" data-i18n="usage.subtab.explorer">Explorer</button>
141
- </div>
142
-
143
- <!-- Overview 面板 -->
144
- <div id="usage-overview" class="usage-subpanel active">
145
- <div class="overview-filters">
146
- <div class="ov-range-tabs">
147
- <button class="ov-range-btn active" data-range="last24" data-i18n="usage.overview.range.last24">Last 24 Hours</button>
148
- <button class="ov-range-btn" data-range="week" data-i18n="usage.overview.range.week">This Week</button>
149
- <button class="ov-range-btn" data-range="lastWeek" data-i18n="usage.overview.range.lastWeek">Last Week</button>
150
- <button class="ov-range-btn" data-range="month" data-i18n="usage.overview.range.month">This Month</button>
151
- <button class="ov-range-btn" data-range="last30" data-i18n="usage.overview.range.last30">Last 30 Days</button>
152
- <button class="ov-range-btn" data-range="custom" data-i18n="usage.overview.range.custom">Custom</button>
153
- </div>
154
- <div class="ov-custom-date" id="ov-custom-date" style="display:none;">
155
- <label><span data-i18n="usage.explorer.filter.from">From</span> <input type="datetime-local" id="ov-from"></label>
156
- <label><span data-i18n="usage.explorer.filter.to">To</span> <input type="datetime-local" id="ov-to"></label>
157
- <button id="ov-query-btn" data-i18n="action.query">Query</button>
158
- </div>
159
- </div>
160
- <div id="ov-cards" class="usage-cards"></div>
161
- <div class="usage-table-section" style="margin-top:4px">
162
- <h3 id="ov-table-title" data-i18n="usage.overview.title">Summary by Agent</h3>
163
- <table id="ov-agent-table" class="usage-table"></table>
164
- </div>
165
-
166
- <!-- 模型访问明细查询 -->
167
- <div class="usage-table-section" style="margin-top:16px">
168
- <h3 data-i18n="usage.detail.title">Model Access Details</h3>
169
- <div class="detail-filters">
170
- <label><span data-i18n="usage.detail.agent">Agent</span>
171
- <select id="detail-agent">
172
- <option value="" data-i18n="usage.explorer.all">All</option>
173
- </select>
174
- </label>
175
- <label><span data-i18n="usage.detail.model">Model</span>
176
- <select id="detail-model">
177
- <option value="" data-i18n="usage.explorer.all">All</option>
178
- </select>
179
- </label>
180
- <label><span data-i18n="usage.detail.pageSize">Per page</span>
181
- <select id="detail-page-size">
182
- <option value="20">20</option>
183
- <option value="50" selected>50</option>
184
- <option value="100">100</option>
185
- <option value="200">200</option>
186
- </select>
187
- </label>
188
- </div>
189
- <div id="detail-result" style="margin-top:12px;">
190
- <table id="detail-table" class="usage-table"></table>
191
- </div>
192
- <div class="detail-pagination">
193
- <button id="detail-prev-page" data-i18n="usage.detail.prevPage">Previous</button>
194
- <input type="number" id="detail-page" value="1" min="1" style="width:60px;text-align:center">
195
- <button id="detail-next-page" data-i18n="usage.detail.nextPage">Next</button>
196
- <span id="detail-pagination-info" style="margin-left:12px;color:var(--dim);font-size:12px"></span>
197
- </div>
198
- </div>
199
- </div>
200
-
201
- <!-- Explorer 面板 -->
202
- <div id="usage-explorer" class="usage-subpanel">
203
- <div class="exp-layout">
204
- <!-- 左侧列表 -->
205
- <div class="exp-sidebar">
206
- <div class="exp-sidebar-group">
207
- <div class="exp-sidebar-title" data-i18n="usage.explorer.sidebar.agents">Agents</div>
208
- <div id="exp-agent-list"></div>
209
- </div>
210
- <div class="exp-sidebar-group">
211
- <div class="exp-sidebar-title" data-i18n="usage.explorer.sidebar.peers">Peers</div>
212
- <div id="exp-peer-list"></div>
213
- </div>
214
- </div>
215
- <!-- 右侧详情 -->
216
- <div class="exp-detail">
217
- <div id="exp-detail-header" class="exp-detail-header">
218
- <span id="exp-selected-name" data-i18n="usage.explorer.selectHint">Select an Agent or Peer from the left</span>
219
- </div>
220
-
221
- <!-- 时间范围选择(与总览类似,但支持精确到时分秒) -->
222
- <div class="exp-time-filters">
223
- <div class="exp-range-tabs">
224
- <button class="exp-range-btn active" data-range="last24" data-i18n="usage.overview.range.last24">Last 24 Hours</button>
225
- <button class="exp-range-btn" data-range="week" data-i18n="usage.overview.range.week">This Week</button>
226
- <button class="exp-range-btn" data-range="lastWeek" data-i18n="usage.overview.range.lastWeek">Last Week</button>
227
- <button class="exp-range-btn" data-range="month" data-i18n="usage.overview.range.month">This Month</button>
228
- <button class="exp-range-btn" data-range="last30" data-i18n="usage.overview.range.last30">Last 30 Days</button>
229
- <button class="exp-range-btn" data-range="custom" data-i18n="usage.overview.range.custom">Custom</button>
230
- </div>
231
- <div class="exp-custom-date" id="exp-custom-date" style="display:none;">
232
- <label><span data-i18n="usage.explorer.filter.from">From</span> <input type="datetime-local" id="exp-from"></label>
233
- <label><span data-i18n="usage.explorer.filter.to">To</span> <input type="datetime-local" id="exp-to"></label>
234
- <button id="exp-time-query-btn" data-i18n="action.query">Query</button>
235
- </div>
236
- </div>
237
-
238
- <div id="exp-detail-cards" class="usage-cards"></div>
239
-
240
- <!-- 模型和粒度筛选 -->
241
- <div class="explorer-filters">
242
- <label>
243
- <span data-i18n="usage.explorer.filter.model">Model</span>
244
- <select id="exp-model">
245
- <option value="" data-i18n="usage.explorer.all">All</option>
246
- </select>
247
- </label>
248
- <label>
249
- <span data-i18n="usage.explorer.filter.granularity">Granularity</span>
250
- <select id="exp-granularity">
251
- <option value="hour" selected data-i18n="usage.explorer.filter.granularity.hour">Hour</option>
252
- <option value="day" data-i18n="usage.explorer.filter.granularity.day">Day</option>
253
- <option value="week" data-i18n="usage.explorer.filter.granularity.week">Week</option>
254
- <option value="month" data-i18n="usage.explorer.filter.granularity.month">Month</option>
255
- </select>
256
- </label>
257
- </div>
258
-
259
- <div id="usage-explorer-chart" class="usage-chart" style="height:260px"></div>
260
- <div class="usage-table-section" style="margin-top:12px">
261
- <h3 data-i18n="usage.explorer.results">Results</h3>
262
- <table id="usage-explorer-table" class="usage-table"></table>
263
- </div>
264
- </div>
265
- </div>
266
- </div>
267
- </div>
268
- </section>
269
-
270
- <!-- 角色管理页面 -->
271
- <section id="view-roleManagement" class="view">
272
- <div class="role-management-shell">
273
- <div class="role-management-toolbar">
274
- <nav class="role-subtabs ag-subtabs" role="tablist" aria-label="角色">
275
- <button class="role-subtab ag-subtab active" id="role-subtab-definitions" type="button" role="tab" aria-selected="true" aria-controls="role-panel-definitions" data-role-subview="definitions" data-i18n="roleManagement.definitions">角色定义</button>
276
- <button class="role-subtab ag-subtab" id="role-subtab-assignments" type="button" role="tab" aria-selected="false" aria-controls="role-panel-assignments" data-role-subview="assignments" data-i18n="roleManagement.assignments">角色分配</button>
277
- </nav>
278
- <button class="btn-new-role" id="btn-new-role">+ 新增角色</button>
279
- </div>
280
-
281
- <div class="role-subpanel active" id="role-panel-definitions" role="tabpanel" aria-labelledby="role-subtab-definitions" data-role-panel="definitions">
282
- <div class="role-defs-layout">
283
- <!-- 全局默认角色选择器 -->
284
- <div class="role-default-selector" id="role-default-selector">
285
- <label class="role-default-label">
286
- <span>默认角色 (Default Role):</span>
287
- <select id="default-private-role-select" class="form-select">
288
- <!-- 动态填充 -->
289
- </select>
290
- </label>
291
- <small style="color: var(--dim);">未显式分配角色的用户将使用此角色</small>
292
- </div>
293
-
294
- <div class="role-cards-grid" id="role-cards-grid">
295
- <!-- 动态渲染角色卡片 -->
296
- </div>
297
- </div>
298
-
299
- <!-- 角色编辑弹窗 -->
300
- <div id="role-edit-modal" class="modal" style="display:none">
301
- <div class="modal-content">
302
- <div class="modal-header">
303
- <h3 id="role-edit-title">编辑角色</h3>
304
- <button class="modal-close" id="role-edit-close">×</button>
305
- </div>
306
- <div class="modal-body" id="role-edit-body">
307
- <!-- 动态渲染表单 -->
308
- </div>
309
- <div class="modal-footer">
310
- <button class="btn-secondary" id="role-edit-cancel" data-i18n="roleDefs.cancel">取消</button>
311
- <button class="btn-primary" id="role-edit-save" data-i18n="roleDefs.save">保存</button>
312
- </div>
313
- </div>
314
- </div>
315
- </div>
316
-
317
- <div class="role-subpanel" id="role-panel-assignments" role="tabpanel" aria-labelledby="role-subtab-assignments" data-role-panel="assignments" hidden>
318
- <div class="role-assign-layout">
319
- <!-- Agent 选择器 + 搜索 + 筛选 -->
320
- <div class="assign-header">
321
- <label>
322
- <span data-i18n="roles.selectAgent">Select Agent:</span>
323
- <select id="roles-agent-select">
324
- <option value="" data-i18n="roles.selectAgentPlaceholder">-- Select an Agent --</option>
325
- </select>
326
- </label>
327
- <select id="filter-chattype">
328
- <option value="" data-i18n="roles.filter.allChatType">全部会话</option>
329
- <option value="private" data-i18n="roles.filter.private">💬 私聊</option>
330
- <option value="group" data-i18n="roles.filter.group">👥 群聊</option>
331
- </select>
332
- <select id="filter-verify">
333
- <option value="" data-i18n="roles.filter.allVerify">全部状态</option>
334
- <option value="verified" data-i18n="roles.filter.verified">✅ 已验证</option>
335
- <option value="invalid" data-i18n="roles.filter.invalid">⚠️ 验证失败</option>
336
- <option value="agentmd-unverified" data-i18n="roles.filter.unverified">⚠️ 未验证</option>
337
- <option value="unknown" data-i18n="roles.filter.unknown">❓ 未知</option>
338
- </select>
339
- <div class="search-box">
340
- <input type="search" id="peer-search" placeholder="Search peer key..." data-i18n="roles.searchPlaceholder">
341
- </div>
342
- </div>
343
-
344
- <!-- 对端关系列表 -->
345
- <div class="assign-peer-roles">
346
- <table id="relations-table" class="relations-table">
347
- <thead>
348
- <tr>
349
- <th data-i18n="roles.table.chatType">会话</th>
350
- <th data-i18n="roles.table.peerName">昵称</th>
351
- <th data-i18n="roles.table.peerAid">对端AID</th>
352
- <th data-i18n="roles.table.peerType">类型</th>
353
- <th data-i18n="roles.table.role">角色</th>
354
- <th data-i18n="roles.table.source">来源</th>
355
- <th data-i18n="roles.table.actions">操作</th>
356
- </tr>
357
- </thead>
358
- <tbody></tbody>
359
- </table>
360
- </div>
361
- </div>
362
-
363
- <!-- 对端角色编辑弹窗 -->
364
- <div id="peer-role-modal" class="modal" style="display:none">
365
- <div class="modal-content">
366
- <div class="modal-header">
367
- <h3 id="peer-role-title">Edit Peer Role</h3>
368
- <button class="modal-close" id="peer-role-close">×</button>
369
- </div>
370
- <div class="modal-body" id="peer-role-body">
371
- <!-- 动态渲染 -->
372
- </div>
373
- <div class="modal-footer">
374
- <button class="btn-secondary" id="peer-role-cancel" data-i18n="roleDefs.cancel">Cancel</button>
375
- <button class="btn-primary" id="peer-role-save" data-i18n="roleDefs.save">Save</button>
376
- </div>
377
- </div>
378
- </div>
379
- </div>
380
- </div>
381
- </section>
382
-
383
- </main>
384
- </div>
385
-
386
- <!-- 基础设置弹窗 -->
387
- <div id="basic-settings-modal" class="basic-settings-modal" hidden>
388
- <section class="basic-settings-dialog" role="dialog" aria-modal="true" aria-labelledby="basic-settings-title">
389
- <header class="basic-settings-header">
390
- <div>
391
- <h2 id="basic-settings-title" data-i18n="basicSettings.title">基础设置</h2>
392
- <p data-i18n="basicSettings.subtitle">按用途组织参数。保存前会校验并保留未编辑字段。</p>
393
- </div>
394
- <div class="basic-settings-header-actions">
395
- <button type="button" class="basic-settings-close" id="basic-settings-close" data-i18n-title="common.close" aria-label="关闭">×</button>
396
- </div>
397
- </header>
398
- <div class="basic-settings-layout">
399
- <nav class="basic-settings-tabs" aria-label="基础设置页签" role="tablist">
400
- <button type="button" class="basic-settings-tab active" role="tab" aria-selected="true" data-basic-tab="models"><span>01</span><b data-i18n="basicSettings.tabs.models">模型与推理</b><small data-i18n="basicSettings.tabs.modelsHint">基座、模型与连接</small></button>
401
- <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="response"><span>02</span><b data-i18n="basicSettings.tabs.response">响应与会话</b><small data-i18n="basicSettings.tabs.responseHint">响应方式与续接</small></button>
402
- <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="projects"><span>03</span><b data-i18n="basicSettings.tabs.projects">项目与服务</b><small data-i18n="basicSettings.tabs.projectsHint">路径与本机服务</small></button>
403
- <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="identity"><span>04</span><b data-i18n="basicSettings.tabs.identity">身份与安全</b><small data-i18n="basicSettings.tabs.identityHint">AID 与访问保护</small></button>
404
- <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="runtime"><span>05</span><b data-i18n="basicSettings.tabs.runtime">运行与诊断</b><small data-i18n="basicSettings.tabs.runtimeHint">日志与稳定性</small></button>
405
- </nav>
406
- <div class="basic-settings-scroll" id="basic-settings-content" aria-live="polite"></div>
407
- </div>
408
- <footer class="basic-settings-footer">
409
- <span id="basic-settings-status" class="basic-settings-status" data-i18n="basicSettings.noChanges">没有需要保存的改动</span>
410
- <span class="basic-settings-footer-spacer"></span>
411
- <button type="button" class="basic-settings-secondary-btn" id="basic-settings-json"><span aria-hidden="true">{ }</span><span data-i18n="basicSettings.viewJson">查看 JSON</span></button>
412
- <button type="button" class="basic-settings-secondary-btn" id="basic-settings-reload" data-i18n="basicSettings.reload">重新读取</button>
413
- <button type="button" class="basic-settings-primary-btn" id="basic-settings-save" data-i18n="basicSettings.save">保存设置</button>
414
- </footer>
415
- </section>
416
- </div>
417
-
418
- <div id="basic-settings-json-modal" class="basic-settings-json-modal" hidden>
419
- <section class="basic-settings-json-dialog" role="dialog" aria-modal="true" aria-labelledby="basic-settings-json-title">
420
- <header class="basic-settings-json-header"><div><h2 id="basic-settings-json-title" data-i18n="basicSettings.jsonTitle">查看 JSON</h2><p data-i18n="basicSettings.jsonSubtitle">只读预览,敏感值已脱敏。</p></div><button type="button" class="basic-settings-close" id="basic-settings-json-close" data-i18n-title="common.close" aria-label="关闭">×</button></header>
421
- <div class="basic-settings-json-tabs"><button type="button" class="active" data-basic-json-file="daemon" data-i18n="basicSettings.daemonFile">daemon.json</button><button type="button" data-basic-json-file="defaults" data-i18n="basicSettings.defaultsFile">agents/defaults.json</button></div>
422
- <pre id="basic-settings-json-view" class="basic-settings-json-view"></pre>
423
- </section>
424
- </div>
425
-
426
- </body>
427
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>EC Web | EvolCore</title>
7
+ <link rel="icon" href="data:,">
8
+ <script>
9
+ (function () {
10
+ var pref = localStorage.getItem('ecTheme') || 'light';
11
+ if (pref === 'auto') pref = 'system';
12
+ if (pref !== 'light' && pref !== 'dark' && pref !== 'system') pref = 'light';
13
+ var theme = pref === 'system' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : (pref === 'dark' ? 'dark' : 'light');
14
+ document.documentElement.setAttribute('data-theme', theme);
15
+ document.documentElement.setAttribute('data-theme-pref', pref);
16
+ })();
17
+ </script>
18
+ <link rel="stylesheet" href="style.css">
19
+ <script defer src="vendor/echarts-5.6.0.min.js"></script>
20
+ <script defer src="app.js"></script>
21
+ <script defer src="basic-settings.js"></script>
22
+ </head>
23
+ <body>
24
+ <!-- 配对页 -->
25
+ <div id="pair-page" class="pair-page">
26
+ <div class="pair-box">
27
+ <h1 data-i18n="pair.title">EC Web</h1>
28
+ <p class="pair-hint" data-i18n="pair.hint">输入终端显示的 6 位配对码</p>
29
+ <input id="pair-input" type="text" inputmode="numeric" maxlength="6" placeholder="000000" data-i18n="pair.placeholder" autocomplete="off">
30
+ <button id="pair-btn" data-i18n="action.pair">配对</button>
31
+ <div id="pair-error" class="pair-error"></div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- 主面板 -->
36
+ <div id="app" class="app" style="display:none">
37
+ <header class="topbar">
38
+ <span class="brand">EC Web</span>
39
+ <nav class="tabs">
40
+ <button class="tab active" data-view="agents" data-i18n="tab.agents">Agents</button>
41
+ <button class="tab" data-view="msg" data-i18n="tab.messages">Messages</button>
42
+ <button class="tab" data-view="session" data-i18n="tab.sessions">Sessions</button>
43
+ <button class="tab" data-view="triggers" data-i18n="tab.triggers">Triggers</button>
44
+ <button class="tab" data-view="cache" data-i18n="tab.cache">Cache</button>
45
+ <button class="tab" data-view="system" data-i18n="tab.system">System</button>
46
+ <button class="tab" data-view="logs" data-i18n="tab.logs">Logs</button>
47
+ <button class="tab" data-view="usage" data-i18n="tab.usage">Usage</button>
48
+ <button class="tab" data-view="roleManagement" data-i18n="tab.roleManagement">Roles</button>
49
+
50
+ </nav>
51
+ <span style="flex:1"></span>
52
+ <span class="build-ts" style="font-size:10px;color:var(--dim)" data-i18n-title="common.buildTime" title="构建时间">2026-09-24 11:34</span>
53
+ <span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
54
+ <button id="lang-btn" class="theme-btn" data-i18n-title="language.buttonTitle" title="切换语言">🌐</button>
55
+ <button id="theme-btn" class="theme-btn" data-i18n-title="theme.buttonTitle" title="主题">🌗</button>
56
+ <button id="basic-settings-btn" class="theme-btn basic-settings-launch" data-i18n-title="basicSettings.openTitle" title="打开基础设置" aria-label="打开基础设置" aria-haspopup="dialog"><span aria-hidden="true">⚙️</span></button>
57
+ <button id="logout-btn" class="logout-btn" data-i18n="action.logout" data-i18n-title="pair.logoutTitle" title="退出配对">退出</button>
58
+ </header>
59
+
60
+ <main class="content">
61
+ <section id="view-agents" class="view active"></section>
62
+ <section id="view-msg" class="view">
63
+ <div class="msg-layout">
64
+ <div id="msg-aids" class="msg-col msg-aids"></div>
65
+ <div id="msg-peers" class="msg-col msg-peers"></div>
66
+ <div id="msg-stream" class="msg-col msg-stream"></div>
67
+ </div>
68
+ </section>
69
+ <section id="view-session" class="view">
70
+ <div class="sess-layout">
71
+ <div id="sess-list" class="sess-col sess-list"></div>
72
+ <div id="sess-detail" class="sess-col sess-detail"></div>
73
+ </div>
74
+ </section>
75
+ <section id="view-triggers" class="view">
76
+ <div class="trig-layout">
77
+ <div id="trig-agents" class="msg-col trig-agents-col"></div>
78
+ <div id="trig-table" class="trig-table-col"></div>
79
+ </div>
80
+ </section>
81
+ <section id="view-cache" class="view"></section>
82
+ <section id="view-system" class="view"></section>
83
+ <section id="view-logs" class="view">
84
+ <div class="log-layout">
85
+ <div class="log-toolbar">
86
+ <label class="log-control">
87
+ <span class="visually-hidden" data-i18n="logs.source">日志源</span>
88
+ <select id="log-source"></select>
89
+ </label>
90
+ <label class="log-control">
91
+ <span class="visually-hidden" data-i18n="logs.level">级别</span>
92
+ <select id="log-level">
93
+ <option value="all" data-i18n="logs.level.all">全部级别</option>
94
+ <option value="error">Error</option>
95
+ <option value="warn">Warn</option>
96
+ <option value="info">Info</option>
97
+ <option value="debug">Debug</option>
98
+ <option value="other" data-i18n="logs.level.other">其他</option>
99
+ </select>
100
+ </label>
101
+ <label class="log-control">
102
+ <span class="visually-hidden" data-i18n="logs.module">模块</span>
103
+ <select id="log-module">
104
+ <option value="all" data-i18n="logs.module.all">全部模块</option>
105
+ </select>
106
+ </label>
107
+ <label class="log-search">
108
+ <span class="visually-hidden" data-i18n="logs.search">搜索</span>
109
+ <input id="log-search" type="search" data-i18n="logs.searchPlaceholder" placeholder="筛选当前日志">
110
+ </label>
111
+ <label class="log-control log-limit-control">
112
+ <span class="visually-hidden" data-i18n="logs.lines">行数</span>
113
+ <select id="log-limit">
114
+ <option value="500" data-i18n="logs.lines.500" selected>500 行</option>
115
+ <option value="1000" data-i18n="logs.lines.1000">1000 行</option>
116
+ <option value="2000" data-i18n="logs.lines.2000">2000 行</option>
117
+ </select>
118
+ </label>
119
+ <label class="log-follow">
120
+ <input id="log-follow" type="checkbox" checked>
121
+ <span data-i18n="logs.follow">跟随尾部</span>
122
+ </label>
123
+ </div>
124
+ <div class="log-stream" id="log-stream"><div class="empty" data-i18n="common.loading">加载中…</div></div>
125
+ <div class="log-meta">
126
+ <span id="log-meta-info"></span>
127
+ <span class="log-meta-status">
128
+ <span id="log-filter-result" hidden></span>
129
+ <span class="log-live"><i></i><span data-i18n="logs.live">自动刷新</span></span>
130
+ </span>
131
+ </div>
132
+ </div>
133
+ </section>
134
+ <section id="view-gateway" class="view"></section>
135
+ <section id="view-usage" class="view">
136
+ <div class="usage-layout">
137
+ <!-- 子 Tab:Overview / Explorer -->
138
+ <div class="usage-subtabs">
139
+ <button class="usage-subtab active" data-subview="overview" data-i18n="usage.subtab.overview">Overview</button>
140
+ <button class="usage-subtab" data-subview="explorer" data-i18n="usage.subtab.explorer">Explorer</button>
141
+ </div>
142
+
143
+ <!-- Overview 面板 -->
144
+ <div id="usage-overview" class="usage-subpanel active">
145
+ <div class="overview-filters">
146
+ <div class="ov-range-tabs">
147
+ <button class="ov-range-btn active" data-range="last24" data-i18n="usage.overview.range.last24">Last 24 Hours</button>
148
+ <button class="ov-range-btn" data-range="week" data-i18n="usage.overview.range.week">This Week</button>
149
+ <button class="ov-range-btn" data-range="lastWeek" data-i18n="usage.overview.range.lastWeek">Last Week</button>
150
+ <button class="ov-range-btn" data-range="month" data-i18n="usage.overview.range.month">This Month</button>
151
+ <button class="ov-range-btn" data-range="last30" data-i18n="usage.overview.range.last30">Last 30 Days</button>
152
+ <button class="ov-range-btn" data-range="custom" data-i18n="usage.overview.range.custom">Custom</button>
153
+ </div>
154
+ <div class="ov-custom-date" id="ov-custom-date" style="display:none;">
155
+ <label><span data-i18n="usage.explorer.filter.from">From</span> <input type="datetime-local" id="ov-from"></label>
156
+ <label><span data-i18n="usage.explorer.filter.to">To</span> <input type="datetime-local" id="ov-to"></label>
157
+ <button id="ov-query-btn" data-i18n="action.query">Query</button>
158
+ </div>
159
+ </div>
160
+ <div id="ov-cards" class="usage-cards"></div>
161
+ <div class="usage-table-section" style="margin-top:4px">
162
+ <h3 id="ov-table-title" data-i18n="usage.overview.title">Summary by Agent</h3>
163
+ <table id="ov-agent-table" class="usage-table"></table>
164
+ </div>
165
+
166
+ <!-- 模型访问明细查询 -->
167
+ <div class="usage-table-section" style="margin-top:16px">
168
+ <h3 data-i18n="usage.detail.title">Model Access Details</h3>
169
+ <div class="detail-filters">
170
+ <label><span data-i18n="usage.detail.agent">Agent</span>
171
+ <select id="detail-agent">
172
+ <option value="" data-i18n="usage.explorer.all">All</option>
173
+ </select>
174
+ </label>
175
+ <label><span data-i18n="usage.detail.model">Model</span>
176
+ <select id="detail-model">
177
+ <option value="" data-i18n="usage.explorer.all">All</option>
178
+ </select>
179
+ </label>
180
+ <label><span data-i18n="usage.detail.pageSize">Per page</span>
181
+ <select id="detail-page-size">
182
+ <option value="20">20</option>
183
+ <option value="50" selected>50</option>
184
+ <option value="100">100</option>
185
+ <option value="200">200</option>
186
+ </select>
187
+ </label>
188
+ </div>
189
+ <div id="detail-result" style="margin-top:12px;">
190
+ <table id="detail-table" class="usage-table"></table>
191
+ </div>
192
+ <div class="detail-pagination">
193
+ <button id="detail-prev-page" data-i18n="usage.detail.prevPage">Previous</button>
194
+ <input type="number" id="detail-page" value="1" min="1" style="width:60px;text-align:center">
195
+ <button id="detail-next-page" data-i18n="usage.detail.nextPage">Next</button>
196
+ <span id="detail-pagination-info" style="margin-left:12px;color:var(--dim);font-size:12px"></span>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <!-- Explorer 面板 -->
202
+ <div id="usage-explorer" class="usage-subpanel">
203
+ <div class="exp-layout">
204
+ <!-- 左侧列表 -->
205
+ <div class="exp-sidebar">
206
+ <div class="exp-sidebar-group">
207
+ <div class="exp-sidebar-title" data-i18n="usage.explorer.sidebar.agents">Agents</div>
208
+ <div id="exp-agent-list"></div>
209
+ </div>
210
+ <div class="exp-sidebar-group">
211
+ <div class="exp-sidebar-title" data-i18n="usage.explorer.sidebar.peers">Peers</div>
212
+ <div id="exp-peer-list"></div>
213
+ </div>
214
+ </div>
215
+ <!-- 右侧详情 -->
216
+ <div class="exp-detail">
217
+ <div id="exp-detail-header" class="exp-detail-header">
218
+ <span id="exp-selected-name" data-i18n="usage.explorer.selectHint">Select an Agent or Peer from the left</span>
219
+ </div>
220
+
221
+ <!-- 时间范围选择(与总览类似,但支持精确到时分秒) -->
222
+ <div class="exp-time-filters">
223
+ <div class="exp-range-tabs">
224
+ <button class="exp-range-btn active" data-range="last24" data-i18n="usage.overview.range.last24">Last 24 Hours</button>
225
+ <button class="exp-range-btn" data-range="week" data-i18n="usage.overview.range.week">This Week</button>
226
+ <button class="exp-range-btn" data-range="lastWeek" data-i18n="usage.overview.range.lastWeek">Last Week</button>
227
+ <button class="exp-range-btn" data-range="month" data-i18n="usage.overview.range.month">This Month</button>
228
+ <button class="exp-range-btn" data-range="last30" data-i18n="usage.overview.range.last30">Last 30 Days</button>
229
+ <button class="exp-range-btn" data-range="custom" data-i18n="usage.overview.range.custom">Custom</button>
230
+ </div>
231
+ <div class="exp-custom-date" id="exp-custom-date" style="display:none;">
232
+ <label><span data-i18n="usage.explorer.filter.from">From</span> <input type="datetime-local" id="exp-from"></label>
233
+ <label><span data-i18n="usage.explorer.filter.to">To</span> <input type="datetime-local" id="exp-to"></label>
234
+ <button id="exp-time-query-btn" data-i18n="action.query">Query</button>
235
+ </div>
236
+ </div>
237
+
238
+ <div id="exp-detail-cards" class="usage-cards"></div>
239
+
240
+ <!-- 模型和粒度筛选 -->
241
+ <div class="explorer-filters">
242
+ <label>
243
+ <span data-i18n="usage.explorer.filter.model">Model</span>
244
+ <select id="exp-model">
245
+ <option value="" data-i18n="usage.explorer.all">All</option>
246
+ </select>
247
+ </label>
248
+ <label>
249
+ <span data-i18n="usage.explorer.filter.granularity">Granularity</span>
250
+ <select id="exp-granularity">
251
+ <option value="hour" selected data-i18n="usage.explorer.filter.granularity.hour">Hour</option>
252
+ <option value="day" data-i18n="usage.explorer.filter.granularity.day">Day</option>
253
+ <option value="week" data-i18n="usage.explorer.filter.granularity.week">Week</option>
254
+ <option value="month" data-i18n="usage.explorer.filter.granularity.month">Month</option>
255
+ </select>
256
+ </label>
257
+ </div>
258
+
259
+ <div id="usage-explorer-chart" class="usage-chart" style="height:260px"></div>
260
+ <div class="usage-table-section" style="margin-top:12px">
261
+ <h3 data-i18n="usage.explorer.results">Results</h3>
262
+ <table id="usage-explorer-table" class="usage-table"></table>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </section>
269
+
270
+ <!-- 角色管理页面 -->
271
+ <section id="view-roleManagement" class="view">
272
+ <div class="role-management-shell">
273
+ <div class="role-management-toolbar">
274
+ <nav class="role-subtabs ag-subtabs" role="tablist" aria-label="角色">
275
+ <button class="role-subtab ag-subtab active" id="role-subtab-definitions" type="button" role="tab" aria-selected="true" aria-controls="role-panel-definitions" data-role-subview="definitions" data-i18n="roleManagement.definitions">角色定义</button>
276
+ <button class="role-subtab ag-subtab" id="role-subtab-assignments" type="button" role="tab" aria-selected="false" aria-controls="role-panel-assignments" data-role-subview="assignments" data-i18n="roleManagement.assignments">角色分配</button>
277
+ </nav>
278
+ <button class="btn-new-role" id="btn-new-role">+ 新增角色</button>
279
+ </div>
280
+
281
+ <div class="role-subpanel active" id="role-panel-definitions" role="tabpanel" aria-labelledby="role-subtab-definitions" data-role-panel="definitions">
282
+ <div class="role-defs-layout">
283
+ <!-- 全局默认角色选择器 -->
284
+ <div class="role-default-selector" id="role-default-selector">
285
+ <label class="role-default-label">
286
+ <span>默认角色 (Default Role):</span>
287
+ <select id="default-private-role-select" class="form-select">
288
+ <!-- 动态填充 -->
289
+ </select>
290
+ </label>
291
+ <small style="color: var(--dim);">未显式分配角色的用户将使用此角色</small>
292
+ </div>
293
+
294
+ <div class="role-cards-grid" id="role-cards-grid">
295
+ <!-- 动态渲染角色卡片 -->
296
+ </div>
297
+ </div>
298
+
299
+ <!-- 角色编辑弹窗 -->
300
+ <div id="role-edit-modal" class="modal" style="display:none">
301
+ <div class="modal-content">
302
+ <div class="modal-header">
303
+ <h3 id="role-edit-title">编辑角色</h3>
304
+ <button class="modal-close" id="role-edit-close">×</button>
305
+ </div>
306
+ <div class="modal-body" id="role-edit-body">
307
+ <!-- 动态渲染表单 -->
308
+ </div>
309
+ <div class="modal-footer">
310
+ <button class="btn-secondary" id="role-edit-cancel" data-i18n="roleDefs.cancel">取消</button>
311
+ <button class="btn-primary" id="role-edit-save" data-i18n="roleDefs.save">保存</button>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+
317
+ <div class="role-subpanel" id="role-panel-assignments" role="tabpanel" aria-labelledby="role-subtab-assignments" data-role-panel="assignments" hidden>
318
+ <div class="role-assign-layout">
319
+ <!-- Agent 选择器 + 搜索 + 筛选 -->
320
+ <div class="assign-header">
321
+ <label>
322
+ <span data-i18n="roles.selectAgent">Select Agent:</span>
323
+ <select id="roles-agent-select">
324
+ <option value="" data-i18n="roles.selectAgentPlaceholder">-- Select an Agent --</option>
325
+ </select>
326
+ </label>
327
+ <select id="filter-chattype">
328
+ <option value="" data-i18n="roles.filter.allChatType">全部会话</option>
329
+ <option value="private" data-i18n="roles.filter.private">💬 私聊</option>
330
+ <option value="group" data-i18n="roles.filter.group">👥 群聊</option>
331
+ </select>
332
+ <select id="filter-verify">
333
+ <option value="" data-i18n="roles.filter.allVerify">全部状态</option>
334
+ <option value="verified" data-i18n="roles.filter.verified">✅ 已验证</option>
335
+ <option value="invalid" data-i18n="roles.filter.invalid">⚠️ 验证失败</option>
336
+ <option value="agentmd-unverified" data-i18n="roles.filter.unverified">⚠️ 未验证</option>
337
+ <option value="unknown" data-i18n="roles.filter.unknown">❓ 未知</option>
338
+ </select>
339
+ <div class="search-box">
340
+ <input type="search" id="peer-search" placeholder="Search peer key..." data-i18n="roles.searchPlaceholder">
341
+ </div>
342
+ </div>
343
+
344
+ <!-- 对端关系列表 -->
345
+ <div class="assign-peer-roles">
346
+ <table id="relations-table" class="relations-table">
347
+ <thead>
348
+ <tr>
349
+ <th data-i18n="roles.table.chatType">会话</th>
350
+ <th data-i18n="roles.table.peerName">昵称</th>
351
+ <th data-i18n="roles.table.peerAid">对端AID</th>
352
+ <th data-i18n="roles.table.peerType">类型</th>
353
+ <th data-i18n="roles.table.role">角色</th>
354
+ <th data-i18n="roles.table.source">来源</th>
355
+ <th data-i18n="roles.table.actions">操作</th>
356
+ </tr>
357
+ </thead>
358
+ <tbody></tbody>
359
+ </table>
360
+ </div>
361
+ </div>
362
+
363
+ <!-- 对端角色编辑弹窗 -->
364
+ <div id="peer-role-modal" class="modal" style="display:none">
365
+ <div class="modal-content">
366
+ <div class="modal-header">
367
+ <h3 id="peer-role-title">Edit Peer Role</h3>
368
+ <button class="modal-close" id="peer-role-close">×</button>
369
+ </div>
370
+ <div class="modal-body" id="peer-role-body">
371
+ <!-- 动态渲染 -->
372
+ </div>
373
+ <div class="modal-footer">
374
+ <button class="btn-secondary" id="peer-role-cancel" data-i18n="roleDefs.cancel">Cancel</button>
375
+ <button class="btn-primary" id="peer-role-save" data-i18n="roleDefs.save">Save</button>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ </div>
381
+ </section>
382
+
383
+ </main>
384
+ </div>
385
+
386
+ <!-- 基础设置弹窗 -->
387
+ <div id="basic-settings-modal" class="basic-settings-modal" hidden>
388
+ <section class="basic-settings-dialog" role="dialog" aria-modal="true" aria-labelledby="basic-settings-title">
389
+ <header class="basic-settings-header">
390
+ <div>
391
+ <h2 id="basic-settings-title" data-i18n="basicSettings.title">基础设置</h2>
392
+ <p data-i18n="basicSettings.subtitle">按用途组织参数。保存前会校验并保留未编辑字段。</p>
393
+ </div>
394
+ <div class="basic-settings-header-actions">
395
+ <button type="button" class="basic-settings-close" id="basic-settings-close" data-i18n-title="common.close" aria-label="关闭">×</button>
396
+ </div>
397
+ </header>
398
+ <div class="basic-settings-layout">
399
+ <nav class="basic-settings-tabs" aria-label="基础设置页签" role="tablist">
400
+ <button type="button" class="basic-settings-tab active" role="tab" aria-selected="true" data-basic-tab="models"><span>01</span><b data-i18n="basicSettings.tabs.models">模型与推理</b><small data-i18n="basicSettings.tabs.modelsHint">基座、模型与连接</small></button>
401
+ <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="response"><span>02</span><b data-i18n="basicSettings.tabs.response">响应与会话</b><small data-i18n="basicSettings.tabs.responseHint">响应方式与续接</small></button>
402
+ <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="projects"><span>03</span><b data-i18n="basicSettings.tabs.projects">项目与服务</b><small data-i18n="basicSettings.tabs.projectsHint">路径与本机服务</small></button>
403
+ <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="identity"><span>04</span><b data-i18n="basicSettings.tabs.identity">身份与安全</b><small data-i18n="basicSettings.tabs.identityHint">AID 与访问保护</small></button>
404
+ <button type="button" class="basic-settings-tab" role="tab" aria-selected="false" data-basic-tab="runtime"><span>05</span><b data-i18n="basicSettings.tabs.runtime">运行与诊断</b><small data-i18n="basicSettings.tabs.runtimeHint">日志与稳定性</small></button>
405
+ </nav>
406
+ <div class="basic-settings-scroll" id="basic-settings-content" aria-live="polite"></div>
407
+ </div>
408
+ <footer class="basic-settings-footer">
409
+ <span id="basic-settings-status" class="basic-settings-status" data-i18n="basicSettings.noChanges">没有需要保存的改动</span>
410
+ <span class="basic-settings-footer-spacer"></span>
411
+ <button type="button" class="basic-settings-secondary-btn" id="basic-settings-json"><span aria-hidden="true">{ }</span><span data-i18n="basicSettings.viewJson">查看 JSON</span></button>
412
+ <button type="button" class="basic-settings-secondary-btn" id="basic-settings-reload" data-i18n="basicSettings.reload">重新读取</button>
413
+ <button type="button" class="basic-settings-primary-btn" id="basic-settings-save" data-i18n="basicSettings.save">保存设置</button>
414
+ </footer>
415
+ </section>
416
+ </div>
417
+
418
+ <div id="basic-settings-json-modal" class="basic-settings-json-modal" hidden>
419
+ <section class="basic-settings-json-dialog" role="dialog" aria-modal="true" aria-labelledby="basic-settings-json-title">
420
+ <header class="basic-settings-json-header"><div><h2 id="basic-settings-json-title" data-i18n="basicSettings.jsonTitle">查看 JSON</h2><p data-i18n="basicSettings.jsonSubtitle">只读预览,敏感值已脱敏。</p></div><button type="button" class="basic-settings-close" id="basic-settings-json-close" data-i18n-title="common.close" aria-label="关闭">×</button></header>
421
+ <div class="basic-settings-json-tabs"><button type="button" class="active" data-basic-json-file="daemon" data-i18n="basicSettings.daemonFile">daemon.json</button><button type="button" data-basic-json-file="defaults" data-i18n="basicSettings.defaultsFile">agents/defaults.json</button></div>
422
+ <pre id="basic-settings-json-view" class="basic-settings-json-view"></pre>
423
+ </section>
424
+ </div>
425
+
426
+ </body>
427
+ </html>