@@ -561,6 +561,41 @@ def _wait_for_stable_screen(
561561 return prev_png
562562
563563
564+ def _build_setup_desc (task_config : dict ) -> str :
565+ """Build a human-readable description of the task setup actions."""
566+ parts = []
567+ for entry in task_config .get ("config" , []):
568+ t = entry .get ("type" , "" )
569+ p = entry .get ("parameters" , {})
570+ if t == "download" :
571+ for f in p .get ("files" , []):
572+ parts .append (f" - File downloaded: { f ['path' ]} " )
573+ elif t == "open" :
574+ parts .append (f" - File opened: { p .get ('path' , '?' )} " )
575+ elif t == "launch" :
576+ parts .append (f" - App launched: { p .get ('command' , '?' )} " )
577+ return "\n " .join (parts ) if parts else " (none)"
578+
579+
580+ def _vlm_call (
581+ messages : list [dict ],
582+ api_key : str ,
583+ model : str = "gpt-4o" ,
584+ max_tokens : int = 800 ,
585+ ) -> str :
586+ """Send a chat completion request to OpenAI. Returns the text response."""
587+ import requests as _req
588+
589+ resp = _req .post (
590+ "https://api.openai.com/v1/chat/completions" ,
591+ headers = {"Authorization" : f"Bearer { api_key } " },
592+ json = {"model" : model , "max_tokens" : max_tokens , "messages" : messages },
593+ timeout = 60 ,
594+ )
595+ resp .raise_for_status ()
596+ return resp .json ()["choices" ][0 ]["message" ]["content" ]
597+
598+
564599def _generate_steps (
565600 screenshot_png : bytes ,
566601 instruction : str ,
@@ -577,66 +612,143 @@ def _generate_steps(
577612 if not api_key :
578613 return "(No OPENAI_API_KEY set — skipping AI step generation)"
579614
580- import requests as _req
581-
582615 b64 = base64 .b64encode (screenshot_png ).decode ()
583-
584- setup_desc = ""
585- for entry in task_config .get ("config" , []):
586- t = entry .get ("type" , "" )
587- p = entry .get ("parameters" , {})
588- if t == "download" :
589- for f in p .get ("files" , []):
590- setup_desc += f" - File downloaded: { f ['path' ]} \n "
591- elif t == "open" :
592- setup_desc += f" - File opened: { p .get ('path' , '?' )} \n "
593- elif t == "launch" :
594- setup_desc += f" - App launched: { p .get ('command' , '?' )} \n "
616+ setup_desc = _build_setup_desc (task_config )
595617
596618 prompt = f"""You are helping a human perform a task on a Windows desktop via VNC.
597619
598620TASK: { instruction }
599621
600622ENVIRONMENT SETUP (already done automatically):
601- { setup_desc if setup_desc else " (none)" }
623+ { setup_desc }
602624
603625Look at the screenshot and give step-by-step instructions to complete the task.
604626Be specific about what to click, what to type, and what menus to use.
605627If a file should already be open but isn't visible, say how to open it.
606628Keep each step to one action. Use plain text, numbered list.
607629Be concise — the user will read this on a phone screen."""
608630
631+ messages = [
632+ {
633+ "role" : "user" ,
634+ "content" : [
635+ {"type" : "text" , "text" : prompt },
636+ {
637+ "type" : "image_url" ,
638+ "image_url" : {
639+ "url" : f"data:image/png;base64,{ b64 } " ,
640+ "detail" : "high" ,
641+ },
642+ },
643+ ],
644+ }
645+ ]
646+
609647 try :
610- resp = _req .post (
611- "https://api.openai.com/v1/chat/completions" ,
612- headers = {"Authorization" : f"Bearer { api_key } " },
613- json = {
614- "model" : "gpt-4o" ,
615- "max_tokens" : 800 ,
616- "messages" : [
617- {
618- "role" : "user" ,
619- "content" : [
620- {"type" : "text" , "text" : prompt },
621- {
622- "type" : "image_url" ,
623- "image_url" : {
624- "url" : f"data:image/png;base64,{ b64 } " ,
625- "detail" : "high" ,
626- },
627- },
628- ],
629- }
630- ],
631- },
632- timeout = 30 ,
633- )
634- resp .raise_for_status ()
635- return resp .json ()["choices" ][0 ]["message" ]["content" ]
648+ return _vlm_call (messages , api_key )
636649 except Exception as e :
637650 return f"(AI step generation failed: { e } )"
638651
639652
653+ def _refine_steps (
654+ screenshot_png : bytes ,
655+ instruction : str ,
656+ task_config : dict ,
657+ current_steps : str ,
658+ feedback : str ,
659+ ) -> str :
660+ """Refine suggested steps based on user feedback.
661+
662+ Sends the original screenshot, instruction, current steps, and the
663+ user's correction back to the VLM for a revised set of steps.
664+ """
665+ import base64
666+ import os
667+
668+ api_key = os .environ .get ("OPENAI_API_KEY" )
669+ if not api_key :
670+ return current_steps
671+
672+ b64 = base64 .b64encode (screenshot_png ).decode ()
673+ setup_desc = _build_setup_desc (task_config )
674+
675+ prompt = f"""You are helping a human perform a task on a Windows desktop via VNC.
676+
677+ TASK: { instruction }
678+
679+ ENVIRONMENT SETUP (already done automatically):
680+ { setup_desc }
681+
682+ You previously suggested these steps:
683+
684+ { current_steps }
685+
686+ The user says this is wrong and provides this feedback:
687+
688+ "{ feedback } "
689+
690+ Look at the screenshot again and produce CORRECTED step-by-step instructions
691+ that address the user's feedback. Keep the same format: plain text, numbered list,
692+ one action per step, concise."""
693+
694+ messages = [
695+ {
696+ "role" : "user" ,
697+ "content" : [
698+ {"type" : "text" , "text" : prompt },
699+ {
700+ "type" : "image_url" ,
701+ "image_url" : {
702+ "url" : f"data:image/png;base64,{ b64 } " ,
703+ "detail" : "high" ,
704+ },
705+ },
706+ ],
707+ }
708+ ]
709+
710+ try :
711+ return _vlm_call (messages , api_key )
712+ except Exception as e :
713+ print (f" (Refinement failed: { e } )" )
714+ return current_steps
715+
716+
717+ def _display_steps (steps_text : str ) -> None :
718+ """Pretty-print suggested steps in a box."""
719+ print ()
720+ print (" ┌─ SUGGESTED STEPS ──────────────────────────────" )
721+ for line in steps_text .splitlines ():
722+ print (f" │ { line } " )
723+ print (" └────────────────────────────────────────────────" )
724+ print ()
725+
726+
727+ def _interactive_step_review (
728+ screenshot_png : bytes ,
729+ instruction : str ,
730+ task_config : dict ,
731+ initial_steps : str ,
732+ ) -> str :
733+ """Let the user review and iteratively correct the suggested steps.
734+
735+ The user can press Enter to accept, or type feedback to refine.
736+ Returns the final accepted steps text.
737+ """
738+ current = initial_steps
739+ while True :
740+ correction = input (
741+ " Press Enter to accept steps, or type correction: "
742+ ).strip ()
743+ if not correction :
744+ return current
745+ print (" Refining steps..." )
746+ current = _refine_steps (
747+ screenshot_png , instruction , task_config , current , correction ,
748+ )
749+ _display_steps (current )
750+
751+
640752def cmd_record_waa (
641753 tasks : str = "," .join (HARDER_TASK_IDS ),
642754 server : str = "http://localhost:5001" ,
@@ -848,11 +960,10 @@ def _hard_reset_task_env() -> bytes:
848960 # Generate AI step-by-step guidance from screenshot
849961 print (" Generating suggested steps..." )
850962 suggested = _generate_steps (before_png , instruction , task_config )
851- print ()
852- print (" ┌─ SUGGESTED STEPS ──────────────────────────────" )
853- for line in suggested .splitlines ():
854- print (f" │ { line } " )
855- print (" └────────────────────────────────────────────────" )
963+ _display_steps (suggested )
964+ suggested = _interactive_step_review (
965+ before_png , instruction , task_config , suggested ,
966+ )
856967 print ()
857968 print (" Perform each action in VNC, then press Enter here." )
858969 print (" Press 'd' when done, 'r' to redo last step," )
@@ -886,11 +997,10 @@ def _hard_reset_task_env() -> bytes:
886997 # Re-generate steps from the new stable screenshot
887998 print (" Generating suggested steps..." )
888999 suggested = _generate_steps (before_png , instruction , task_config )
889- print ()
890- print (" ┌─ SUGGESTED STEPS ──────────────────────────────" )
891- for line in suggested .splitlines ():
892- print (f" │ { line } " )
893- print (" └────────────────────────────────────────────────" )
1000+ _display_steps (suggested )
1001+ suggested = _interactive_step_review (
1002+ before_png , instruction , task_config , suggested ,
1003+ )
8941004 print ()
8951005 print (" Task restarted. Continue recording.\n " )
8961006 continue
0 commit comments