page-action-cache 1.0.3 → 2.0.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.
- package/README.md +399 -0
- package/dist/browser-action-executor.d.ts +87 -0
- package/dist/browser-action-executor.d.ts.map +1 -0
- package/dist/browser-action-executor.js +283 -0
- package/dist/browser-action-executor.js.map +1 -0
- package/dist/cache-invalidation.d.ts +128 -0
- package/dist/cache-invalidation.d.ts.map +1 -0
- package/dist/cache-invalidation.js +262 -0
- package/dist/cache-invalidation.js.map +1 -0
- package/dist/cache-manager.d.ts +83 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +184 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/index.d.ts +7 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +249 -31
- package/dist/index.js.map +1 -1
- package/dist/multi-level-cache.d.ts +127 -0
- package/dist/multi-level-cache.d.ts.map +1 -0
- package/dist/multi-level-cache.js +362 -0
- package/dist/multi-level-cache.js.map +1 -0
- package/dist/scenario-recognizer.d.ts +17 -27
- package/dist/scenario-recognizer.d.ts.map +1 -1
- package/dist/scenario-recognizer.js +63 -183
- package/dist/scenario-recognizer.js.map +1 -1
- package/dist/types.d.ts +38 -312
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -4
- package/dist/types.js.map +1 -1
- package/dist/variable-extractor.d.ts +56 -0
- package/dist/variable-extractor.d.ts.map +1 -0
- package/dist/variable-extractor.js +159 -0
- package/dist/variable-extractor.js.map +1 -0
- package/openclaw.plugin.json +11 -189
- package/package.json +28 -44
- package/src/browser-action-executor.ts +337 -0
- package/src/cache-invalidation.ts +338 -0
- package/src/cache-manager.ts +211 -0
- package/src/index.ts +306 -0
- package/src/multi-level-cache.ts +478 -0
- package/src/scenario-recognizer.ts +121 -0
- package/src/types-mock.d.ts +18 -0
- package/src/types.ts +66 -0
- package/src/variable-extractor.ts +204 -0
- package/dist/actions-executor.d.ts +0 -62
- package/dist/actions-executor.d.ts.map +0 -1
- package/dist/actions-executor.js +0 -339
- package/dist/actions-executor.js.map +0 -1
- package/dist/cache-invalidator.d.ts +0 -70
- package/dist/cache-invalidator.d.ts.map +0 -1
- package/dist/cache-invalidator.js +0 -212
- package/dist/cache-invalidator.js.map +0 -1
- package/dist/cache-store.d.ts +0 -80
- package/dist/cache-store.d.ts.map +0 -1
- package/dist/cache-store.js +0 -361
- package/dist/cache-store.js.map +0 -1
- package/dist/cache-strategy.d.ts +0 -65
- package/dist/cache-strategy.d.ts.map +0 -1
- package/dist/cache-strategy.js +0 -237
- package/dist/cache-strategy.js.map +0 -1
- package/dist/hooks-entry.d.ts +0 -18
- package/dist/hooks-entry.d.ts.map +0 -1
- package/dist/hooks-entry.js +0 -27
- package/dist/hooks-entry.js.map +0 -1
- package/dist/hooks.d.ts +0 -10
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -277
- package/dist/hooks.js.map +0 -1
- package/dist/security-policy.d.ts +0 -62
- package/dist/security-policy.d.ts.map +0 -1
- package/dist/security-policy.js +0 -219
- package/dist/security-policy.js.map +0 -1
- package/dist/tools.d.ts +0 -209
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -383
- package/dist/tools.js.map +0 -1
- package/dist/ux-enhancer.d.ts +0 -60
- package/dist/ux-enhancer.d.ts.map +0 -1
- package/dist/ux-enhancer.js +0 -218
- package/dist/ux-enhancer.js.map +0 -1
- package/dist/variable-resolver.d.ts +0 -28
- package/dist/variable-resolver.d.ts.map +0 -1
- package/dist/variable-resolver.js +0 -201
- package/dist/variable-resolver.js.map +0 -1
- package/docs/API.md +0 -555
- package/docs/IMPLEMENTATION.md +0 -1792
- package/docs/INTEGRATION.md +0 -387
- package/docs/README.md +0 -183
- package/skills/page-action-cache/SKILL.md +0 -216
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Action Executor - Page Action Cache
|
|
3
|
+
* 浏览器动作执行器 - 执行缓存的页面操作
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 浏览器动作执行器
|
|
7
|
+
*/
|
|
8
|
+
export class BrowserActionExecutor {
|
|
9
|
+
browserClient = null;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.initializeBrowserClient();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 初始化浏览器客户端
|
|
15
|
+
*/
|
|
16
|
+
initializeBrowserClient() {
|
|
17
|
+
try {
|
|
18
|
+
// 尝试连接到 OpenClaw Browser Client
|
|
19
|
+
// 这里使用 HTTP API 调用
|
|
20
|
+
const baseURL = process.env.OPENCLAW_BROWSER_CLIENT_URL || 'http://localhost:3000';
|
|
21
|
+
this.browserClient = {
|
|
22
|
+
navigate: async (url) => {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(`${baseURL}/api/browser/navigate`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
body: JSON.stringify({ url })
|
|
28
|
+
});
|
|
29
|
+
const result = await response.json();
|
|
30
|
+
return {
|
|
31
|
+
success: result.success || false,
|
|
32
|
+
message: result.message || 'Navigation completed'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
message: `Navigation failed: ${error.message}`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
click: async (selector) => {
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(`${baseURL}/api/browser/click`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ selector })
|
|
48
|
+
});
|
|
49
|
+
const result = await response.json();
|
|
50
|
+
return {
|
|
51
|
+
success: result.success || false,
|
|
52
|
+
message: result.message || 'Click completed'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
message: `Click failed: ${error.message}`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
'type': async (selector, text) => {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`${baseURL}/api/browser/type`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ selector, text })
|
|
68
|
+
});
|
|
69
|
+
const result = await response.json();
|
|
70
|
+
return {
|
|
71
|
+
success: result.success || false,
|
|
72
|
+
message: result.message || 'Type completed'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
message: `Type failed: ${error.message}`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
screenshot: async (path) => {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(`${baseURL}/api/browser/screenshot`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ path })
|
|
88
|
+
});
|
|
89
|
+
const result = await response.json();
|
|
90
|
+
return {
|
|
91
|
+
success: result.success || false,
|
|
92
|
+
message: result.message || 'Screenshot saved'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
message: `Screenshot failed: ${error.message}`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
executeScript: async (script) => {
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${baseURL}/api/browser/execute-script`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ script })
|
|
108
|
+
});
|
|
109
|
+
const result = await response.json();
|
|
110
|
+
return {
|
|
111
|
+
success: result.success || false,
|
|
112
|
+
message: result.message || 'Script executed',
|
|
113
|
+
result: result.result
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
message: `Script execution failed: ${error.message}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
console.log('[BrowserActionExecutor] Browser client initialized');
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.warn('[BrowserActionExecutor] Failed to initialize browser client:', error);
|
|
128
|
+
this.browserClient = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 执行单个动作
|
|
133
|
+
*/
|
|
134
|
+
async executeAction(action) {
|
|
135
|
+
if (!this.browserClient) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
message: 'Browser client not available',
|
|
139
|
+
savedTime: 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const startTime = Date.now();
|
|
143
|
+
try {
|
|
144
|
+
let result;
|
|
145
|
+
switch (action.type) {
|
|
146
|
+
case 'navigate':
|
|
147
|
+
if (!action.params?.url) {
|
|
148
|
+
return { success: false, message: 'Missing URL parameter', savedTime: 0 };
|
|
149
|
+
}
|
|
150
|
+
result = await this.browserClient.navigate(action.params.url);
|
|
151
|
+
break;
|
|
152
|
+
case 'click':
|
|
153
|
+
if (!action.params?.selector) {
|
|
154
|
+
return { success: false, message: 'Missing selector parameter', savedTime: 0 };
|
|
155
|
+
}
|
|
156
|
+
result = await this.browserClient.click(action.params.selector);
|
|
157
|
+
break;
|
|
158
|
+
case 'type':
|
|
159
|
+
if (!action.params?.selector || action.params?.text === undefined) {
|
|
160
|
+
return { success: false, message: 'Missing selector or text parameter', savedTime: 0 };
|
|
161
|
+
}
|
|
162
|
+
result = await this.browserClient['type'](action.params.selector, action.params.text);
|
|
163
|
+
break;
|
|
164
|
+
case 'screenshot':
|
|
165
|
+
result = await this.browserClient.screenshot(action.params?.path);
|
|
166
|
+
break;
|
|
167
|
+
case 'script':
|
|
168
|
+
if (!action.params?.script) {
|
|
169
|
+
return { success: false, message: 'Missing script parameter', savedTime: 0 };
|
|
170
|
+
}
|
|
171
|
+
result = await this.browserClient.executeScript(action.params.script);
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
message: `Unknown action type: ${action.type}`,
|
|
177
|
+
savedTime: 0
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const executionTime = Date.now() - startTime;
|
|
181
|
+
// 假设每个动作平均可以节省 2 秒
|
|
182
|
+
const estimatedTimeSaved = 2000;
|
|
183
|
+
const savedTime = result.success ? Math.max(0, estimatedTimeSaved - executionTime) : 0;
|
|
184
|
+
return {
|
|
185
|
+
success: result.success,
|
|
186
|
+
message: result.message,
|
|
187
|
+
savedTime
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
message: `Action execution error: ${error.message}`,
|
|
194
|
+
savedTime: 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 执行缓存条目中的所有动作
|
|
200
|
+
*/
|
|
201
|
+
async executeCacheEntry(entry) {
|
|
202
|
+
if (!entry.actions || entry.actions.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
success: true,
|
|
205
|
+
message: 'No actions to execute',
|
|
206
|
+
executedActions: 0,
|
|
207
|
+
skippedActions: 0,
|
|
208
|
+
failedActions: 0,
|
|
209
|
+
savedTime: 0
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
let executedActions = 0;
|
|
213
|
+
let skippedActions = 0;
|
|
214
|
+
let failedActions = 0;
|
|
215
|
+
let totalSavedTime = 0;
|
|
216
|
+
let hasError = false;
|
|
217
|
+
let errorMessage = '';
|
|
218
|
+
for (let i = 0; i < entry.actions.length; i++) {
|
|
219
|
+
const action = entry.actions[i];
|
|
220
|
+
// 跳过失败次数过多的动作
|
|
221
|
+
const totalAttempts = action.successCount + action.failCount;
|
|
222
|
+
if (totalAttempts > 0 && action.failCount / totalAttempts > 0.7) {
|
|
223
|
+
skippedActions++;
|
|
224
|
+
console.log(`[BrowserActionExecutor] Skipping action ${i + 1} (high failure rate): ${action.type}`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const result = await this.executeAction(action);
|
|
228
|
+
if (result.success) {
|
|
229
|
+
executedActions++;
|
|
230
|
+
totalSavedTime += result.savedTime;
|
|
231
|
+
console.log(`[BrowserActionExecutor] Executed action ${i + 1}: ${action.type} (${result.message})`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
failedActions++;
|
|
235
|
+
hasError = true;
|
|
236
|
+
errorMessage = result.message;
|
|
237
|
+
console.error(`[BrowserActionExecutor] Failed action ${i + 1}: ${action.type} (${result.message})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
success: !hasError || failedActions < entry.actions.length / 2, // 允许部分失败
|
|
242
|
+
message: hasError ? `Execution completed with ${failedActions} failures` : 'Execution completed successfully',
|
|
243
|
+
executedActions,
|
|
244
|
+
skippedActions,
|
|
245
|
+
failedActions,
|
|
246
|
+
savedTime: totalSavedTime,
|
|
247
|
+
error: errorMessage
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 检查浏览器客户端是否可用
|
|
252
|
+
*/
|
|
253
|
+
isAvailable() {
|
|
254
|
+
return this.browserClient !== null;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 设置浏览器客户端(用于测试或自定义实现)
|
|
258
|
+
*/
|
|
259
|
+
setBrowserClient(client) {
|
|
260
|
+
this.browserClient = client;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* 估算执行时间
|
|
264
|
+
*/
|
|
265
|
+
estimateExecutionTime(actions) {
|
|
266
|
+
// 估算每个动作的执行时间(毫秒)
|
|
267
|
+
const timeEstimates = {
|
|
268
|
+
navigate: 3000, // 导航通常需要 3 秒
|
|
269
|
+
click: 500, // 点击通常需要 0.5 秒
|
|
270
|
+
type: 1000, // 输入通常需要 1 秒
|
|
271
|
+
screenshot: 1500, // 截图通常需要 1.5 秒
|
|
272
|
+
script: 500 // 脚本执行通常需要 0.5 秒
|
|
273
|
+
};
|
|
274
|
+
return actions.reduce((total, action) => {
|
|
275
|
+
return total + (timeEstimates[action.type] || 500);
|
|
276
|
+
}, 0);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 单例导出
|
|
281
|
+
*/
|
|
282
|
+
export const browserActionExecutor = new BrowserActionExecutor();
|
|
283
|
+
//# sourceMappingURL=browser-action-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-action-executor.js","sourceRoot":"","sources":["../src/browser-action-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Invalidation - Page Action Cache
|
|
3
|
+
* 缓存失效管理器 - 页面变化检测和缓存失效
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 页面变化类型
|
|
7
|
+
*/
|
|
8
|
+
type ChangeType = 'major' | 'minor' | 'none';
|
|
9
|
+
/**
|
|
10
|
+
* 页面快照
|
|
11
|
+
*/
|
|
12
|
+
interface PageSnapshot {
|
|
13
|
+
url: string;
|
|
14
|
+
viewport: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
htmlHash?: string;
|
|
17
|
+
structureHash?: string;
|
|
18
|
+
contentHash?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 缓存失效策略
|
|
22
|
+
*/
|
|
23
|
+
type InvalidationStrategy = 'soft' | 'hard';
|
|
24
|
+
/**
|
|
25
|
+
* 页面变化检测结果
|
|
26
|
+
*/
|
|
27
|
+
interface ChangeDetectionResult {
|
|
28
|
+
changed: boolean;
|
|
29
|
+
changeType: ChangeType;
|
|
30
|
+
changeScore: number;
|
|
31
|
+
confidence: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 缓存失效管理器
|
|
35
|
+
*/
|
|
36
|
+
export declare class CacheInvalidation {
|
|
37
|
+
private snapshots;
|
|
38
|
+
private changeHistory;
|
|
39
|
+
private strategy;
|
|
40
|
+
private changeThreshold;
|
|
41
|
+
constructor(strategy?: InvalidationStrategy, changeThreshold?: number);
|
|
42
|
+
/**
|
|
43
|
+
* 设置失效策略
|
|
44
|
+
*/
|
|
45
|
+
setStrategy(strategy: InvalidationStrategy): void;
|
|
46
|
+
/**
|
|
47
|
+
* 设置变化阈值
|
|
48
|
+
*/
|
|
49
|
+
setChangeThreshold(threshold: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* 创建页面快照
|
|
52
|
+
*/
|
|
53
|
+
createSnapshot(url: string, viewport: string, html?: string): PageSnapshot;
|
|
54
|
+
/**
|
|
55
|
+
* 保存页面快照
|
|
56
|
+
*/
|
|
57
|
+
saveSnapshot(url: string, viewport: string, html?: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* 检测页面变化
|
|
60
|
+
*/
|
|
61
|
+
detectChanges(url: string, viewport: string, currentHtml: string): ChangeDetectionResult;
|
|
62
|
+
/**
|
|
63
|
+
* 计算变化分数
|
|
64
|
+
*/
|
|
65
|
+
private calculateChangeScore;
|
|
66
|
+
/**
|
|
67
|
+
* 确定变化类型
|
|
68
|
+
*/
|
|
69
|
+
private determineChangeType;
|
|
70
|
+
/**
|
|
71
|
+
* 计算置信度
|
|
72
|
+
*/
|
|
73
|
+
private calculateConfidence;
|
|
74
|
+
/**
|
|
75
|
+
* 记录变化历史
|
|
76
|
+
*/
|
|
77
|
+
private recordChangeHistory;
|
|
78
|
+
/**
|
|
79
|
+
* 判断是否应该失效缓存
|
|
80
|
+
*/
|
|
81
|
+
shouldInvalidate(url: string, viewport: string, currentHtml: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 失效缓存条目
|
|
84
|
+
*/
|
|
85
|
+
invalidate(url: string, viewport: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* 批量失效 URL 相关的所有缓存
|
|
88
|
+
*/
|
|
89
|
+
invalidateByUrl(url: string): void;
|
|
90
|
+
/**
|
|
91
|
+
* 获取失效统计
|
|
92
|
+
*/
|
|
93
|
+
getInvalidationStats(): {
|
|
94
|
+
totalSnapshots: number;
|
|
95
|
+
activeSnapshots: number;
|
|
96
|
+
invalidationRate: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* 清理过期快照
|
|
100
|
+
*/
|
|
101
|
+
cleanup(maxAge?: number): void;
|
|
102
|
+
/**
|
|
103
|
+
* 生成哈希
|
|
104
|
+
*/
|
|
105
|
+
private generateHash;
|
|
106
|
+
/**
|
|
107
|
+
* 生成结构哈希
|
|
108
|
+
*/
|
|
109
|
+
private generateStructureHash;
|
|
110
|
+
/**
|
|
111
|
+
* 生成内容哈希
|
|
112
|
+
*/
|
|
113
|
+
private generateContentHash;
|
|
114
|
+
/**
|
|
115
|
+
* 生成快照键
|
|
116
|
+
*/
|
|
117
|
+
private getSnapshotKey;
|
|
118
|
+
/**
|
|
119
|
+
* 重置
|
|
120
|
+
*/
|
|
121
|
+
reset(): void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 单例导出
|
|
125
|
+
*/
|
|
126
|
+
export declare const cacheInvalidation: CacheInvalidation;
|
|
127
|
+
export {};
|
|
128
|
+
//# sourceMappingURL=cache-invalidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../src/cache-invalidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C;;GAEG;AACH,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,KAAK,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C;;GAEG;AACH,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,eAAe,CAAe;gBAE1B,QAAQ,GAAE,oBAA6B,EAAE,eAAe,GAAE,MAAY;IAKlF;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAIjD;;OAEG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI3C;;OAEG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY;IAgB1E;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAMhE;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,qBAAqB;IA4BxF;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA4B5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAM3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IA0B7E;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM/C;;OAEG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IASlC;;OAEG;IACH,oBAAoB,IAAI;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IAiBD;;OAEG;IACH,OAAO,CAAC,MAAM,GAAE,MAAgC,GAAG,IAAI;IAkBvD;;OAEG;IACH,OAAO,CAAC,YAAY;IAUpB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAM3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,KAAK,IAAI,IAAI;CAId;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Invalidation - Page Action Cache
|
|
3
|
+
* 缓存失效管理器 - 页面变化检测和缓存失效
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 缓存失效管理器
|
|
7
|
+
*/
|
|
8
|
+
export class CacheInvalidation {
|
|
9
|
+
snapshots = new Map();
|
|
10
|
+
changeHistory = new Map();
|
|
11
|
+
strategy = 'soft';
|
|
12
|
+
changeThreshold = 0.5;
|
|
13
|
+
constructor(strategy = 'soft', changeThreshold = 0.5) {
|
|
14
|
+
this.strategy = strategy;
|
|
15
|
+
this.changeThreshold = changeThreshold;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 设置失效策略
|
|
19
|
+
*/
|
|
20
|
+
setStrategy(strategy) {
|
|
21
|
+
this.strategy = strategy;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 设置变化阈值
|
|
25
|
+
*/
|
|
26
|
+
setChangeThreshold(threshold) {
|
|
27
|
+
this.changeThreshold = Math.max(0, Math.min(1, threshold));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 创建页面快照
|
|
31
|
+
*/
|
|
32
|
+
createSnapshot(url, viewport, html) {
|
|
33
|
+
const snapshot = {
|
|
34
|
+
url,
|
|
35
|
+
viewport,
|
|
36
|
+
timestamp: Date.now()
|
|
37
|
+
};
|
|
38
|
+
if (html) {
|
|
39
|
+
snapshot.htmlHash = this.generateHash(html);
|
|
40
|
+
snapshot.structureHash = this.generateStructureHash(html);
|
|
41
|
+
snapshot.contentHash = this.generateContentHash(html);
|
|
42
|
+
}
|
|
43
|
+
return snapshot;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 保存页面快照
|
|
47
|
+
*/
|
|
48
|
+
saveSnapshot(url, viewport, html) {
|
|
49
|
+
const key = this.getSnapshotKey(url, viewport);
|
|
50
|
+
const snapshot = this.createSnapshot(url, viewport, html);
|
|
51
|
+
this.snapshots.set(key, snapshot);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 检测页面变化
|
|
55
|
+
*/
|
|
56
|
+
detectChanges(url, viewport, currentHtml) {
|
|
57
|
+
const key = this.getSnapshotKey(url, viewport);
|
|
58
|
+
const previousSnapshot = this.snapshots.get(key);
|
|
59
|
+
if (!previousSnapshot) {
|
|
60
|
+
return {
|
|
61
|
+
changed: true,
|
|
62
|
+
changeType: 'major',
|
|
63
|
+
changeScore: 1.0,
|
|
64
|
+
confidence: 0.8
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const currentSnapshot = this.createSnapshot(url, viewport, currentHtml);
|
|
68
|
+
const changeScore = this.calculateChangeScore(previousSnapshot, currentSnapshot);
|
|
69
|
+
const changeType = this.determineChangeType(changeScore);
|
|
70
|
+
// 记录变化历史
|
|
71
|
+
this.recordChangeHistory(key, changeScore);
|
|
72
|
+
return {
|
|
73
|
+
changed: changeScore > this.changeThreshold,
|
|
74
|
+
changeType,
|
|
75
|
+
changeScore,
|
|
76
|
+
confidence: this.calculateConfidence(previousSnapshot, currentSnapshot)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 计算变化分数
|
|
81
|
+
*/
|
|
82
|
+
calculateChangeScore(previous, current) {
|
|
83
|
+
let totalScore = 0;
|
|
84
|
+
let weightSum = 0;
|
|
85
|
+
// HTML 变化 (权重 0.4)
|
|
86
|
+
if (previous.htmlHash && current.htmlHash) {
|
|
87
|
+
const htmlChanged = previous.htmlHash !== current.htmlHash ? 1 : 0;
|
|
88
|
+
totalScore += htmlChanged * 0.4;
|
|
89
|
+
weightSum += 0.4;
|
|
90
|
+
}
|
|
91
|
+
// 结构变化 (权重 0.35)
|
|
92
|
+
if (previous.structureHash && current.structureHash) {
|
|
93
|
+
const structureChanged = previous.structureHash !== current.structureHash ? 1 : 0;
|
|
94
|
+
totalScore += structureChanged * 0.35;
|
|
95
|
+
weightSum += 0.35;
|
|
96
|
+
}
|
|
97
|
+
// 内容变化 (权重 0.25)
|
|
98
|
+
if (previous.contentHash && current.contentHash) {
|
|
99
|
+
const contentChanged = previous.contentHash !== current.contentHash ? 1 : 0;
|
|
100
|
+
totalScore += contentChanged * 0.25;
|
|
101
|
+
weightSum += 0.25;
|
|
102
|
+
}
|
|
103
|
+
return weightSum > 0 ? totalScore / weightSum : 0;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 确定变化类型
|
|
107
|
+
*/
|
|
108
|
+
determineChangeType(changeScore) {
|
|
109
|
+
if (changeScore >= 0.7)
|
|
110
|
+
return 'major';
|
|
111
|
+
if (changeScore >= 0.3)
|
|
112
|
+
return 'minor';
|
|
113
|
+
return 'none';
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 计算置信度
|
|
117
|
+
*/
|
|
118
|
+
calculateConfidence(previous, current) {
|
|
119
|
+
// 时间越近,置信度越高
|
|
120
|
+
const timeDiff = current.timestamp - previous.timestamp;
|
|
121
|
+
const timeFactor = Math.max(0, 1 - timeDiff / (24 * 60 * 60 * 1000)); // 24小时内
|
|
122
|
+
return timeFactor * 0.8 + 0.2; // 基础置信度 0.2
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 记录变化历史
|
|
126
|
+
*/
|
|
127
|
+
recordChangeHistory(key, changeScore) {
|
|
128
|
+
if (!this.changeHistory.has(key)) {
|
|
129
|
+
this.changeHistory.set(key, []);
|
|
130
|
+
}
|
|
131
|
+
const history = this.changeHistory.get(key);
|
|
132
|
+
history.push(changeScore);
|
|
133
|
+
// 只保留最近 10 次记录
|
|
134
|
+
if (history.length > 10) {
|
|
135
|
+
history.shift();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 判断是否应该失效缓存
|
|
140
|
+
*/
|
|
141
|
+
shouldInvalidate(url, viewport, currentHtml) {
|
|
142
|
+
const detection = this.detectChanges(url, viewport, currentHtml);
|
|
143
|
+
if (this.strategy === 'hard') {
|
|
144
|
+
// 硬失效:任何变化都失效
|
|
145
|
+
return detection.changed;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// 软失效:根据变化类型和阈值判断
|
|
149
|
+
const key = this.getSnapshotKey(url, viewport);
|
|
150
|
+
const history = this.changeHistory.get(key) || [];
|
|
151
|
+
// 如果是重大变化,立即失效
|
|
152
|
+
if (detection.changeType === 'major') {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
// 如果连续 3 次检测到变化,失效
|
|
156
|
+
const recentChanges = history.slice(-3);
|
|
157
|
+
if (recentChanges.length === 3 && recentChanges.every(s => s > this.changeThreshold)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return detection.changed && detection.confidence > 0.7;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 失效缓存条目
|
|
165
|
+
*/
|
|
166
|
+
invalidate(url, viewport) {
|
|
167
|
+
const key = this.getSnapshotKey(url, viewport);
|
|
168
|
+
this.snapshots.delete(key);
|
|
169
|
+
this.changeHistory.delete(key);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 批量失效 URL 相关的所有缓存
|
|
173
|
+
*/
|
|
174
|
+
invalidateByUrl(url) {
|
|
175
|
+
for (const key of this.snapshots.keys()) {
|
|
176
|
+
if (key.startsWith(`${url}:`)) {
|
|
177
|
+
this.snapshots.delete(key);
|
|
178
|
+
this.changeHistory.delete(key);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 获取失效统计
|
|
184
|
+
*/
|
|
185
|
+
getInvalidationStats() {
|
|
186
|
+
const totalSnapshots = this.changeHistory.size;
|
|
187
|
+
let activeInvalidations = 0;
|
|
188
|
+
for (const history of this.changeHistory.values()) {
|
|
189
|
+
if (history.length > 0 && history[history.length - 1] > this.changeThreshold) {
|
|
190
|
+
activeInvalidations++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
totalSnapshots,
|
|
195
|
+
activeSnapshots: this.snapshots.size,
|
|
196
|
+
invalidationRate: totalSnapshots > 0 ? activeInvalidations / totalSnapshots : 0
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 清理过期快照
|
|
201
|
+
*/
|
|
202
|
+
cleanup(maxAge = 7 * 24 * 60 * 60 * 1000) {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
const expiredKeys = [];
|
|
205
|
+
for (const [key, snapshot] of this.snapshots.entries()) {
|
|
206
|
+
if (now - snapshot.timestamp > maxAge) {
|
|
207
|
+
expiredKeys.push(key);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const key of expiredKeys) {
|
|
211
|
+
this.snapshots.delete(key);
|
|
212
|
+
this.changeHistory.delete(key);
|
|
213
|
+
}
|
|
214
|
+
console.log(`[CacheInvalidation] Cleaned up ${expiredKeys.length} expired snapshots`);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 生成哈希
|
|
218
|
+
*/
|
|
219
|
+
generateHash(str) {
|
|
220
|
+
let hash = 0;
|
|
221
|
+
for (let i = 0; i < str.length; i++) {
|
|
222
|
+
const char = str.charCodeAt(i);
|
|
223
|
+
hash = ((hash << 5) - hash) + char;
|
|
224
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
225
|
+
}
|
|
226
|
+
return Math.abs(hash).toString(16);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 生成结构哈希
|
|
230
|
+
*/
|
|
231
|
+
generateStructureHash(html) {
|
|
232
|
+
// 移除文本内容,只保留标签结构
|
|
233
|
+
const structure = html.replace(/>[^<]*</g, '><').replace(/\s+/g, '');
|
|
234
|
+
return this.generateHash(structure);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 生成内容哈希
|
|
238
|
+
*/
|
|
239
|
+
generateContentHash(html) {
|
|
240
|
+
// 移除 HTML 标签,只保留文本内容
|
|
241
|
+
const content = html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
|
242
|
+
return this.generateHash(content);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 生成快照键
|
|
246
|
+
*/
|
|
247
|
+
getSnapshotKey(url, viewport) {
|
|
248
|
+
return `${url}:${viewport}`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 重置
|
|
252
|
+
*/
|
|
253
|
+
reset() {
|
|
254
|
+
this.snapshots.clear();
|
|
255
|
+
this.changeHistory.clear();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 单例导出
|
|
260
|
+
*/
|
|
261
|
+
export const cacheInvalidation = new CacheInvalidation();
|
|
262
|
+
//# sourceMappingURL=cache-invalidation.js.map
|