@agentunion/kite 1.4.0 → 1.5.0

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.
Files changed (235) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/cli.js +44 -5
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/server.py +33 -17
  7. package/extensions/channels/acp_channel/server.py +33 -17
  8. package/extensions/services/backup/entry.py +23 -16
  9. package/extensions/services/evol/auth_manager.py +443 -0
  10. package/extensions/services/evol/config.yaml +149 -0
  11. package/extensions/services/evol/config_loader.py +117 -0
  12. package/extensions/services/evol/entry.py +406 -0
  13. package/extensions/services/evol/evol_api.py +173 -0
  14. package/extensions/services/evol/evol_config.json5 +29 -0
  15. package/extensions/services/evol/migrate_tokens.py +122 -0
  16. package/extensions/services/evol/module.md +32 -0
  17. package/extensions/services/evol/pairing.py +250 -0
  18. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  19. package/extensions/services/evol/relay.py +682 -0
  20. package/extensions/services/evol/relay_config.json5 +67 -0
  21. package/extensions/services/evol/routes/__init__.py +1 -0
  22. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  23. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  24. package/extensions/services/evol/routes/routes_test.py +61 -0
  25. package/extensions/services/evol/server.py +875 -0
  26. package/extensions/services/evol/static/css/style.css +1200 -0
  27. package/extensions/services/evol/static/index.html +781 -0
  28. package/extensions/services/evol/static/index_evol.html +14 -0
  29. package/extensions/services/evol/static/js/app.js +6304 -0
  30. package/extensions/services/evol/static/js/auth.js +326 -0
  31. package/extensions/services/evol/static/js/dialog.js +285 -0
  32. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  33. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  34. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  35. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  36. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  37. package/extensions/services/evol/static/js/main.js +141 -0
  38. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  39. package/extensions/services/evol/static/js/stats.js +217 -0
  40. package/extensions/services/evol/static/js/token-manager.js +175 -0
  41. package/extensions/services/evol/static/pairing.html +248 -0
  42. package/extensions/services/evol/static/test_registry.html +262 -0
  43. package/extensions/services/evol/static/test_relay.html +462 -0
  44. package/extensions/services/evol/stats_manager.py +240 -0
  45. package/extensions/services/model_service/entry.py +23 -1
  46. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  47. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  48. package/extensions/services/proxy/_fix_prints.py +133 -0
  49. package/extensions/services/proxy/_fix_prints2.py +87 -0
  50. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  51. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  52. package/extensions/services/proxy/agentcp/README.md +260 -0
  53. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  54. package/extensions/services/proxy/agentcp/agent.py +4 -0
  55. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  56. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  57. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  58. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  59. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  60. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  61. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  62. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  63. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  64. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  65. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  66. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  67. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  68. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  69. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  70. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  71. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  72. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  73. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  74. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  75. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  76. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  77. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  78. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  79. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  80. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  81. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  82. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  83. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  84. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  85. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  86. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  87. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  88. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  89. package/extensions/services/proxy/agentcp/message.py +149 -0
  90. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  91. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  92. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  93. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  94. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  95. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  96. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  97. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  98. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  99. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  100. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  101. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  102. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  103. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  104. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  105. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  106. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  107. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  108. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  109. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  110. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  111. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  112. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  113. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  122. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  123. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  124. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  125. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  126. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  127. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  128. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  129. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  130. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  131. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  132. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  133. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  134. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  135. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  136. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  138. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  139. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  140. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  141. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  142. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  143. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  144. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  145. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  146. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  147. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  148. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  149. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  150. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  151. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  152. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  153. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  154. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  155. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  156. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  157. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  158. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  159. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  160. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  161. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  162. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  163. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  165. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  166. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  168. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  169. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  171. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  172. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  173. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  174. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  175. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  176. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  177. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  178. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  179. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  180. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  181. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  182. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  183. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  184. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  185. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  186. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  187. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  188. package/extensions/services/proxy/console_auth.py +109 -0
  189. package/extensions/services/proxy/evol/__init__.py +1 -0
  190. package/extensions/services/proxy/evol/config.py +37 -0
  191. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  192. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  193. package/extensions/services/proxy/evol/log.py +28 -0
  194. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  195. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  196. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  197. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  198. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  199. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  200. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  201. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  202. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  203. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  204. package/extensions/services/proxy/evol/version.py +24 -0
  205. package/extensions/services/proxy/logs/websocket.log +260 -0
  206. package/extensions/services/proxy/main.py +240 -0
  207. package/extensions/services/proxy/requirements.txt +13 -0
  208. package/extensions/services/proxy/server.py +271 -0
  209. package/extensions/services/watchdog/entry.py +42 -16
  210. package/extensions/services/watchdog/module.md +1 -0
  211. package/extensions/services/watchdog/monitor.py +34 -4
  212. package/extensions/services/web/module.md +1 -1
  213. package/extensions/services/web/server.py +30 -18
  214. package/extensions/services/web/static/js/token-manager.js +10 -10
  215. package/kernel/entry.py +1 -1
  216. package/kernel/module.md +25 -1
  217. package/kernel/registry_store.py +2 -26
  218. package/kernel/rpc_router.py +36 -10
  219. package/kernel/server.py +106 -17
  220. package/kite_cli/commands/deps_install.py +67 -0
  221. package/kite_cli/commands/env_check.py +45 -0
  222. package/kite_cli/commands/prepare.py +49 -0
  223. package/kite_cli/commands/venv_setup.py +56 -0
  224. package/kite_cli/main.py +29 -1
  225. package/launcher/entry.py +306 -21
  226. package/launcher/module.md +9 -0
  227. package/launcher/module_scanner.py +11 -1
  228. package/main.py +4 -1
  229. package/package.json +8 -1
  230. package/python_version.json +4 -0
  231. package/requirements.txt +38 -0
  232. package/scripts/env-manager.js +328 -0
  233. package/scripts/python-env.js +79 -0
  234. package/scripts/scan_dependencies.py +461 -0
  235. package/scripts/setup-python-env.js +191 -0
