CoolFace
Apppublic

junyangwang0410/Mobile-Agent

sourceHugging Facemitupdated 2y agoView on Hugging Face
9likes
app.py465 linesDownload Raw Back to root
1import io2import os3import shutil4import base645import gradio as gr6from PIL import Image, ImageDraw7 8from MobileAgent.text_localization import ocr9from MobileAgent.icon_localization import det10from MobileAgent.local_server import mobile_agent_infer11 12from modelscope import snapshot_download13from modelscope.pipelines import pipeline14from modelscope.utils.constant import Tasks15 16 17chatbot_css = """18<style>19.chat-container {20    display: flex;21    flex-direction: column;22    overflow-y: auto;23    max-height: 630px;24    margin: 10px;25}26.user-message, .bot-message {27    margin: 5px;28    padding: 10px;29    border-radius: 10px;30}31.user-message {32    text-align: right;33    background-color: #7B68EE;34    color: white;35    align-self: flex-end;36}37.bot-message {38    text-align: left;39    background-color: #ADD8E6;40    color: black;41    align-self: flex-start;42}43.user-image {44    text-align: right;45    align-self: flex-end;46    max-width: 150px;47    max-height: 300px;48}49.bot-image {50    text-align: left;51    align-self: flex-start;52    max-width: 200px;53    max-height: 400px;54}55</style>56"""57 58 59temp_file = "temp"60screenshot = "screenshot"61cache = "cache"62if not os.path.exists(temp_file):63    os.mkdir(temp_file)64if not os.path.exists(screenshot):65    os.mkdir(screenshot)66if not os.path.exists(cache):67    os.mkdir(cache)68 69 70groundingdino_dir = snapshot_download('AI-ModelScope/GroundingDINO', revision='v1.0.0')71groundingdino_model = pipeline('grounding-dino-task', model=groundingdino_dir)72ocr_detection = pipeline(Tasks.ocr_detection, model='damo/cv_resnet18_ocr-detection-line-level_damo')73ocr_recognition = pipeline(Tasks.ocr_recognition, model='damo/cv_convnextTiny_ocr-recognition-document_damo')74 75 76def encode_image(image_path):77    with open(image_path, "rb") as image_file:78        return base64.b64encode(image_file.read()).decode('utf-8')79 80 81def get_all_files_in_folder(folder_path):82    file_list = []83    for file_name in os.listdir(folder_path):84        file_list.append(file_name)85    return file_list86 87 88def crop(image, box, i):89    image = Image.open(image)90    x1, y1, x2, y2 = int(box[0]), int(box[1]), int(box[2]), int(box[3])91    if x1 >= x2-10 or y1 >= y2-10:92        return93    cropped_image = image.crop((x1, y1, x2, y2))94    cropped_image.save(f"./temp/{i}.png", format="PNG")95 96 97def merge_text_blocks(text_list, coordinates_list):98    merged_text_blocks = []99    merged_coordinates = []100 101    sorted_indices = sorted(range(len(coordinates_list)), key=lambda k: (coordinates_list[k][1], coordinates_list[k][0]))102    sorted_text_list = [text_list[i] for i in sorted_indices]103    sorted_coordinates_list = [coordinates_list[i] for i in sorted_indices]104 105    num_blocks = len(sorted_text_list)106    merge = [False] * num_blocks107 108    for i in range(num_blocks):109        if merge[i]:110            continue111        112        anchor = i113        114        group_text = [sorted_text_list[anchor]]115        group_coordinates = [sorted_coordinates_list[anchor]]116 117        for j in range(i+1, num_blocks):118            if merge[j]:119                continue120 121            if abs(sorted_coordinates_list[anchor][0] - sorted_coordinates_list[j][0]) < 10 and \122            sorted_coordinates_list[j][1] - sorted_coordinates_list[anchor][3] >= -10 and sorted_coordinates_list[j][1] - sorted_coordinates_list[anchor][3] < 30 and \123            abs(sorted_coordinates_list[anchor][3] - sorted_coordinates_list[anchor][1] - (sorted_coordinates_list[j][3] - sorted_coordinates_list[j][1])) < 10:124                group_text.append(sorted_text_list[j])125                group_coordinates.append(sorted_coordinates_list[j])126                merge[anchor] = True127                anchor = j128                merge[anchor] = True129 130        merged_text = "\n".join(group_text)131        min_x1 = min(group_coordinates, key=lambda x: x[0])[0]132        min_y1 = min(group_coordinates, key=lambda x: x[1])[1]133        max_x2 = max(group_coordinates, key=lambda x: x[2])[2]134        max_y2 = max(group_coordinates, key=lambda x: x[3])[3]135 136        merged_text_blocks.append(merged_text)137        merged_coordinates.append([min_x1, min_y1, max_x2, max_y2])138 139    return merged_text_blocks, merged_coordinates140 141 142def get_perception_infos(screenshot_file):143    width, height = Image.open(screenshot_file).size144    145    text, coordinates = ocr(screenshot_file, ocr_detection, ocr_recognition)146    text, coordinates = merge_text_blocks(text, coordinates)147    148    perception_infos = []149    for i in range(len(coordinates)):150        perception_info = {"text": "text: " + text[i], "coordinates": coordinates[i]}151        perception_infos.append(perception_info)152        153    coordinates = det(screenshot_file, "icon", groundingdino_model)154    155    for i in range(len(coordinates)):156        perception_info = {"text": "icon", "coordinates": coordinates[i]}157        perception_infos.append(perception_info)158        159    image_box = []160    image_id = []161    for i in range(len(perception_infos)):162        if perception_infos[i]['text'] == 'icon':163            image_box.append(perception_infos[i]['coordinates'])164            image_id.append(i)165 166    for i in range(len(image_box)):167        crop(screenshot_file, image_box[i], image_id[i])168 169    images = get_all_files_in_folder(temp_file)170    if len(images) > 0:171        images = sorted(images, key=lambda x: int(x.split('/')[-1].split('.')[0]))172        image_id = [int(image.split('/')[-1].split('.')[0]) for image in images]173        icon_map = {}174        prompt = 'This image is an icon from a phone screen. Please briefly describe the shape and color of this icon in one sentence.'175 176        string_image = []177        for i in range(len(images)):178            image_path = os.path.join(temp_file, images[i])179            string_image.append({"image_name": images[i], "image_file": encode_image(image_path)})180        query_data = {"task": "caption", "images": string_image, "query": prompt}181        response_query = mobile_agent_infer(query_data)182        icon_map = response_query["icon_map"]183 184        for i, j in zip(image_id, range(1, len(image_id)+1)):185            if icon_map.get(str(j)):186                perception_infos[i]['text'] = "icon: " + icon_map[str(j)]187 188    for i in range(len(perception_infos)):189        perception_infos[i]['coordinates'] = [int((perception_infos[i]['coordinates'][0]+perception_infos[i]['coordinates'][2])/2), int((perception_infos[i]['coordinates'][1]+perception_infos[i]['coordinates'][3])/2)]190        191    return perception_infos, width, height192 193 194def image_to_base64(image):195    buffered = io.BytesIO()196    image.save(buffered, format="PNG")197    img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")198    img_html = f'<img src="data:image/png;base64,{img_str}" />'199    return img_html200 201 202def chatbot(image, instruction, add_info, history, chat_log):203    if history == {}:204        thought_history = []205        summary_history = []206        action_history = []207        summary = ""208        action = ""209        completed_requirements = ""210        memory = ""211        insight = ""212        error_flag = False213        user_msg = "<div class='user-message'>{}</div>".format(instruction)214    else:215        thought_history = history["thought_history"]216        summary_history = history["summary_history"]217        action_history = history["action_history"]218        summary = history["summary"]219        action = history["action"]220        completed_requirements = history["completed_requirements"]221        memory = history["memory"][0]222        insight = history["insight"]223        error_flag = history["error_flag"]224        user_msg = "<div class='user-message'>{}</div>".format("I have uploaded the screenshot. Please continue operating.")225        226    images = get_all_files_in_folder(cache)227    if len(images) > 0 and len(images) <= 100:228        images = sorted(images, key=lambda x: int(x.split('/')[-1].split('.')[0]))229        image_id = [int(image.split('/')[-1].split('.')[0]) for image in images]230        cur_image_id = image_id[-1] + 1231    elif len(images) > 100:232        images = sorted(images, key=lambda x: int(x.split('/')[-1].split('.')[0]))233        image_id = [int(image.split('/')[-1].split('.')[0]) for image in images]234        cur_image_id = image_id[-1] + 1235        os.remove(os.path.join(cache, str(image_id[0])+".png"))236    else:237        cur_image_id = 1238    239    image.save(os.path.join(cache, str(cur_image_id) + ".png"), format="PNG")240    screenshot_file = os.path.join(cache, str(cur_image_id) + ".png")241    perception_infos, width, height = get_perception_infos(screenshot_file)242    shutil.rmtree(temp_file)243    os.mkdir(temp_file)244    245    local_screenshot_file = encode_image(screenshot_file)246    query_data = {247        "task": "decision",248        "screenshot_file": local_screenshot_file,249        "instruction": instruction,250        "perception_infos": perception_infos,251        "width": width,252        "height": height,253        "summary_history": summary_history,254        "action_history": action_history,255        "summary": summary,256        "action": action,257        "add_info": add_info,258        "error_flag": error_flag,259        "completed_requirements": completed_requirements,260        "memory": memory,261        "memory_switch": True,262        "insight": insight263    }264 265    response_query = mobile_agent_infer(query_data)266    output_action = response_query["decision"]267    output_memory = response_query["memory"]268    if output_action == "No token":269        bot_response = ["<div class='bot-message'>{}</div>".format("Sorry, the resources can be exhausted today.")]270        chat_html = "<div class='chat-container'>{}</div>".format("".join(bot_response))271        return chatbot_css + chat_html, history, chat_log272    273    thought = output_action.split("### Thought ###")[-1].split("### Action ###")[0].replace("\n", " ").replace(":", "").replace("  ", " ").strip()274    summary = output_action.split("### Operation ###")[-1].replace("\n", " ").replace("  ", " ").strip()275    action = output_action.split("### Action ###")[-1].split("### Operation ###")[0].replace("\n", " ").replace("  ", " ").strip()276 277    output_memory = output_memory.split("### Important content ###")[-1].split("\n\n")[0].strip() + "\n"278    if "None" not in output_memory and output_memory not in memory:279        memory += output_memory280    281    if "Open app" in action:282        bot_response = "Please click the red circle and upload the current screenshot again."283        app_name = action.split("(")[-1].split(")")[0]284        text, coordinate = ocr(screenshot_file, ocr_detection, ocr_recognition)285        for ti in range(len(text)):286            if app_name == text[ti]:287                name_coordinate = [int((coordinate[ti][0] + coordinate[ti][2])/2), int((coordinate[ti][1] + coordinate[ti][3])/2)]288                x, y = name_coordinate[0], name_coordinate[1]289                radius = 75290                draw = ImageDraw.Draw(image)291                draw.ellipse([x - radius, y - radius, x + radius, y + radius], outline='red', width=10)292                break293        294    elif "Tap" in action:295        bot_response = "Please click the red circle and upload the current screenshot again."296        coordinate = action.split("(")[-1].split(")")[0].split(", ")297        x, y = int(coordinate[0]), int(coordinate[1])298        radius = 75299        draw = ImageDraw.Draw(image)300        draw.ellipse([x - radius, y - radius, x + radius, y + radius], outline='red', width=10)301 302    elif "Swipe" in action:303        bot_response = "Please slide from red circle to blue circle and upload the current screenshot again."304        coordinate1 = action.split("Swipe (")[-1].split("), (")[0].split(", ")305        coordinate2 = action.split("), (")[-1].split(")")[0].split(", ")306        x1, y1 = int(coordinate1[0]), int(coordinate1[1])307        x2, y2 = int(coordinate2[0]), int(coordinate2[1])308        radius = 75309        draw = ImageDraw.Draw(image)310        draw.ellipse([x1 - radius, y1 - radius, x1 + radius, y1 + radius], outline='red', width=10)311        draw.ellipse([x2 - radius, y2 - radius, x2 + radius, y2 + radius], outline='blue', width=10)312        313    elif "Type" in action:314        if "(text)" not in action:315            text = action.split("(")[-1].split(")")[0]316        else:317            text = action.split(" \"")[-1].split("\"")[0]318        bot_response = f"Please type the \"{text}\" and upload the current screenshot again."319        320    elif "Back" in action:321        bot_response = f"Please back to previous page and upload the current screenshot again."322        323    elif "Home" in action:324        bot_response = f"Please back to home page and upload the current screenshot again."325        326    elif "Stop" in action:327        bot_response = f"Task completed."328    329    bot_text1 = "<div class='bot-message'>{}</div>".format("### Decision ###")330    bot_thought = "<div class='bot-message'>{}</div>".format("Thought: " + thought)331    bot_action = "<div class='bot-message'>{}</div>".format("Action: " + action)332    bot_operation = "<div class='bot-message'>{}</div>".format("Operation: " + summary)333    bot_text2 = "<div class='bot-message'>{}</div>".format("### Memory ###")334    bot_memory = "<div class='bot-message'>{}</div>".format(output_memory)335    bot_response = "<div class='bot-message'>{}</div>".format(bot_response)336    if image is not None:337        bot_img_html = image_to_base64(image)338        bot_response = "<div class='bot-image'>{}</div>".format(bot_img_html) + bot_response339 340    chat_log.append(user_msg)341    342    thought_history.append(thought)343    summary_history.append(summary)344    action_history.append(action)345    346    history["thought_history"] = thought_history347    history["summary_history"] = summary_history348    history["action_history"] = action_history349    history["summary"] = summary350    history["action"] = action351    history["memory"] = memory,352    history["memory_switch"] = True,353    history["insight"] = insight354    history["error_flag"] = error_flag355    356    query_data = {357        "task": "planning",358        "instruction": instruction,359        "thought_history": thought_history,360        "summary_history": summary_history,361        "action_history": action_history,362        "completed_requirements": "",363        "add_info": add_info364    }365 366    response_query = mobile_agent_infer(query_data)367    output_planning = response_query["planning"]368    if output_planning == "No token":369        bot_response = ["<div class='bot-message'>{}</div>".format("Sorry, the resources can be exhausted today.")]370        chat_html = "<div class='chat-container'>{}</div>".format("".join(bot_response))371        return chatbot_css + chat_html, history, chat_log372    373    output_planning = output_planning.split("### Completed contents ###")[-1].replace("\n", " ").strip()374    history["completed_requirements"] = output_planning375    376    bot_text3 = "<div class='bot-message'>{}</div>".format("### Planning ###")377    output_planning = "<div class='bot-message'>{}</div>".format(output_planning)378    379    chat_log.append(bot_text3)380    chat_log.append(output_planning)381    chat_log.append(bot_text1)382    chat_log.append(bot_thought)383    chat_log.append(bot_action)384    chat_log.append(bot_operation)385    chat_log.append(bot_text2)386    chat_log.append(bot_memory)387    chat_log.append(bot_response)388 389    chat_html = "<div class='chat-container'>{}</div>".format("".join(chat_log))390 391    return chatbot_css + chat_html, history, chat_log392 393 394def lock_input(instruction):395    return gr.update(value=instruction, interactive=False), gr.update(value=None)396 397 398def reset_demo():399    return gr.update(value="", interactive=True), gr.update(value="If you want to tap an icon of an app, use the action \"Open app\"", interactive=True), "<div class='chat-container'></div>", {}, []400 401 402tos_markdown = ("""<div style="display:flex; gap: 0.25rem;" align="center">403    <a href='https://github.com/X-PLUG/MobileAgent'><img src='https://img.shields.io/badge/Github-Code-blue'></a>404    <a href="https://arxiv.org/abs/2406.01014"><img src="https://img.shields.io/badge/Arxiv-2406.01014-red"></a>405    <a href='https://github.com/X-PLUG/MobileAgent/stargazers'><img src='https://img.shields.io/github/stars/X-PLUG/MobileAgent.svg?style=social'></a>406</div>407If you like our project, please give us a star ✨ on Github for latest update.408 409**Terms of use**4101. Input your instruction in \"Instruction\", for example \"Turn on the dark mode\".4112. You can input helpful operation knowledge in \"Knowledge\".4123. Click \"Submit\" to get the operation. You need to operate your mobile device according to the operation and then upload the screenshot after your operation.4134. The 5 cases in \"Examples\" are a complete flow. Click and submit from top to bottom to experience.4145. Due to limited resources, each operation may take a long time, please be patient and wait.415 416**使用说明**4171. 在“Instruction”中输入你的指令,例如“打开深色模式”。4182. 你可以在“Knowledge”中输入帮助性的操作知识。4193. 点击“Submit”来获得操作。你需要根据输出来操作手机,并且上传操作后的截图。4204. “Example”中的5个例子是一个任务。从上到下点击它们并且点击“Submit”来体验。4215. 由于资源有限,每次操作的时间会比较长,请耐心等待。""")422 423title_markdowm = ("""# Mobile-Agent-v2: Mobile Device Operation Assistant with Effective Navigation via Multi-Agent Collaboration""")424 425instruction_input = gr.Textbox(label="Instruction", placeholder="Input your instruction")426knowledge_input = gr.Textbox(label="Knowledge", placeholder="Input your knowledge", value="If you want to tap an icon of an app, use the action \"Open app\"")427with gr.Blocks() as demo:428    history_state = gr.State(value={})429    history_output = gr.State(value=[])430    with gr.Row():431        gr.Markdown(title_markdowm)432    with gr.Row():433        with gr.Column(scale=5):434            gr.Markdown(tos_markdown)435            with gr.Row():436                image_input = gr.Image(label="Screenshot", type="pil", height=550, width=230)437                gr.Examples(examples=[438                    ["./example/example_1.jpg", "Turn on the dark mode"],439                    ["./example/example_2.jpg", "Turn on the dark mode"],440                    ["./example/example_3.jpg", "Turn on the dark mode"],441                    ["./example/example_4.jpg", "Turn on the dark mode"],442                    ["./example/example_5.jpg", "Turn on the dark mode"],443                ], inputs=[image_input, instruction_input, knowledge_input])444            445        with gr.Column(scale=6):446            instruction_input.render()447            knowledge_input.render()448            with gr.Row():449                start_button = gr.Button("Submit")450                clear_button = gr.Button("Clear")451            output_component = gr.HTML(label="Chat history", value="<div class='chat-container'></div>")452            453    start_button.click(454        fn=lambda image, instruction, add_info, history, output: chatbot(image, instruction, add_info, history, output),455        inputs=[image_input, instruction_input, knowledge_input, history_state, history_output],456        outputs=[output_component, history_state, history_output]457    )458 459    clear_button.click(460        fn=reset_demo,461        inputs=[],462        outputs=[instruction_input, knowledge_input, output_component, history_state, history_output]463    )464 465demo.queue().launch(share=True)