2929 ModelName ,
3030 ModelRegistry ,
3131 Point ,
32+ PointList ,
3233 TotalModelChoice ,
3334)
3435from .models .types .response_schemas import ResponseSchema
@@ -352,13 +353,14 @@ class LinkedListNode(ResponseSchemaBase):
352353 self ._reporter .add_message ("Agent" , message_content )
353354 return response
354355
356+ @validate_call (config = ConfigDict (arbitrary_types_allowed = True ))
355357 def _locate (
356358 self ,
357359 locator : str | Locator ,
358360 screenshot : Optional [Img ] = None ,
359361 model : ModelComposition | str | None = None ,
360- ) -> Point :
361- def locate_with_screenshot () -> Point :
362+ ) -> PointList :
363+ def locate_with_screenshot () -> PointList :
362364 _screenshot = load_image_source (
363365 self ._agent_os .screenshot () if screenshot is None else screenshot
364366 )
@@ -368,10 +370,10 @@ def locate_with_screenshot() -> Point:
368370 model_choice = model or self ._model_choice ["locate" ],
369371 )
370372
371- point = self ._retry .attempt (locate_with_screenshot )
372- self ._reporter .add_message ("ModelRouter" , f"locate: ( { point [ 0 ] } , { point [ 1 ] } ) " )
373- logger .debug ("ModelRouter locate: (%d, %d) " , point [ 0 ], point [ 1 ] )
374- return point
373+ points = self ._retry .attempt (locate_with_screenshot )
374+ self ._reporter .add_message ("ModelRouter" , f"locate { len ( points ) } elements " )
375+ logger .debug ("ModelRouter locate: %d elements " , len ( points ) )
376+ return points
375377
376378 @telemetry .record_call (exclude = {"locator" , "screenshot" })
377379 @validate_call (config = ConfigDict (arbitrary_types_allowed = True ))
@@ -382,7 +384,7 @@ def locate(
382384 model : ModelComposition | str | None = None ,
383385 ) -> Point :
384386 """
385- Locates the UI element identified by the provided locator.
387+ Locates the first matching UI element identified by the provided locator.
386388
387389 Args:
388390 locator (str | Locator): The identifier or description of the element to
@@ -405,8 +407,53 @@ def locate(
405407 print(f"Element found at coordinates: {point}")
406408 ```
407409 """
408- self ._reporter .add_message ("User" , f"locate { locator } " )
409- logger .debug ("VisionAgent received instruction to locate %s" , locator )
410+ self ._reporter .add_message ("User" , f"locate first matching element { locator } " )
411+ logger .debug (
412+ "VisionAgent received instruction to locate first matching element %s" ,
413+ locator ,
414+ )
415+ return self ._locate (locator , screenshot , model )[0 ]
416+
417+ @telemetry .record_call (exclude = {"locator" , "screenshot" })
418+ @validate_call (config = ConfigDict (arbitrary_types_allowed = True ))
419+ def locate_all (
420+ self ,
421+ locator : str | Locator ,
422+ screenshot : Optional [Img ] = None ,
423+ model : ModelComposition | str | None = None ,
424+ ) -> PointList :
425+ """
426+ Locates all matching UI elements identified by the provided locator.
427+
428+ Note: Some LocateModels can only locate a single element. In this case, the
429+ returned list will have a length of 1.
430+
431+ Args:
432+ locator (str | Locator): The identifier or description of the element to
433+ locate.
434+ screenshot (Img | None, optional): The screenshot to use for locating the
435+ element. Can be a path to an image file, a PIL Image object or a data
436+ URL. If `None`, takes a screenshot of the currently selected display.
437+ model (ModelComposition | str | None, optional): The composition or name
438+ of the model(s) to be used for locating the element using the `locator`.
439+
440+ Returns:
441+ PointList: The coordinates of the elements as a list of tuples (x, y).
442+
443+ Example:
444+ ```python
445+ from askui import VisionAgent
446+
447+ with VisionAgent() as agent:
448+ points = agent.locate_all("Submit button")
449+ print(f"Found {len(points)} elements at coordinates: {points}")
450+ ```
451+ """
452+ self ._reporter .add_message ("User" , f"locate all matching UI elements { locator } " )
453+ logger .debug (
454+ "VisionAgent received instruction to locate all matching UI elements %s" ,
455+ locator ,
456+ )
410457 return self ._locate (locator , screenshot , model )
411458
412459 @telemetry .record_call ()
0 commit comments