@@ -0,0 +1,217 @@
1
+ // Stats functions
2
+
3
+ let currentPeriod = 'day';
4
+ let currentDate = new Date().toISOString().split('T')[0];
5
+
6
+ async function loadCreditsStats(period = 'day', date = null) {
7
+ const kiteToken = localStorage.getItem('evol_kite_token');
8
+ if (!kiteToken) {
9
+ document.getElementById('stats-content').innerHTML = '<p>请先登录</p>';
10
+ return;
11
+ }
12
+
13
+ currentPeriod = period;
14
+ if (date) {
15
+ currentDate = date;
16
+ }
17
+
18
+ try {
19
+ const response = await fetch('/api/get_credits_stats', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({
23
+ kiteToken,
24
+ period,
25
+ date: currentDate
26
+ })
27
+ });
28
+
29
+ const result = await response.json();
30
+
31
+ if (result.success) {
32
+ renderStats(result.data);
33
+ } else {
34
+ document.getElementById('stats-content').innerHTML = `<p class="error">${result.msg}</p>`;
35
+ }
36
+ } catch (error) {
37
+ document.getElementById('stats-content').innerHTML = '<p class="error">加载失败</p>';
38
+ }
39
+ }
40
+
41
+ function renderStats(data) {
42
+ const consumed = data.consumed || 0;
43
+ const recovered = data.recovered || 0;
44
+ const netChange = recovered - consumed;
45
+ const changeText = netChange > 0 ? `+${netChange}` : netChange;
46
+
47
+ let html = `
48
+ <div class="panel">
49
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
50
+ <h3 class="panel-title" style="margin: 0;">${data.date}</h3>
51
+ <div style="display: flex; gap: 8px;">
52
+ <button class="btn btn-sm ${currentPeriod === 'day' ? 'btn-primary' : 'btn-secondary'}" onclick="switchPeriod('day')">按天</button>
53
+ <button class="btn btn-sm ${currentPeriod === 'week' ? 'btn-primary' : 'btn-secondary'}" onclick="switchPeriod('week')">按周</button>
54
+ <button class="btn btn-sm ${currentPeriod === 'month' ? 'btn-primary' : 'btn-secondary'}" onclick="switchPeriod('month')">按月</button>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="stats-row" style="margin-bottom: 20px;">
59
+ <div class="stat-card">
60
+ <div class="stat-value">${data.startCredits}</div>
61
+ <div class="stat-label">起始积分</div>
62
+ </div>
63
+ <div class="stat-card">
64
+ <div class="stat-value">${data.endCredits}</div>
65
+ <div class="stat-label">结束积分</div>
66
+ </div>
67
+ <div class="stat-card">
68
+ <div class="stat-value" style="color: var(--error)">${consumed}</div>
69
+ <div class="stat-label">消耗</div>
70
+ </div>
71
+ <div class="stat-card">
72
+ <div class="stat-value" style="color: var(--success)">${recovered}</div>
73
+ <div class="stat-label">恢复</div>
74
+ </div>
75
+ </div>
76
+
77
+ <div style="background: var(--white); padding: 20px; border-radius: 8px; border: 1px solid var(--gray-200); margin-bottom: 16px;">
78
+ <canvas id="creditsChart" style="width: 100%; height: 300px;"></canvas>
79
+ </div>
80
+
81
+ <div style="display: flex; justify-content: center; gap: 10px;">
82
+ <button class="btn btn-secondary" onclick="prevDate()">← 上一${getPeriodName()}</button>
83
+ <button class="btn btn-primary" onclick="today()">今天</button>
84
+ <button class="btn btn-secondary" onclick="nextDate()">下一${getPeriodName()} →</button>
85
+ </div>
86
+ </div>
87
+ `;
88
+
89
+ document.getElementById('stats-content').innerHTML = html;
90
+
91
+ // Render chart
92
+ renderChart(data.dataPoints);
93
+ }
94
+
95
+ function renderChart(dataPoints) {
96
+ const canvas = document.getElementById('creditsChart');
97
+ if (!canvas) return;
98
+
99
+ const ctx = canvas.getContext('2d');
100
+
101
+ // Simple line chart implementation
102
+ if (dataPoints.length === 0) {
103
+ ctx.fillText('暂无数据', canvas.width / 2, canvas.height / 2);
104
+ return;
105
+ }
106
+
107
+ // Clear canvas
108
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
109
+
110
+ // Set canvas size
111
+ canvas.width = canvas.offsetWidth;
112
+ canvas.height = 300;
113
+
114
+ const padding = 40;
115
+ const width = canvas.width - padding * 2;
116
+ const height = canvas.height - padding * 2;
117
+
118
+ // Find min/max
119
+ const credits = dataPoints.map(p => p.credits);
120
+ const minCredits = Math.min(...credits);
121
+ const maxCredits = Math.max(...credits);
122
+ const range = maxCredits - minCredits || 1;
123
+
124
+ // Draw axes
125
+ ctx.strokeStyle = '#e0e0e0';
126
+ ctx.lineWidth = 1;
127
+ ctx.beginPath();
128
+ ctx.moveTo(padding, padding);
129
+ ctx.lineTo(padding, padding + height);
130
+ ctx.lineTo(padding + width, padding + height);
131
+ ctx.stroke();
132
+
133
+ // Draw line
134
+ ctx.strokeStyle = '#1890ff';
135
+ ctx.lineWidth = 2;
136
+ ctx.beginPath();
137
+
138
+ dataPoints.forEach((point, i) => {
139
+ const x = padding + (i / (dataPoints.length - 1 || 1)) * width;
140
+ const y = padding + height - ((point.credits - minCredits) / range) * height;
141
+
142
+ if (i === 0) {
143
+ ctx.moveTo(x, y);
144
+ } else {
145
+ ctx.lineTo(x, y);
146
+ }
147
+ });
148
+
149
+ ctx.stroke();
150
+
151
+ // Draw points
152
+ ctx.fillStyle = '#1890ff';
153
+ dataPoints.forEach((point, i) => {
154
+ const x = padding + (i / (dataPoints.length - 1 || 1)) * width;
155
+ const y = padding + height - ((point.credits - minCredits) / range) * height;
156
+
157
+ ctx.beginPath();
158
+ ctx.arc(x, y, 3, 0, Math.PI * 2);
159
+ ctx.fill();
160
+ });
161
+
162
+ // Draw labels
163
+ ctx.fillStyle = '#666';
164
+ ctx.font = '12px sans-serif';
165
+ ctx.textAlign = 'right';
166
+ ctx.fillText(maxCredits.toString(), padding - 5, padding + 5);
167
+ ctx.fillText(minCredits.toString(), padding - 5, padding + height + 5);
168
+ }
169
+
170
+ function switchPeriod(period) {
171
+ loadCreditsStats(period, currentDate);
172
+ }
173
+
174
+ function getPeriodName() {
175
+ return currentPeriod === 'day' ? '天' : currentPeriod === 'week' ? '周' : '月';
176
+ }
177
+
178
+ function prevDate() {
179
+ const date = new Date(currentDate);
180
+ if (currentPeriod === 'day') {
181
+ date.setDate(date.getDate() - 1);
182
+ } else if (currentPeriod === 'week') {
183
+ date.setDate(date.getDate() - 7);
184
+ } else {
185
+ date.setMonth(date.getMonth() - 1);
186
+ }
187
+ currentDate = date.toISOString().split('T')[0];
188
+ loadCreditsStats(currentPeriod, currentDate);
189
+ }
190
+
191
+ function nextDate() {
192
+ const date = new Date(currentDate);
193
+ if (currentPeriod === 'day') {
194
+ date.setDate(date.getDate() + 1);
195
+ } else if (currentPeriod === 'week') {
196
+ date.setDate(date.getDate() + 7);
197
+ } else {
198
+ date.setMonth(date.getMonth() + 1);
199
+ }
200
+ currentDate = date.toISOString().split('T')[0];
201
+ loadCreditsStats(currentPeriod, currentDate);
202
+ }
203
+
204
+ function today() {
205
+ currentDate = new Date().toISOString().split('T')[0];
206
+ loadCreditsStats(currentPeriod, currentDate);
207
+ }
208
+
209
+ // Auto load stats when switching to stats tab
210
+ document.addEventListener('DOMContentLoaded', () => {
211
+ const statsTab = document.querySelector('[data-tab="stats"]');
212
+ if (statsTab) {
213
+ statsTab.addEventListener('click', () => {
214
+ setTimeout(() => loadCreditsStats(), 100);
215
+ });
216
+ }
217
+ });
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Token Management
3
+ * Handles token listing, revocation, and statistics via WebSocket RPC
4
+ */
5
+
6
+ // Load tokens on page load
7
+ async function loadTokens() {
8
+ console.log('[evol] loadTokens() called');
9
+
10
+ // 立即显示加载中
11
+ const tbody = document.getElementById('tokens-tbody');
12
+ tbody.innerHTML = `
13
+ <tr><td colspan="6" class="text-muted" style="text-align:center;padding:40px;">
14
+ 加载中...
15
+ </td></tr>
16
+ `;
17
+
18
+ try {
19
+ // Use kernel client to call RPC
20
+ if (!kernelClient || !kernelClient.connected) {
21
+ console.error('[evol] kernelClient not ready:', { kernelClient, connected: kernelClient?.connected });
22
+ throw new Error('WebSocket 未连接');
23
+ }
24
+
25
+ console.log('[evol] Calling evol.list_tokens...');
26
+ const result = await kernelClient.call('evol.list_tokens', {});
27
+ console.log('[evol] Got result:', result);
28
+ renderTokens(result.tokens || []);
29
+ } catch (err) {
30
+ console.error('[evol] Failed to load tokens:', err);
31
+
32
+ // 根据错误类型显示不同的提示
33
+ let errorMsg = err.message;
34
+ if (errorMsg.includes('Permission denied')) {
35
+ errorMsg = '权限不足:当前用户没有查看 Token 的权限';
36
+ } else if (errorMsg.includes('not connected') || errorMsg.includes('未连接')) {
37
+ errorMsg = 'WebSocket 未连接,请刷新页面重试';
38
+ }
39
+
40
+ tbody.innerHTML = `
41
+ <tr><td colspan="6" class="text-danger" style="text-align:center;padding:40px;">
42
+ 加载失败: ${errorMsg}
43
+ </td></tr>
44
+ `;
45
+ }
46
+ }
47
+
48
+ // Render tokens table
49
+ function renderTokens(tokens) {
50
+ const tbody = document.getElementById('tokens-tbody');
51
+
52
+ if (tokens.length === 0) {
53
+ tbody.innerHTML = `
54
+ <tr><td colspan="6" class="text-muted" style="text-align:center;padding:40px;">
55
+ 暂无 Token
56
+ </td></tr>
57
+ `;
58
+ return;
59
+ }
60
+
61
+ tbody.innerHTML = tokens.map(token => {
62
+ const isExpired = new Date(token.expires_at) < new Date();
63
+ const statusClass = isExpired ? 'text-danger' : 'text-success';
64
+ const statusText = isExpired ? '已过期' : '活跃';
65
+
66
+ // Mask token for security (show first 8 and last 4 chars)
67
+ const maskedToken = token.token.length > 12
68
+ ? `${token.token.substring(0, 8)}...${token.token.substring(token.token.length - 4)}`
69
+ : token.token;
70
+
71
+ return `
72
+ <tr>
73
+ <td style="font-family:monospace;font-size:12px;" title="${token.token}">${maskedToken}</td>
74
+ <td><span class="badge badge-${getRoleBadgeClass(token.role)}">${token.role}</span></td>
75
+ <td style="font-size:12px;">${formatDateTime(token.paired_at)}</td>
76
+ <td style="font-size:12px;">${formatDateTime(token.expires_at)}</td>
77
+ <td><span class="${statusClass}">${statusText}</span></td>
78
+ <td>
79
+ <button class="btn btn-sm btn-danger" onclick="revokeToken('${token.token}')" ${isExpired ? 'disabled' : ''}>
80
+ 吊销
81
+ </button>
82
+ </td>
83
+ </tr>
84
+ `;
85
+ }).join('');
86
+ }
87
+
88
+ // Get role badge class
89
+ function getRoleBadgeClass(role) {
90
+ const classes = {
91
+ 'admin': 'primary',
92
+ 'operator': 'warning',
93
+ 'viewer': 'secondary'
94
+ };
95
+ return classes[role] || 'secondary';
96
+ }
97
+
98
+ // Format datetime
99
+ function formatDateTime(isoString) {
100
+ if (!isoString) return '-';
101
+ const date = new Date(isoString);
102
+ return date.toLocaleString('zh-CN', {
103
+ year: 'numeric',
104
+ month: '2-digit',
105
+ day: '2-digit',
106
+ hour: '2-digit',
107
+ minute: '2-digit'
108
+ });
109
+ }
110
+
111
+ // Revoke token
112
+ async function revokeToken(token) {
113
+ try {
114
+ if (!kernelClient || !kernelClient.connected) {
115
+ throw new Error('WebSocket 未连接');
116
+ }
117
+
118
+ // 立即变灰并禁用
119
+ const row = event.target.closest('tr');
120
+ if (row) {
121
+ row.style.opacity = '0.3';
122
+ row.style.pointerEvents = 'none';
123
+ }
124
+
125
+ // 后台调用 RPC 吊销
126
+ await kernelClient.call('evol.revoke_token', { token });
127
+
128
+ } catch (err) {
129
+ console.error('[evol] Failed to revoke token:', err);
130
+
131
+ // 失败后恢复该行
132
+ const row = event.target.closest('tr');
133
+ if (row) {
134
+ row.style.opacity = '1';
135
+ row.style.pointerEvents = 'auto';
136
+ }
137
+
138
+ // 显示错误提示
139
+ const errorMsg = err.message.includes('Permission denied')
140
+ ? '权限不足:无法吊销 Token'
141
+ : `吊销失败: ${err.message}`;
142
+
143
+ console.error('[evol]', errorMsg);
144
+ }
145
+ }
146
+
147
+ // Refresh tokens
148
+ function refreshTokens() {
149
+ loadTokens();
150
+ }
151
+
152
+ // Auto-load tokens when modules page is shown
153
+ document.addEventListener('DOMContentLoaded', () => {
154
+ // Load tokens when switching to modules page
155
+ const modulesNav = document.querySelector('[data-page="modules"]');
156
+ if (modulesNav) {
157
+ modulesNav.addEventListener('click', () => {
158
+ // Wait a bit for page switch, then load tokens
159
+ setTimeout(() => {
160
+ console.log('[evol] Modules page clicked, loading tokens...');
161
+ loadTokens();
162
+ }, 100);
163
+ });
164
+ }
165
+
166
+ // Wait for kernelClient to be ready before loading tokens
167
+ window.addEventListener('kernelClientReady', () => {
168
+ console.log('[evol] kernelClient ready');
169
+ // Load tokens if modules page is active on load
170
+ if (document.getElementById('page-modules').classList.contains('active')) {
171
+ console.log('[evol] Modules page is active, loading tokens...');
172
+ loadTokens();
173
+ }
174
+ });
175
+ });
@@ -0,0 +1,248 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Kite 配对</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 16px;
27
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
28
+ padding: 40px;
29
+ max-width: 400px;
30
+ width: 100%;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 28px;
35
+ color: #333;
36
+ margin-bottom: 10px;
37
+ text-align: center;
38
+ }
39
+
40
+ .subtitle {
41
+ color: #666;
42
+ text-align: center;
43
+ margin-bottom: 30px;
44
+ font-size: 14px;
45
+ }
46
+
47
+ .form-group {
48
+ margin-bottom: 20px;
49
+ }
50
+
51
+ label {
52
+ display: block;
53
+ color: #555;
54
+ font-size: 14px;
55
+ margin-bottom: 8px;
56
+ font-weight: 500;
57
+ }
58
+
59
+ input {
60
+ width: 100%;
61
+ padding: 12px 16px;
62
+ border: 2px solid #e0e0e0;
63
+ border-radius: 8px;
64
+ font-size: 16px;
65
+ transition: border-color 0.3s;
66
+ }
67
+
68
+ input:focus {
69
+ outline: none;
70
+ border-color: #667eea;
71
+ }
72
+
73
+ button {
74
+ width: 100%;
75
+ padding: 14px;
76
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
77
+ color: white;
78
+ border: none;
79
+ border-radius: 8px;
80
+ font-size: 16px;
81
+ font-weight: 600;
82
+ cursor: pointer;
83
+ transition: transform 0.2s, box-shadow 0.2s;
84
+ }
85
+
86
+ button:hover {
87
+ transform: translateY(-2px);
88
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
89
+ }
90
+
91
+ button:active {
92
+ transform: translateY(0);
93
+ }
94
+
95
+ button:disabled {
96
+ opacity: 0.6;
97
+ cursor: not-allowed;
98
+ transform: none;
99
+ }
100
+
101
+ .message {
102
+ margin-top: 20px;
103
+ padding: 12px;
104
+ border-radius: 8px;
105
+ font-size: 14px;
106
+ text-align: center;
107
+ }
108
+
109
+ .message.error {
110
+ background: #fee;
111
+ color: #c33;
112
+ border: 1px solid #fcc;
113
+ }
114
+
115
+ .message.success {
116
+ background: #efe;
117
+ color: #3c3;
118
+ border: 1px solid #cfc;
119
+ }
120
+
121
+ .message.info {
122
+ background: #eef;
123
+ color: #33c;
124
+ border: 1px solid #ccf;
125
+ }
126
+
127
+ .hint {
128
+ margin-top: 20px;
129
+ padding: 12px;
130
+ background: #f5f5f5;
131
+ border-radius: 8px;
132
+ font-size: 13px;
133
+ color: #666;
134
+ }
135
+
136
+ .hint strong {
137
+ color: #333;
138
+ }
139
+ </style>
140
+ </head>
141
+ <body>
142
+ <div class="container">
143
+ <h1>🚀 Kite 配对</h1>
144
+ <p class="subtitle">输入配对码以连接到 Kite 系统</p>
145
+
146
+ <div class="form-group">
147
+ <label for="code">配对码</label>
148
+ <input type="text" id="code" placeholder="输入 6 位配对码" maxlength="6" autocomplete="off">
149
+ </div>
150
+
151
+ <button id="pairBtn">配对</button>
152
+
153
+ <div id="message"></div>
154
+
155
+ <div class="hint">
156
+ <strong>如何获取配对码?</strong><br>
157
+ 配对码会在 Kite 启动时自动生成并显示在控制台(绿色高亮)<br>
158
+ 每个配对码只能使用一次,使用后会自动生成新的配对码
159
+ </div>
160
+ </div>
161
+
162
+ <script src="js/kernel-client.js"></script>
163
+ <script>
164
+ const codeInput = document.getElementById('code');
165
+ const pairBtn = document.getElementById('pairBtn');
166
+ const messageDiv = document.getElementById('message');
167
+
168
+ // 页面加载时请求配对码
169
+ window.addEventListener('DOMContentLoaded', async () => {
170
+ showMessage('正在生成配对码...', 'info');
171
+
172
+ try {
173
+ // 连接 WebSocket 请求配对码
174
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
175
+ const ws = new WebSocket(`${proto}//${location.host}/ws/relay`);
176
+
177
+ ws.onopen = () => {
178
+ // 请求配对码
179
+ ws.send(JSON.stringify({ type: 'request_code' }));
180
+ };
181
+
182
+ ws.onmessage = (event) => {
183
+ const msg = JSON.parse(event.data);
184
+
185
+ if (msg.type === 'code_generated') {
186
+ showMessage(`配对码已生成,请查看控制台`, 'success');
187
+ codeInput.focus();
188
+ } else if (msg.type === 'error') {
189
+ showMessage(`生成配对码失败: ${msg.message}`, 'error');
190
+ }
191
+ };
192
+
193
+ ws.onerror = () => {
194
+ showMessage('连接失败,请刷新页面重试', 'error');
195
+ };
196
+
197
+ } catch (err) {
198
+ showMessage(`生成配对码失败: ${err.message}`, 'error');
199
+ }
200
+ });
201
+
202
+ // 回车提交
203
+ codeInput.addEventListener('keypress', (e) => {
204
+ if (e.key === 'Enter') {
205
+ pairBtn.click();
206
+ }
207
+ });
208
+
209
+ // 配对
210
+ pairBtn.addEventListener('click', async () => {
211
+ const code = codeInput.value.trim();
212
+
213
+ if (!code) {
214
+ showMessage('请输入配对码', 'error');
215
+ return;
216
+ }
217
+
218
+ pairBtn.disabled = true;
219
+ showMessage('正在配对...', 'info');
220
+
221
+ try {
222
+ // 创建 Kernel 客户端
223
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
224
+ const client = new KernelClient(`${proto}//${location.host}/ws/relay`);
225
+
226
+ // 配对
227
+ const result = await client.pair(code);
228
+
229
+ showMessage(`配对成功!模块 ID: ${result.moduleId}`, 'success');
230
+
231
+ // 2 秒后跳转
232
+ setTimeout(() => {
233
+ window.location.href = '/';
234
+ }, 2000);
235
+
236
+ } catch (err) {
237
+ showMessage(`配对失败: ${err.message}`, 'error');
238
+ pairBtn.disabled = false;
239
+ }
240
+ });
241
+
242
+ function showMessage(text, type) {
243
+ messageDiv.textContent = text;
244
+ messageDiv.className = `message ${type}`;
245
+ }
246
+ </script>
247
+ </body>
248
+ </html>