-
Notifications
You must be signed in to change notification settings - Fork 12
feat: 允许上传文件作为待匹配图片 #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 允许上传文件作为待匹配图片 #145
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,25 +1,44 @@ | ||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||
| import io | ||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||
| from typing import Callable, List, Optional, Tuple, Union | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| from asyncify import asyncify | ||||||||||||||||||||||||||||
| from PIL import Image | ||||||||||||||||||||||||||||
| from maa.controller import AdbController, Win32Controller | ||||||||||||||||||||||||||||
| from maa.controller import AdbController, Win32Controller, CustomController | ||||||||||||||||||||||||||||
| from maa.context import Context, ContextEventSink | ||||||||||||||||||||||||||||
| from maa.tasker import Tasker, RecognitionDetail | ||||||||||||||||||||||||||||
| from maa.resource import Resource, ResourceEventSink | ||||||||||||||||||||||||||||
| from maa.toolkit import Toolkit, AdbDevice, DesktopWindow | ||||||||||||||||||||||||||||
| from maa.agent_client import AgentClient | ||||||||||||||||||||||||||||
| from maa.library import Library | ||||||||||||||||||||||||||||
| from maa.event_sink import NotificationType | ||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| from ..utils import cvmat_to_image | ||||||||||||||||||||||||||||
| from ..utils.img_tools import cvmat_to_image, rgb_to_bgr | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class MyCustomController(CustomController): | ||||||||||||||||||||||||||||
| def __init__(self, img_bytes: bytes): | ||||||||||||||||||||||||||||
| super().__init__() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| img = Image.open(io.BytesIO(img_bytes)) | ||||||||||||||||||||||||||||
| self.ndarray = rgb_to_bgr(np.array(img)) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def connect(self) -> bool: | ||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def request_uuid(self) -> str: | ||||||||||||||||||||||||||||
| return "0" | ||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+32
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def screencap(self) -> np.ndarray: | ||||||||||||||||||||||||||||
| return self.ndarray | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class MaaFW: | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| resource: Optional[Resource] | ||||||||||||||||||||||||||||
| controller: Union[AdbController, Win32Controller, None] | ||||||||||||||||||||||||||||
| controller: Union[AdbController, Win32Controller, CustomController, None] | ||||||||||||||||||||||||||||
| tasker: Optional[Tasker] | ||||||||||||||||||||||||||||
| agent: Optional[AgentClient] | ||||||||||||||||||||||||||||
| context_event_sink: Optional[ContextEventSink] | ||||||||||||||||||||||||||||
|
|
@@ -89,6 +108,12 @@ def connect_win32hwnd( | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return True, None | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| @asyncify |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name img_bytes lacks type annotation. Other connection methods in this class consistently use type annotations for their parameters. Add : bytes type annotation for consistency.
| def connect_custom_controller(self, img_bytes) -> Tuple[bool, Optional[str]]: | |
| def connect_custom_controller(self, img_bytes: bytes) -> Tuple[bool, Optional[str]]: |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The connect_custom_controller method doesn't check the connection result like connect_adb and connect_win32hwnd do. Those methods verify post_connection().wait().succeeded and return failure status if unsuccessful. This method should similarly validate the connection and handle failure cases.
| self.controller.post_connection().wait() | |
| connected = self.controller.post_connection().wait().succeeded | |
| if not connected: | |
| return False, "Failed to connect custom controller" |
weinibuliu marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment "disable action" is too brief. Consider expanding it to explain why actions are disabled for custom controllers (e.g., "Disable actions for custom controllers as they use static images and cannot perform clicks or other interactive operations").
| # disable action | |
| # Disable actions for custom controllers as they use static images | |
| # and cannot perform clicks or other interactive operations. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This expression mutates a default value.
weinibuliu marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's code duplication in the if-else branches. Both branches return identical values (self.tasker.post_task(entry, pipeline_override).wait().succeeded, None). The logic can be simplified by updating pipeline_override conditionally before a single post_task call:
if isinstance(self.controller, CustomController):
# disable action
pipeline_override.update(
{entry: {"action": {"type": "DoNothing"}, "next": []}}
)
return (
self.tasker.post_task(entry, pipeline_override).wait().succeeded,
None,
)| return ( | |
| self.tasker.post_task(entry, pipeline_override).wait().succeeded, | |
| None, | |
| ) | |
| else: | |
| return ( | |
| self.tasker.post_task(entry, pipeline_override).wait().succeeded, | |
| None, | |
| ) | |
| return ( | |
| self.tasker.post_task(entry, pipeline_override).wait().succeeded, | |
| None, | |
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,6 +39,7 @@ def connect_control(): | |||||||||||||||||
| with ui.tabs() as tabs: | ||||||||||||||||||
| adb = ui.tab("Adb") | ||||||||||||||||||
| win32 = ui.tab("Win32") | ||||||||||||||||||
| custom = ui.tab("Custom") | ||||||||||||||||||
|
|
||||||||||||||||||
| tab_panels = ui.tab_panels(tabs, value="Adb").bind_value(STORAGE, "controller_type") | ||||||||||||||||||
| with tab_panels: | ||||||||||||||||||
|
|
@@ -49,6 +50,10 @@ def connect_control(): | |||||||||||||||||
| with ui.row(align_items="center").classes("w-full"): | ||||||||||||||||||
| connect_win32_control() | ||||||||||||||||||
|
|
||||||||||||||||||
| with ui.tab_panel(custom): | ||||||||||||||||||
| with ui.row(align_items="center").classes("w-full"): | ||||||||||||||||||
| connect_custom_control() | ||||||||||||||||||
|
|
||||||||||||||||||
| os_type = system.get_os_type() | ||||||||||||||||||
| if os_type != system.OSTypeEnum.Windows: | ||||||||||||||||||
| win32.disable() | ||||||||||||||||||
|
|
@@ -301,6 +306,24 @@ def on_change_hwnd_select(value: Optional[str]): | |||||||||||||||||
| hwnd_input.value = value | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def connect_custom_control(): | ||||||||||||||||||
| def on_upload(e): | ||||||||||||||||||
| GlobalStatus.ctrl_connecting = Status.RUNNING | ||||||||||||||||||
| try: | ||||||||||||||||||
| maafw.connect_custom_controller(e.content.read()) | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| GlobalStatus.ctrl_connecting = Status.FAILED | ||||||||||||||||||
| ui.notify( | ||||||||||||||||||
| f"Failed to load image. {e}", position="bottom-right", type="negative" | ||||||||||||||||||
|
Comment on lines
+314
to
+317
|
||||||||||||||||||
| except Exception as e: | |
| GlobalStatus.ctrl_connecting = Status.FAILED | |
| ui.notify( | |
| f"Failed to load image. {e}", position="bottom-right", type="negative" | |
| except Exception as ex: | |
| GlobalStatus.ctrl_connecting = Status.FAILED | |
| ui.notify( | |
| f"Failed to load image. {ex}", position="bottom-right", type="negative" |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is too generic. When image loading fails, users would benefit from more specific information about what went wrong (e.g., "unsupported image format", "corrupted image file", "image too large"). Consider catching specific exceptions (like PIL.UnidentifiedImageError) and providing tailored error messages.
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ui.upload component should specify accepted file types to prevent users from uploading non-image files. Consider adding the accept parameter with image MIME types, e.g., ui.upload(auto_upload=True, on_upload=lambda e: on_upload(e), accept="image/*").
| ui.upload(auto_upload=True, on_upload=lambda e: on_upload(e)) | |
| ui.upload(auto_upload=True, on_upload=lambda e: on_upload(e), accept="image/*") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The
MyCustomControllerclass name doesn't follow the naming convention of the codebase. Since it's implementing a custom controller for static images, a more descriptive name likeStaticImageControllerorUploadedImageControllerwould better convey its purpose and be more consistent with framework naming patterns.