lybic-guiagents 0.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,493 @@
1
+ import logging
2
+ import os
3
+ import re
4
+ import shutil
5
+ import io
6
+ from itertools import product
7
+ from typing import Any, Dict, List, Union
8
+
9
+ import rapidfuzz.fuzz as fuzz
10
+ from bs4 import BeautifulSoup, Tag
11
+
12
+ from desktop_env.evaluators.metrics.utils import are_lists_equal, compare_urls
13
+
14
+ logger = logging.getLogger("desktopenv.metrics.chrome")
15
+
16
+
17
+ def is_expected_active_tab(active_tab_info: Dict[str, str], rule: Dict[str, Any]) -> float:
18
+ """
19
+ Checks if the expected active tab is open in Chrome.
20
+ """
21
+ if not active_tab_info:
22
+ return 0.
23
+
24
+ match_type = rule['type']
25
+
26
+ if match_type == "url":
27
+ expected_url = rule['url']
28
+ if isinstance(active_tab_info, Dict):
29
+ actual_url = active_tab_info.get('url', None)
30
+ else:
31
+ actual_url = active_tab_info
32
+ logger.info("expected_url: {}".format(expected_url))
33
+ logger.info("actual_url: {}".format(actual_url))
34
+ return 1 if compare_urls(expected_url, actual_url) else 0
35
+ else:
36
+ logger.error(f"Unknown type: {match_type}")
37
+ return 0
38
+
39
+
40
+ def is_expected_active_tab_approximate(active_tab_info: Dict[str, str], rule: Dict[str, Any]) -> float:
41
+ """
42
+ Checks if the expected active tab is open in Chrome, ignoring query parameters in the URL.
43
+ """
44
+ if not active_tab_info:
45
+ return 0.
46
+
47
+ match_type = rule['type']
48
+
49
+ if match_type == "url":
50
+ expected_url = rule['url']
51
+ if isinstance(active_tab_info, Dict):
52
+ actual_url = active_tab_info.get('url', None)
53
+ else:
54
+ actual_url = active_tab_info
55
+ from urllib.parse import urlparse, urlunparse
56
+ def strip_query(url):
57
+ parsed = urlparse(url)
58
+ return urlunparse(parsed._replace(query=""))
59
+ if strip_query(expected_url) == strip_query(actual_url):
60
+ return 1
61
+ else:
62
+ return 0
63
+ else:
64
+ logger.error(f"Unknown type: {match_type}")
65
+ return 0
66
+
67
+
68
+ # rules[expected] is a string-formatted regex
69
+ def is_expected_url_pattern_match(result, rules) -> float:
70
+ """
71
+ This function is used to search the expected pattern in the url using regex.
72
+ result is the return value of function "activte_tab_info" or return value of function "get_active_url_from_accessTree"
73
+ """
74
+ if not result:
75
+ return 0.
76
+
77
+ if type(result) == dict:
78
+ result_url = result["url"]
79
+ logger.info("result url: {}".format(result_url))
80
+ else:
81
+ result_url = result
82
+ # expect_regex = re.compile(rules["expected"])
83
+ patterns = rules["expected"]
84
+ logger.info("expected_regex: {}".format(patterns))
85
+ for pattern in patterns:
86
+ match = re.search(pattern, result_url)
87
+ logger.info("match: {}".format(match))
88
+ if not match:
89
+ return 0.
90
+ return 1.
91
+
92
+
93
+ def is_expected_installed_extensions(installed_extensions, expected) -> float:
94
+ if not installed_extensions:
95
+ return 0.
96
+
97
+ logger.info("installed_extensions: ")
98
+ logger.info(installed_extensions)
99
+ expected_extensions = expected["expected"]
100
+
101
+ # whether the expected extensions are installed
102
+ set_expected_extensions = set(expected_extensions)
103
+ set_installed_extensions = set(installed_extensions)
104
+
105
+ if set_expected_extensions.issubset(set_installed_extensions):
106
+ return 1.
107
+ else:
108
+ return 0.
109
+
110
+
111
+ def is_expected_tabs(open_tabs: List[Dict[str, str]], rule: Dict[str, Any]) -> float:
112
+ """
113
+ Checks if the expected tabs are open in Chrome.
114
+ """
115
+ if not open_tabs:
116
+ return 0.
117
+
118
+ match_type = rule['type']
119
+
120
+ if match_type == "url":
121
+ expected_urls = rule['urls']
122
+ actual_urls = [tab['url'] for tab in open_tabs]
123
+ if not are_lists_equal(expected_urls, actual_urls, compare_urls):
124
+ logger.error("list not match")
125
+ logger.error(expected_urls)
126
+ logger.error(actual_urls)
127
+ return 0
128
+ return 1 if are_lists_equal(expected_urls, actual_urls, compare_urls) else 0
129
+ else:
130
+ logger.error(f"Unknown type: {match_type}")
131
+ return 0
132
+
133
+
134
+ def is_expected_bookmarks(bookmarks: List[str], rule: Dict[str, Any]) -> float:
135
+ """
136
+ Checks if the expected bookmarks are in Chrome.
137
+ """
138
+ if not bookmarks:
139
+ return 0.
140
+ elif rule['type'] == "bookmark_bar_folders_names":
141
+ bookmark_bar_folders_names = [bookmark['name'] for bookmark in bookmarks['bookmark_bar']['children'] if
142
+ bookmark['type'] == 'folder']
143
+ return 1. if set(bookmark_bar_folders_names) == set(rule['names']) else 0.
144
+ elif rule['type'] == "bookmark_bar_websites_urls":
145
+ bookmark_bar_websites_urls = [bookmark['url'] for bookmark in bookmarks['bookmark_bar']['children'] if
146
+ bookmark['type'] == 'url']
147
+ return 1. if set(bookmark_bar_websites_urls) == set(rule['urls']) else 0.
148
+ elif rule['type'] == "liked_authors_websites_urls":
149
+ # Check if "liked authors" folder exists
150
+ liked_authors_folder = next((bookmark for bookmark in bookmarks['bookmark_bar']['children'] if
151
+ bookmark['type'] == 'folder' and bookmark['name'] == 'Liked Authors'), None)
152
+ if liked_authors_folder:
153
+ # Check if it contains the specified URLs
154
+ logger.info("'Liked Authors' folder exists")
155
+ liked_authors_urls = [bookmark['url'] for bookmark in liked_authors_folder['children'] if
156
+ bookmark['type'] == 'url']
157
+ logger.info("Here is the 'Liked Authors' folder's urls: {}".format(liked_authors_urls))
158
+
159
+ urls = rule['urls']
160
+
161
+ for idx, url in enumerate(urls):
162
+ if isinstance(url, str):
163
+ urls[idx] = [url]
164
+
165
+ combinations = product(*urls)
166
+
167
+ for combination in combinations:
168
+ if set(combination) == set(liked_authors_urls):
169
+ return 1.
170
+ return 0.
171
+ else:
172
+ return 0.
173
+ else:
174
+ raise TypeError(f"{rule['type']} not support yet!")
175
+
176
+
177
+ def is_expected_search_query(active_tab_info: Dict[str, str], rules: Dict[str, Any]) -> float:
178
+ if not active_tab_info:
179
+ return 0.
180
+
181
+ expected = rules['expect']
182
+ pattern = expected['pattern']
183
+ matched = re.search(pattern, active_tab_info['url'])
184
+ if matched:
185
+ return 1.
186
+ return 0.
187
+
188
+
189
+ def compare_pdfs(pdf1_path: Union[str, List[str]], pdf2_path: Union[str, List[str]]):
190
+ """
191
+ Compare two PDF files.
192
+ """
193
+ if type(pdf2_path) != list:
194
+ pdf1_path, pdf2_path = [pdf1_path], [pdf2_path]
195
+
196
+ def extract_text_from_pdf(pdf_path):
197
+ """Extract text from each page of the PDF."""
198
+ text = ""
199
+ with fitz.open(pdf_path) as pdf:
200
+ for page in pdf:
201
+ text += page.get_text()
202
+ return text.strip()
203
+
204
+ score = 0.
205
+ for path1, path2 in zip(pdf1_path, pdf2_path):
206
+ try:
207
+ text1 = extract_text_from_pdf(path1)
208
+ text2 = extract_text_from_pdf(path2)
209
+ score += fuzz.ratio(text1, text2) / 100
210
+ except Exception as e:
211
+ logger.info(f"[ERROR]: unexpected error occurred when comparing PDF files: {e}")
212
+ return score / len(pdf2_path)
213
+
214
+
215
+ import fitz
216
+ from PIL import Image
217
+ from borb.pdf import Document
218
+ from borb.pdf import PDF
219
+ import imagehash
220
+
221
+ from pathlib import Path
222
+ import typing
223
+
224
+
225
+ def compare_pdf_images(pdf1_path: str, pdf2_path: str, **kwargs) -> float:
226
+ if not pdf1_path or not pdf2_path:
227
+ return 0.
228
+ if not all(map(os.path.exists, [pdf1_path, pdf2_path])):
229
+ logger.warning(f"PDF file does not exist: {pdf1_path} or {pdf2_path}")
230
+ return 0.
231
+
232
+ def extract_images_from_pdf(pdf_path):
233
+ pdf_document = fitz.open(pdf_path)
234
+ images = []
235
+
236
+ for page_number in range(pdf_document.page_count):
237
+ page = pdf_document[page_number]
238
+ for img_index, img in enumerate(page.get_images(full=True)):
239
+ xref = img[0]
240
+ base_image = pdf_document.extract_image(xref)
241
+ image_bytes = base_image["image"]
242
+
243
+ # convert to PIL Image
244
+ try:
245
+ pil_image = Image.open(io.BytesIO(image_bytes))
246
+ images.append(pil_image)
247
+ except Exception as e:
248
+ logger.error(f"Failed to process image in {pdf_path} on page {page_number}: {e}")
249
+
250
+ return images
251
+
252
+ temp_dir = Path(pdf1_path).parent / "temp_pdf_comparison"
253
+ os.makedirs(temp_dir, exist_ok=True)
254
+
255
+ temp_pdf1 = temp_dir / Path(pdf1_path).name
256
+ temp_pdf2 = temp_dir / Path(pdf2_path).name
257
+
258
+ shutil.copy(pdf1_path, temp_pdf1)
259
+ shutil.copy(pdf2_path, temp_pdf2)
260
+
261
+ try:
262
+ images1 = extract_images_from_pdf(str(temp_pdf1))
263
+ images2 = extract_images_from_pdf(str(temp_pdf2))
264
+ except Exception as e:
265
+ logger.error(f"Error extracting images from PDFs: {e}")
266
+ shutil.rmtree(temp_dir)
267
+ return 0.
268
+ finally:
269
+ shutil.rmtree(temp_dir)
270
+
271
+
272
+ if len(images1) != len(images2):
273
+ logger.info(f"Different number of images found. Gold: {len(images1)}, Pred: {len(images2)}")
274
+ return 0.
275
+
276
+ if not images1:
277
+ logger.info("No images found in either PDF. Considering it a match.")
278
+ return 1.0
279
+
280
+ hash_threshold = 5
281
+ total_score = 0
282
+ for i, (img1, img2) in enumerate(zip(images1, images2)):
283
+ hash1 = imagehash.phash(img1)
284
+ hash2 = imagehash.phash(img2)
285
+ hash_diff = hash1 - hash2
286
+
287
+ logger.info(f"Image {i+1}: Gold hash: {hash1}, Pred hash: {hash2}, Hash difference: {hash_diff}")
288
+
289
+ if hash_diff <= hash_threshold:
290
+ total_score +=1
291
+
292
+ return total_score / len(images1)
293
+
294
+
295
+ def compare_archive(pred_path: str, gold_path: str, **kwargs) -> float:
296
+ """
297
+ Compare two archives. Note that the files in the archives should be of the same type.
298
+ """
299
+ file_path = kwargs.pop('file_path', '')
300
+
301
+ if not pred_path:
302
+ return 0.
303
+ pred_folder = os.path.splitext(pred_path)[0] + '_pred'
304
+ gold_folder = os.path.splitext(gold_path)[0] + '_gold'
305
+
306
+ if os.path.exists(pred_folder): # remove existing folder for new predictions
307
+ shutil.rmtree(pred_folder, ignore_errors=True)
308
+ os.makedirs(pred_folder)
309
+ shutil.unpack_archive(pred_path, pred_folder)
310
+
311
+ if not os.path.exists(gold_folder): # use cache if exists
312
+ os.makedirs(gold_folder)
313
+ shutil.unpack_archive(gold_path, gold_folder)
314
+
315
+ pred_files = sorted(os.listdir(os.path.join(pred_folder, file_path)))
316
+ gold_files = sorted(os.listdir(os.path.join(gold_folder, file_path)))
317
+
318
+ if pred_files != gold_files:
319
+ return 0.
320
+
321
+ def get_compare_function():
322
+ file_type = kwargs.pop('file_type', 'text')
323
+ if file_type == 'text':
324
+ from .vscode import compare_text_file
325
+ return compare_text_file
326
+ elif file_type == 'pdf':
327
+ return compare_pdfs
328
+ elif file_type == 'docx':
329
+ from .docs import compare_docx_files
330
+ return compare_docx_files
331
+ elif file_type == 'ppt':
332
+ from .slides import compare_pptx_files
333
+ return compare_pptx_files
334
+ elif file_type == 'image':
335
+ from .vlc import compare_images
336
+ return compare_images
337
+ elif file_type == 'csv':
338
+ from .table import compare_csv
339
+ return compare_csv
340
+ elif file_type == 'table':
341
+ from .table import compare_table
342
+ return compare_table
343
+ elif file_type == 'audio':
344
+ from .vlc import compare_audios
345
+ return compare_audios
346
+ elif file_type == 'video':
347
+ from .vlc import compare_videos
348
+ return compare_videos
349
+ else:
350
+ raise ValueError('[ERROR]: not support file type: %s' % file_type)
351
+
352
+ score = 0
353
+ compare_function = get_compare_function()
354
+ for f1, f2 in zip(pred_files, gold_files):
355
+ fp1 = os.path.join(pred_folder, file_path, f1)
356
+ fp2 = os.path.join(gold_folder, file_path, f2)
357
+ score += compare_function(fp1, fp2, **kwargs)
358
+ return score / len(pred_files)
359
+
360
+
361
+ def compare_htmls(html_path1: str, html_path2: str, **options) -> float:
362
+ """
363
+ Compare two HTML files.
364
+ """
365
+ with open(html_path1, 'r', encoding='utf-8') as inf:
366
+ soup1 = BeautifulSoup(inf, 'lxml')
367
+ with open(html_path2, 'r', encoding='utf-8') as inf:
368
+ soup2 = BeautifulSoup(inf, 'lxml')
369
+ ignore_sdnum = options.get("ignore_sdnum", None)
370
+
371
+ def compare_elements(elem1, elem2):
372
+ if not (isinstance(elem1, Tag) and isinstance(elem2, Tag)):
373
+ if elem1 != elem2:
374
+ logger.info("not the same")
375
+ return elem1 == elem2
376
+ if elem1.name != elem2.name:
377
+ logger.info("html name not match")
378
+ return False
379
+ if elem1.text.strip() != elem2.text.strip():
380
+ logger.info("html text not match")
381
+ return False
382
+ if elem1.attrs != elem2.attrs:
383
+ if ignore_sdnum:
384
+ attrs1 = {k: v for k, v in elem1.attrs.items() if k != 'sdnum'}
385
+ attrs2 = {k: v for k, v in elem2.attrs.items() if k != 'sdnum'}
386
+ return attrs1 == attrs2
387
+ logger.info("html attrs not match")
388
+ logger.info(f"{elem1.attrs}")
389
+ logger.info(f"{elem2.attrs}")
390
+ return False
391
+ return True
392
+
393
+ for elem1, elem2 in zip(soup1.recursiveChildGenerator(), soup2.recursiveChildGenerator()):
394
+ if not compare_elements(elem1, elem2):
395
+ logger.info("html not match")
396
+ return .0
397
+ return 1.
398
+
399
+
400
+ def is_cookie_deleted(cookie_data, rule):
401
+ """
402
+ Check if the cookie is deleted.
403
+ """
404
+
405
+ if rule['type'] == 'domains':
406
+ cookies_domains = [cookie[1] for cookie in cookie_data]
407
+ for domain in rule['domains']:
408
+ for cookies_domain in cookies_domains:
409
+ if compare_urls(domain, cookies_domain):
410
+ return 0.
411
+ return 1.
412
+ else:
413
+ raise TypeError(f"{rule['type']} not support yet!")
414
+
415
+
416
+ def is_shortcut_on_desktop(shortcuts: Dict[str, str], rule):
417
+ """
418
+ Check if the shortcut is on the desktop.
419
+ """
420
+ # fixme: if the name of the website changed in the future, this will not work; can be replaced with url
421
+ if rule['type'] == 'name':
422
+ for shortcut_path, shortcut_content in shortcuts.items():
423
+ if "Name=" + rule['name'] + "\n" in shortcut_content:
424
+ return 1.
425
+ return 0.0
426
+ elif rule['type'] == 'exec':
427
+ for shortcut_path, shortcut_content in shortcuts.items():
428
+ if "Exec=" + rule['exec'] + "\n" in shortcut_content:
429
+ return 1.
430
+ return 0.0
431
+ elif rule['type'] == 'url':
432
+ raise TypeError(f"{rule['type']} not support yet!")
433
+ elif rule['type'] == 'id':
434
+ raise TypeError(f"{rule['type']} not support yet!")
435
+ else:
436
+ raise TypeError(f"{rule['type']} not support yet!")
437
+
438
+
439
+ def check_history_deleted(history_data, rule):
440
+ """
441
+ Check if the history is deleted.
442
+ """
443
+
444
+ if rule['type'] == 'keywords':
445
+ history_domains = [history[0] for history in history_data]
446
+ for keyword in rule['keywords']:
447
+ for history_domain in history_domains:
448
+ if keyword in history_domain:
449
+ return 0.
450
+ return 1.
451
+ else:
452
+ raise TypeError(f"{rule['type']} not support yet!")
453
+
454
+
455
+ def check_enabled_experiments(enabled_experiments, rule):
456
+ """
457
+ Check if the enabled experiments are as expected.
458
+ """
459
+ enabled_experiments_names = [experiment.split("@")[0] for experiment in enabled_experiments]
460
+
461
+ if rule['type'] == 'names':
462
+ return 1. if enabled_experiments_names == rule['names'] else 0.
463
+ else:
464
+ raise TypeError(f"{rule['type']} not support yet!")
465
+
466
+
467
+ def check_font_size(font_size, rule):
468
+ """
469
+ Check if the font size is as expected.
470
+ """
471
+
472
+ default_font_size = font_size['default_font_size']
473
+ if rule['type'] == 'value':
474
+ return 1. if default_font_size == rule['value'] else 0.
475
+ elif rule['type'] == 'range':
476
+ return 1. if rule['min'] < default_font_size < rule['max'] else 0.
477
+ else:
478
+ raise TypeError(f"{rule['type']} not support yet!")
479
+
480
+
481
+ def is_added_to_steam_cart(active_tab_info, rule):
482
+ """
483
+ Check if the item is added to the Steam cart.
484
+ """
485
+ items = rule['items']
486
+
487
+ content = active_tab_info['content']
488
+
489
+ for item in items:
490
+ if item not in content:
491
+ return 0.
492
+
493
+ return 1.