comate-ui-inspector 0.1.5-alpha.1 → 0.1.5-alpha.2

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.
@@ -126,10 +126,12 @@
126
126
  if (rec && sameHost(url)) {
127
127
  p.then(
128
128
  function (r) {
129
- rec.network.push({method: method, url: url, status: r.status, ms: Date.now() - t0, ts: t0});
129
+ var t1 = Date.now();
130
+ rec.network.push({method: method, url: url, status: r.status, startTs: t0, endTs: t1, ms: t1 - t0});
130
131
  },
131
132
  function () {
132
- rec.network.push({method: method, url: url, status: 0, ms: Date.now() - t0, ts: t0, error: true});
133
+ var t1 = Date.now();
134
+ rec.network.push({method: method, url: url, status: 0, startTs: t0, endTs: t1, ms: t1 - t0, error: true});
133
135
  }
134
136
  );
135
137
  }
@@ -148,12 +150,14 @@
148
150
  if (info) {
149
151
  xhr.addEventListener('loadend', function () {
150
152
  if (rec && sameHost(info.url)) {
153
+ var t1 = Date.now();
151
154
  rec.network.push({
152
155
  method: info.method,
153
156
  url: info.url,
154
157
  status: xhr.status,
155
- ms: Date.now() - info.t0,
156
- ts: info.t0,
158
+ startTs: info.t0,
159
+ endTs: t1,
160
+ ms: t1 - info.t0,
157
161
  });
158
162
  }
159
163
  });
@@ -119,11 +119,27 @@
119
119
  <body>
120
120
  <h1>ui-inspector 配置</h1>
121
121
 
122
- <!-- 增强包模式区:无增强包时保持空;popup.js 读 enhancements.json 后动态填充 tab 与面板。 -->
122
+ <!-- 顶层 tab:模式 / 设置 -->
123
123
  <div
124
- id="mode-bar"
125
- style="display: none"
124
+ class="tabs"
125
+ id="top-tabs"
126
126
  >
127
+ <button
128
+ class="tab active"
129
+ data-top="mode"
130
+ >
131
+ 模式
132
+ </button>
133
+ <button
134
+ class="tab"
135
+ data-top="settings"
136
+ >
137
+ 设置
138
+ </button>
139
+ </div>
140
+
141
+ <!-- 「模式」页:选择 Element + 各增强包模式(popup.js 读 enhancements.json 动态填充子 tab) -->
142
+ <div id="top-mode">
127
143
  <div
128
144
  class="tabs"
129
145
  id="mode-tabs"
@@ -137,71 +153,77 @@
137
153
  <div id="mode-panes"></div>
138
154
  </div>
139
155
 
140
- <section>
141
- <div class="label">生效域名</div>
142
- <div class="row">
143
- <input
144
- id="domain-input"
145
- type="text"
146
- placeholder="如 https://example.com"
147
- />
148
- <button
149
- id="add-domain"
150
- class="primary"
151
- >
152
- 添加
153
- </button>
154
- </div>
155
- <ul id="domain-list"></ul>
156
- <div class="hint">添加时浏览器会请求该域名的访问权限,允许后即在该域名注入选取脚本。</div>
157
- </section>
158
-
159
- <section>
160
- <div class="label">传输通路</div>
161
- <label class="radio">
162
- <input
163
- type="radio"
164
- name="transport"
165
- value="native"
166
- />
167
- 本地(Native Messaging,默认)
168
- </label>
169
- <label class="radio">
170
- <input
171
- type="radio"
172
- name="transport"
173
- value="http"
174
- />
175
- 远程(HTTP bridge)
176
- </label>
177
- <div id="bridge-config">
178
- <div
179
- class="row"
180
- style="margin-top: 6px"
181
- >
156
+ <!-- 「设置」页:生效域名 + 传输通路 -->
157
+ <div
158
+ id="top-settings"
159
+ style="display: none"
160
+ >
161
+ <section>
162
+ <div class="label">生效域名</div>
163
+ <div class="row">
182
164
  <input
183
- id="bridge-target"
165
+ id="domain-input"
184
166
  type="text"
185
- placeholder="devlocal.oreateai.com:8917"
167
+ placeholder="如 https://example.com"
186
168
  />
169
+ <button
170
+ id="add-domain"
171
+ class="primary"
172
+ >
173
+ 添加
174
+ </button>
187
175
  </div>
188
- <div class="hint">
189
- 填开发机 bridge 的完整地址(如
190
- <code>devlocal.oreateai.com:8917</code>
191
- )。开发机上
192
- <code>ui-inspector install --remote</code>
193
- 起 bridge(默认绑 0.0.0.0 供直连)。
176
+ <ul id="domain-list"></ul>
177
+ <div class="hint">添加时浏览器会请求该域名的访问权限,允许后即在该域名注入选取脚本。</div>
178
+ </section>
179
+
180
+ <section>
181
+ <div class="label">传输通路</div>
182
+ <label class="radio">
183
+ <input
184
+ type="radio"
185
+ name="transport"
186
+ value="native"
187
+ />
188
+ 本地(Native Messaging,默认)
189
+ </label>
190
+ <label class="radio">
191
+ <input
192
+ type="radio"
193
+ name="transport"
194
+ value="http"
195
+ />
196
+ 远程(HTTP bridge)
197
+ </label>
198
+ <div id="bridge-config">
199
+ <div
200
+ class="row"
201
+ style="margin-top: 6px"
202
+ >
203
+ <input
204
+ id="bridge-target"
205
+ type="text"
206
+ placeholder="devlocal.oreateai.com:8917"
207
+ />
208
+ </div>
209
+ <div class="hint">
210
+ 填开发机 bridge 的完整地址(如
211
+ <code>devlocal.oreateai.com:8917</code>
212
+ )。开发机上
213
+ <code>ui-inspector install --remote</code>
214
+ 起 bridge(默认绑 0.0.0.0 供直连)。
215
+ </div>
194
216
  </div>
195
- </div>
196
- </section>
217
+ </section>
197
218
 
198
- <button
199
- id="save"
200
- class="primary"
201
- >
202
- 保存传输设置
203
- </button>
204
- <div id="msg"></div>
219
+ <button
220
+ id="save"
221
+ class="primary"
222
+ >
223
+ 保存传输设置
224
+ </button>
225
+ <div id="msg"></div>
226
+ </div>
205
227
 
206
228
  <script src="popup.js"></script>
207
229
  </body>
@@ -139,12 +139,28 @@ $('domain-input').addEventListener('keydown', e => {
139
139
  renderDomains();
140
140
  renderTransport();
141
141
 
142
- // ---------- 增强包:通用加载器 ----------
142
+ // ---------- 顶层 tab:模式 / 设置 ----------
143
+ function initTopTabs() {
144
+ const topTabs = [...document.querySelectorAll('#top-tabs .tab')];
145
+ const panes = {mode: $('top-mode'), settings: $('top-settings')};
146
+ for (const b of topTabs) {
147
+ b.onclick = () => {
148
+ for (const x of topTabs) {
149
+ x.classList.toggle('active', x === b);
150
+ }
151
+ for (const [key, el] of Object.entries(panes)) {
152
+ el.style.display = b.dataset.top === key ? 'block' : 'none';
153
+ }
154
+ };
155
+ }
156
+ }
157
+ initTopTabs();
158
+
159
+ // ---------- 「模式」页:选择 Element + 增强包子 tab ----------
143
160
  // 读 extension/enhancements.json(由 `ui-inspector add <名>` 拷贝插件并写入)。
144
- // 每个插件描述:{id, name, pane, paneJs}。有插件时显示「模式」tab 区:
145
- // 「选择 Element」固定第一项 + 每个插件一项;点 tab 切换对应面板。
161
+ // 子 tab 第一项固定「选择 Element」,其后每个增强包一项;无增强包时隐藏子 tab 条只留默认提示。
146
162
  // 面板 HTML 片段从 enhancements/<id>/<pane> 拉取内嵌,随后加载 enhancements/<id>/<paneJs>。
147
- async function initEnhancements() {
163
+ async function initModes() {
148
164
  let plugins = [];
149
165
  try {
150
166
  const r = await fetch(chrome.runtime.getURL('enhancements.json'));
@@ -157,9 +173,6 @@ async function initEnhancements() {
157
173
  } catch {
158
174
  plugins = [];
159
175
  }
160
- if (!plugins.length) {
161
- return; // 基础版:不显示模式 tab
162
- }
163
176
 
164
177
  const tabsEl = $('mode-tabs');
165
178
  const panesEl = $('mode-panes');
@@ -169,9 +182,9 @@ async function initEnhancements() {
169
182
  const b = document.createElement('button');
170
183
  b.className = active ? 'tab active' : 'tab';
171
184
  b.textContent = label;
185
+ b.dataset.mode = key;
172
186
  b.onclick = () => switchMode(key);
173
187
  tabsEl.appendChild(b);
174
- b.dataset.mode = key;
175
188
  return b;
176
189
  }
177
190
 
@@ -208,6 +221,9 @@ async function initEnhancements() {
208
221
  }
209
222
  }
210
223
 
211
- $('mode-bar').style.display = 'block';
224
+ // 只有「选择 Element」一个模式时,子 tab 条没有意义,隐藏它。
225
+ if (!plugins.length) {
226
+ tabsEl.style.display = 'none';
227
+ }
212
228
  }
213
- initEnhancements();
229
+ initModes();
@@ -58,25 +58,37 @@ function describe(target, index, total) {
58
58
  }
59
59
 
60
60
  function describeRecording(r) {
61
- const secs = Math.round(((r.endedAt || 0) - (r.startedAt || 0) || 0) / 1000);
62
- const parts = [`【ui-inspector 录制】用户录制了一段操作(页面 ${r.pageUrl || ''},约 ${secs}s):`];
63
- if (r.clicks && r.clicks.length) {
64
- parts.push(`· 点击 ${r.clicks.length} 次(由先到后):`);
65
- r.clicks.slice(0, 50).forEach((c, i) => {
66
- const files = (c.componentChain || []).map(x => x && x.file).filter(Boolean);
67
- parts.push(
68
- ` ${i + 1}. <${c.tag}> ${JSON.stringify((c.text || '').slice(0, 40))} @ ${c.selector}` +
69
- (files.length ? ` | 源码 ${files.join(' <- ')}` : '')
70
- );
61
+ const base = r.startedAt || 0;
62
+ const secs = Math.round(((r.endedAt || 0) - base || 0) / 1000);
63
+ const rel = t => (typeof t === 'number' ? `+${((t - base) / 1000).toFixed(2)}s` : '+?');
64
+
65
+ // 把点击 / 控制台 / 网络归并成一条按时间排序的时间线;网络拆成「发起」「完成」两个事件。
66
+ const events = [];
67
+ (r.clicks || []).forEach(c => {
68
+ const files = (c.componentChain || []).map(x => x && x.file).filter(Boolean);
69
+ events.push({
70
+ ts: c.ts,
71
+ text:
72
+ `点击 <${c.tag}> ${JSON.stringify((c.text || '').slice(0, 40))} @ ${c.selector}` +
73
+ (files.length ? ` | 源码 ${files.join(' <- ')}` : ''),
71
74
  });
72
- }
73
- if (r.console && r.console.length) {
74
- parts.push(`· 控制台 ${r.console.length} 条:`);
75
- r.console.slice(0, 50).forEach(l => parts.push(` [${l.level}] ${String(l.text).slice(0, 300)}`));
76
- }
77
- if (r.network && r.network.length) {
78
- parts.push(`· 同域网络 ${r.network.length} 条:`);
79
- r.network.slice(0, 50).forEach(n => parts.push(` ${n.method} ${n.url} → ${n.status} (${n.ms}ms)`));
75
+ });
76
+ (r.console || []).forEach(l => {
77
+ events.push({ts: l.ts, text: `控制台[${l.level}] ${String(l.text).slice(0, 300)}`});
78
+ });
79
+ (r.network || []).forEach(n => {
80
+ events.push({ts: n.startTs, text: `网络→发起 ${n.method} ${n.url}`});
81
+ events.push({ts: n.endTs, text: `网络←完成 ${n.method} ${n.url} → ${n.status} (${n.ms}ms)`});
82
+ });
83
+ // V8 的 sort 稳定:同一时刻保持插入序(发起在完成之前)。
84
+ events.sort((a, b) => (a.ts || 0) - (b.ts || 0));
85
+
86
+ const parts = [
87
+ `【ui-inspector 录制】用户录制了一段操作(页面 ${r.pageUrl || ''},约 ${secs}s,共 ${events.length} 个事件,时间为相对录制起点的偏移):`,
88
+ ];
89
+ events.slice(0, 120).forEach(e => parts.push(` ${rel(e.ts)} ${e.text}`));
90
+ if (events.length > 120) {
91
+ parts.push(` …(其余 ${events.length - 120} 个事件省略)`);
80
92
  }
81
93
  return parts.join('\n');
82
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comate-ui-inspector",
3
- "version": "0.1.5-alpha.1",
3
+ "version": "0.1.5-alpha.2",
4
4
  "description": "按住 Alt 点选页面元素,把「元素 + Vue 组件源码路径」交给 Comate 定位源码修改。浏览器扩展 + Native Messaging host + MCP + hook。",
5
5
  "type": "module",
6
6
  "license": "MIT",