UI-TARS 源码解析 #18:drag 与 scroll 源码解析:复杂 GUI 操作如何落到 pyautogui?

在前两篇文章中,我们分别分析了 UI-TARS 执行层里的两类动作。

第 #16 篇讲的是鼠标类动作:

text 复制代码
click
left_single
left_double
right_single
hover

第 #17 篇讲的是键盘类动作:

text 复制代码
hotkey
type
press / keydown
release / keyup

这一篇继续分析 parsing_response_to_pyautogui_code 中更复杂的 GUI 操作:

text 复制代码
drag
select
scroll

它们比单击和输入更复杂。

因为 click 只需要一个目标点,而 drag 至少需要两个位置:

text 复制代码
起点
终点

scroll 虽然看起来只是滚轮动作,但在真实 GUI 中还要知道:

text 复制代码
在哪个区域滚动?
向上还是向下滚动?

所以,这篇文章的核心问题是:

UI-TARS 如何把模型输出的拖拽、选择、滚动动作,转换成 pyautogui 可以执行的真实操作?


一、drag 和 scroll 在 UI-TARS 动作空间中的位置

在 UI-TARS 的 Prompt 设计中,桌面端 COMPUTER_USE 明确支持:

text 复制代码
drag(start_point='<point>x1 y1</point>', end_point='<point>x2 y2</point>')

scroll(point='<point>x1 y1</point>', direction='down or up or right or left')

这说明拖拽和滚动不是附属功能,而是 GUI Agent 动作空间中的基础动作。prompt.py 中的桌面端模板确实把 dragscroll 放入 Action Space,并要求模型按照函数调用格式输出动作。

如果 GUI Agent 只支持点击和输入,它能完成的任务会很有限。

真实界面里经常需要:

text 复制代码
拖动滑块
拖拽文件
框选文本
调整窗口大小
移动地图
滚动网页
滚动表格
滚动弹窗
滚动侧边栏

这些都不是单个 click 能解决的。

所以,dragscroll 是 GUI Agent 从"会点按钮"走向"能操作复杂界面"的关键动作。


二、从整体链路看 drag 和 scroll

UI-TARS 的完整动作执行链路是:

text 复制代码
模型输出 Thought + Action
    ↓
parse_action_to_structure_output
    ↓
结构化 action dict
    ↓
parsing_response_to_pyautogui_code
    ↓
pyautogui 代码

官方 codes/README.md 也说明,parse_action_to_structure_output 负责把模型输出解析成结构化动作,并处理坐标缩放和 box/point 格式转换;parsing_response_to_pyautogui_code 则把结构化 actions 转换成 pyautogui 脚本,支持 click、type、hotkey、drag、scroll 等动作。

对于 drag 来说,链路大致是:

text 复制代码
模型输出:
drag(start_point='<point>300 500</point>', end_point='<point>700 500</point>')

    ↓

结构化动作:
{
  "action_type": "drag",
  "action_inputs": {
    "start_box": "[0.1562, 0.4630, 0.1562, 0.4630]",
    "end_box": "[0.3646, 0.4630, 0.3646, 0.4630]"
  }
}

    ↓

pyautogui 代码:
pyautogui.moveTo(300, 500)
pyautogui.dragTo(700, 500, duration=1.0)

对于 scroll 来说,链路大致是:

text 复制代码
模型输出:
scroll(point='<point>600 720</point>', direction='down')

    ↓

结构化动作:
{
  "action_type": "scroll",
  "action_inputs": {
    "start_box": "[0.3125, 0.6667, 0.3125, 0.6667]",
    "direction": "down"
  }
}

    ↓

pyautogui 代码:
pyautogui.scroll(-5, x=600, y=720)

也就是说,复杂操作最终还是会落到 pyautogui 的基础能力上:

text 复制代码
移动鼠标
拖拽鼠标
滚动滚轮

三、drag 和 select 共用同一个源码分支

action_parser.pyparsing_response_to_pyautogui_code 中,dragselect 被放在同一个分支:

python 复制代码
elif action_type in ["drag", "select"]:
    start_box = action_inputs.get("start_box")
    end_box = action_inputs.get("end_box")
    ...
    pyautogui.moveTo(sx, sy)
    pyautogui.dragTo(ex, ey, duration=1.0)

源码中确实把 dragselect 放在同一个分支,读取 start_boxend_box,分别计算起点和终点中心坐标,然后生成 pyautogui.moveTo(...)pyautogui.dragTo(...)

为什么 dragselect 可以共用逻辑?

因为它们底层动作一样:

text 复制代码
鼠标移动到起点
按住鼠标
移动到终点
松开鼠标

区别只在语义上。

drag 更偏向:

text 复制代码
拖动滑块
拖动文件
拖动窗口
拖动地图

select 更偏向:

text 复制代码
框选文本
框选区域
选中多个对象

但从 pyautogui 执行角度看,两者都是:

text 复制代码
moveTo + dragTo

所以源码把它们放在同一个分支里,是合理的代码复用。


四、drag 为什么必须有 start_box 和 end_box?

点击动作只需要一个点:

text 复制代码
click(start_box='...')

但拖拽动作必须知道两个位置:

text 复制代码
start_box:从哪里开始拖
end_box:拖到哪里结束

例如:

text 复制代码
Action: drag(start_point='<point>300 500</point>', end_point='<point>700 500</point>')

经过 parse_action_to_structure_output 后,会先统一字段名:

text 复制代码
start_point → start_box
end_point   → end_box

源码中 parse_action_to_structure_output 确实会把 start_point= 替换为 start_box=,把 end_point= 替换为 end_box=,并把 point= 替换为 start_box=

结构化后变成:

json 复制代码
{
  "action_type": "drag",
  "action_inputs": {
    "start_box": "[0.1562, 0.4630, 0.1562, 0.4630]",
    "end_box": "[0.3646, 0.4630, 0.3646, 0.4630]"
  }
}

这里的 start_boxend_box 都是归一化坐标。

也就是说,它们不是直接的屏幕像素,而是:

text 复制代码
0 到 1 之间的比例坐标

执行阶段还要乘以 image_widthimage_height 才能得到真实坐标。


五、drag 的坐标还原逻辑

源码中,drag/select 分支会分别解析 start_boxend_box

对于 start_box

python 复制代码
x1, y1, x2, y2 = eval(start_box)
sx = round(float((x1 + x2) / 2) * image_width, 3)
sy = round(float((y1 + y2) / 2) * image_height, 3)

对于 end_box

python 复制代码
x1, y1, x2, y2 = eval(end_box)
ex = round(float((x1 + x2) / 2) * image_width, 3)
ey = round(float((y1 + y2) / 2) * image_height, 3)

这和前面鼠标点击分支的思路一样:

text 复制代码
box → 取中心点 → 乘以真实宽高 → 得到真实坐标

区别在于,drag 需要算两次:

text 复制代码
start_box → sx, sy
end_box   → ex, ey

最后生成:

python 复制代码
pyautogui.moveTo(sx, sy)
pyautogui.dragTo(ex, ey, duration=1.0)

源码中的 drag/select 分支正是按照这个逻辑生成 pyautogui 代码。


六、为什么 drag 要先 moveTo,再 dragTo?

源码生成的是:

python 复制代码
pyautogui.moveTo(sx, sy)
pyautogui.dragTo(ex, ey, duration=1.0)

而不是直接:

python 复制代码
pyautogui.dragTo(ex, ey)

原因很简单:

拖拽动作必须明确起点。

如果鼠标当前位置不确定,直接 dragTo 可能会从错误位置开始拖。

例如,模型想拖动一个滑块:

text 复制代码
起点:滑块当前位置
终点:目标位置

如果不先 moveTo(sx, sy),鼠标可能从上一次操作的位置开始拖,结果就完全错误。

所以正确流程是:

text 复制代码
先移动到起点
再拖到终点

这也是 UI-TARS 源码的实现方式。


七、duration=1.0 的作用

源码中 dragTo 带了一个参数:

python 复制代码
duration=1.0

这表示拖拽动作持续 1 秒。

为什么不瞬间拖过去?

因为很多 GUI 对拖拽动作的识别需要一定过程。

如果拖得太快,可能出现:

text 复制代码
滑块没有跟上
文件没有被拖起
文本选择失败
目标区域没有触发 hover 状态
页面没有响应拖动

设置 duration=1.0 可以让拖拽更像真实用户操作。

当然,1 秒不是绝对最优。

不同场景可以调整:

text 复制代码
拖动滑块:0.3 ~ 1.0 秒
拖动文件:0.5 ~ 1.5 秒
框选文本:0.5 ~ 1.0 秒
拖动地图:0.2 ~ 0.8 秒

如果做产品化,可以把 duration 作为可配置参数,而不是写死。


八、drag 可以覆盖哪些真实 GUI 场景?

drag 的应用非常广。

1. 拖动滑块

例如验证码、音量条、进度条、范围选择器:

text 复制代码
Action: drag(start_point='<point>300 500</point>', end_point='<point>700 500</point>')

2. 拖动文件

例如把文件拖到另一个文件夹:

text 复制代码
先定位文件图标
再定位目标文件夹
执行 drag

3. 框选文本

例如从一段文字开始拖到另一段文字:

text 复制代码
Action: select(start_box='...', end_box='...')

源码里 selectdrag 共用执行逻辑,因此底层都是 moveTo + dragTo

4. 调整窗口大小

例如拖动窗口边缘或分隔线:

text 复制代码
start_box:窗口边缘
end_box:目标位置

5. 拖动地图或画布

例如地图平移、设计软件画布移动、白板拖动。

这些操作都不能只靠 click 完成。

所以,drag 是 GUI Agent 处理复杂界面的关键动作。


九、drag 比 click 更难在哪里?

从源码看,drag 只是多了一个 end_box

但从模型能力看,drag 比 click 难得多。

因为 click 只需要回答:

text 复制代码
目标点在哪里?

drag 需要回答:

text 复制代码
从哪里开始?
拖到哪里结束?
拖动方向是什么?
拖多远?
拖动过程中是否会触发状态变化?

比如拖动滑块,模型必须知道:

text 复制代码
滑块当前位置
目标位置
拖动方向
拖动距离

如果起点错了,拖不到目标。

如果终点错了,拖动距离不够或过头。

如果拖动速度太快,UI 可能不响应。

所以,drag 是对 GUI grounding 能力更高阶的考验。

这也是为什么很多 GUI Agent 任务里,拖拽比点击更容易失败。


十、scroll 的源码分支

接下来分析 scroll

源码中 scroll 分支大致是:

python 复制代码
elif action_type == "scroll":
    start_box = action_inputs.get("start_box")
    if start_box:
        x1, y1, x2, y2 = eval(start_box)
        x = round(float((x1 + x2) / 2) * image_width, 3)
        y = round(float((y1 + y2) / 2) * image_height, 3)
    else:
        x = None
        y = None

    direction = action_inputs.get("direction", "")

    if x == None:
        if "up" in direction.lower():
            pyautogui.scroll(5)
        elif "down" in direction.lower():
            pyautogui.scroll(-5)
    else:
        if "up" in direction.lower():
            pyautogui.scroll(5, x=x, y=y)
        elif "down" in direction.lower():
            pyautogui.scroll(-5, x=x, y=y)

源码中确实会读取 start_box,有坐标时计算中心点并生成带 xypyautogui.scroll(...);没有坐标时生成不带坐标的滚动;方向上只处理了 updown

这段逻辑可以分成三步:

text 复制代码
1. 计算滚动发生位置;
2. 读取 direction;
3. 根据方向生成滚轮正负值。

十一、scroll 为什么也需要 start_box?

很多人可能会以为滚动不需要坐标。

因为鼠标滚轮不就是上下滚吗?

但真实 GUI 中,滚动和鼠标所在位置高度相关。

例如一个网页里可能同时存在:

text 复制代码
左侧菜单
中间正文
右侧目录
弹窗内部
表格区域
代码编辑器

鼠标停在哪里,滚动的就是哪个区域。

所以模型输出 scroll 时,不只要说明方向,还要说明滚动位置:

text 复制代码
scroll(point='<point>600 720</point>', direction='down')

经过解析后:

json 复制代码
{
  "action_type": "scroll",
  "action_inputs": {
    "start_box": "[0.3125, 0.6667, 0.3125, 0.6667]",
    "direction": "down"
  }
}

这里的 start_box 表示:

把鼠标放到哪个区域附近,然后滚动。

这和 click 不一样。

click 的坐标是目标点。

scroll 的坐标是滚动作用区域。


十二、scroll 的方向如何映射?

源码中,滚动方向主要处理两类:

text 复制代码
up
down

如果 direction 包含 up

python 复制代码
pyautogui.scroll(5)

如果 direction 包含 down

python 复制代码
pyautogui.scroll(-5)

有坐标时则是:

python 复制代码
pyautogui.scroll(5, x=x, y=y)
pyautogui.scroll(-5, x=x, y=y)

也就是说:

text 复制代码
up   → 正数滚动
down → 负数滚动

这符合 pyautogui 的常见用法。

不过这里有一个细节值得注意。

prompt.py 中,scroll 的方向说明包含:

text 复制代码
down or up or right or left

但当前 parsing_response_to_pyautogui_code 的源码分支只处理了 updown,并没有生成左右滚动代码。prompt.py 的动作空间包含四个方向,而 action_parser.py 的 scroll 分支只根据 up/down 生成 pyautogui.scroll 正负值。

这说明 Prompt 和执行层之间存在一个小的不完全对齐点。

如果模型输出:

text 复制代码
scroll(point='<point>600 720</point>', direction='right')

当前这段执行层不会生成有效的左右滚动 pyautogui 代码。

产品化时,这个地方需要补齐。


十三、为什么 pyautogui.scroll 使用 5 和 -5?

源码里写死了:

text 复制代码
up   → 5
down → -5

这个数字表示滚轮滚动的 click 数。

5 不是模型输出的参数,而是源码中的固定值。

这是一种简化设计。

它让模型只需要输出:

text 复制代码
direction='down'

而不需要输出:

text 复制代码
amount=5

好处是 Prompt 更简单。

坏处是滚动距离不可控。

有些页面可能只需要轻微滚动,有些页面需要滚动很远。

如果统一使用 5,可能出现:

text 复制代码
滚得太少,看不到目标
滚得太多,越过目标
滚动区域没有响应

所以,产品化时可以考虑扩展:

text 复制代码
scroll(point='...', direction='down', amount=5)

或者:

text 复制代码
scroll(point='...', direction='down', distance='small')
scroll(point='...', direction='down', distance='large')

但只要扩展 Prompt,就必须同步扩展 Parser 和 Executor。


十四、scroll 和 drag 的核心区别

dragscroll 都是复杂鼠标操作,但它们的语义不同。

drag 是:

text 复制代码
从 A 点拖到 B 点。

需要:

text 复制代码
start_box
end_box

执行方式是:

text 复制代码
moveTo(start)
dragTo(end)

scroll 是:

text 复制代码
在某个区域向某个方向滚动。

需要:

text 复制代码
start_box
direction

执行方式是:

text 复制代码
scroll(amount, x, y)

也就是说:

text 复制代码
drag 强调路径;
scroll 强调区域和方向。

这也是为什么 drag 需要两个坐标,而 scroll 通常只需要一个坐标。


十五、scroll 为什么执行前没有 click?

源码中有一段被注释掉的代码:

python 复制代码
# 先点对应区域,再滚动
# pyautogui_code += f"\npyautogui.click({x}, {y}, button='left')"

这说明作者考虑过一种做法:

text 复制代码
先点击滚动区域
再执行滚动

但最终注释掉了。

为什么?

可能是因为滚动前点击区域有风险。

例如:

text 复制代码
滚动正文区域时,点击可能打开链接;
滚动表格时,点击可能选中单元格;
滚动弹窗时,点击可能触发按钮;
滚动文件列表时,点击可能选中文件;

所以源码选择只在指定坐标处滚动,而不是先点击。

这个细节很重要。

它说明:

滚动动作应该尽量只改变滚动位置,不额外触发点击副作用。

当然,有些 GUI 框架需要先让区域获得焦点才能滚动。

这时是否先 click,应该作为可配置策略,而不是默认行为。


十六、scroll 最容易失败的场景

滚动看起来简单,但在 GUI Agent 中非常容易失败。

1. 滚动区域判断错误

模型想滚动主内容区,但坐标落在侧边栏。

结果滚动的是侧边栏。

2. 页面没有焦点

某些窗口需要先获得焦点才能响应滚轮。

3. 弹窗遮挡

模型以为滚动的是页面,但其实弹窗挡住了页面。

4. 方向错了

目标在下面,模型却向上滚。

5. 滚动距离固定

源码使用固定值 5 或 -5,可能不适合所有页面。

6. 横向滚动未实现

Prompt 允许 left/right,但当前 pyautogui 生成逻辑只处理 up/down

这些都是产品化时需要补强的地方。


十七、drag 最容易失败的场景

拖拽失败的情况更多。

1. 起点不是可拖动对象

模型输出的 start_box 落在滑块附近,但没有落在滑块上。

结果拖不动。

2. 终点不合理

拖到太远、太近,或者拖到无效区域。

3. 拖拽速度不适配

duration=1.0 对某些界面可以,对某些界面可能太慢或太快。

4. 需要按住某个键配合

例如某些多选或复制拖拽需要 Ctrl 或 Shift。

5. 拖拽过程中触发 hover 或弹窗

拖到中途界面变了,最终结果和预期不一致。

6. 远程桌面或虚拟机拖拽不稳定

远程环境中拖拽动作经常比点击更不稳定。

所以拖拽执行后,一定要重新截图验证结果。


十八、完整示例:拖动滑块

假设用户任务是:

text 复制代码
把音量调高。

模型看到当前界面后输出:

text 复制代码
Thought: 我需要把音量滑块向右拖动。
Action: drag(start_point='<point>300 500</point>', end_point='<point>600 500</point>')

经过 parse_action_to_structure_output 后:

json 复制代码
{
  "action_type": "drag",
  "action_inputs": {
    "start_box": "[0.1562, 0.4630, 0.1562, 0.4630]",
    "end_box": "[0.3125, 0.4630, 0.3125, 0.4630]"
  }
}

假设图像尺寸是 1920×1080,执行层还原:

text 复制代码
sx = 0.1562 * 1920 ≈ 300
sy = 0.4630 * 1080 ≈ 500

ex = 0.3125 * 1920 = 600
ey = 0.4630 * 1080 ≈ 500

生成:

python 复制代码
pyautogui.moveTo(300, 500)
pyautogui.dragTo(600, 500, duration=1.0)

执行后重新截图,模型判断音量是否已经调高。

如果还不够,再继续拖动。

这就是拖拽动作的闭环。


十九、完整示例:滚动网页

假设用户任务是:

text 复制代码
向下滚动页面,找到下载按钮。

模型当前没有看到下载按钮,于是输出:

text 复制代码
Thought: 当前页面没有看到下载按钮,我需要在主内容区向下滚动。
Action: scroll(point='<point>960 720</point>', direction='down')

结构化后:

json 复制代码
{
  "action_type": "scroll",
  "action_inputs": {
    "start_box": "[0.5, 0.6667, 0.5, 0.6667]",
    "direction": "down"
  }
}

执行层还原坐标:

text 复制代码
x = 0.5 * 1920 = 960
y = 0.6667 * 1080 ≈ 720

生成:

python 复制代码
pyautogui.scroll(-5, x=960, y=720)

执行后重新截图。

如果下载按钮出现,下一步模型再输出 click。

如果还没出现,可能继续 scroll。

这就是滚动动作的典型使用方式。


二十、为什么 drag 和 scroll 都需要执行后验证?

dragscroll 都是"状态改变动作"。

它们不像 hotkey(ctrl c) 那样通常没有明显界面变化。

拖拽和滚动会改变界面状态:

text 复制代码
滑块位置变化
页面内容变化
文本被选中
文件位置变化
地图视图变化
表格滚动位置变化

但这些变化是否符合预期,不能只靠 pyautogui 执行结果判断。

pyautogui.dragTo(...) 执行成功,只表示鼠标动作完成。

不代表滑块真的移动成功。

pyautogui.scroll(...) 执行成功,只表示滚轮事件发出了。

不代表页面真的滚动到了目标位置。

所以 GUI Agent 必须:

text 复制代码
执行 drag / scroll
    ↓
重新截图
    ↓
观察界面是否变化
    ↓
判断是否达到目标

这才是完整闭环。


二十一、源码中的 eval 风险

和鼠标点击分支一样,dragscroll 分支里也使用了:

python 复制代码
eval(start_box)
eval(end_box)

这用于把字符串形式的 box 转成 Python list。源码中的 drag/select 分支和 scroll 分支都使用了 eval 来解析 box 字符串。

这在研究代码中很方便,但在真实产品中不建议直接使用。

因为 start_boxend_box 最初来自模型输出,属于不可信输入。

更安全的方式是:

python 复制代码
import ast

coords = ast.literal_eval(start_box)

或者自己写严格解析函数:

text 复制代码
只允许 list 或 tuple;
长度必须是 4;
每个元素必须是 int 或 float;
归一化坐标必须在 0 到 1 之间;
真实坐标不能超出屏幕范围。

对于 drag,还要额外检查:

text 复制代码
起点和终点不能完全相同;
拖拽距离不能异常过大;
拖拽不能跨越高风险区域;

二十二、产品化建议:封装 drag 执行

如果你要做自己的桌面自动化项目,可以把 drag 封装成独立函数。

例如:

python 复制代码
def build_drag_code(start_box, end_box, image_width, image_height, duration=1.0):
    sx1, sy1, sx2, sy2 = safe_parse_box(start_box)
    ex1, ey1, ex2, ey2 = safe_parse_box(end_box)

    sx = round(((sx1 + sx2) / 2) * image_width, 3)
    sy = round(((sy1 + sy2) / 2) * image_height, 3)
    ex = round(((ex1 + ex2) / 2) * image_width, 3)
    ey = round(((ey1 + ey2) / 2) * image_height, 3)

    validate_coordinate(sx, sy, image_width, image_height)
    validate_coordinate(ex, ey, image_width, image_height)

    return (
        f"pyautogui.moveTo({sx}, {sy})\n"
        f"pyautogui.dragTo({ex}, {ey}, duration={duration})"
    )

这样可以集中处理:

text 复制代码
安全解析
坐标还原
边界检查
duration 配置
日志记录

比在一个大函数里直接写所有逻辑更适合产品化。


二十三、产品化建议:增强 scroll 能力

当前源码中的 scroll 比较简单:

text 复制代码
up   → pyautogui.scroll(5)
down → pyautogui.scroll(-5)

如果要做真实产品,可以增强几项。

1. 支持滚动距离

例如:

text 复制代码
scroll(point='...', direction='down', amount=3)
scroll(point='...', direction='down', amount=10)

2. 支持横向滚动

Prompt 中已经允许 rightleft,但当前执行层只处理 up/down

可以考虑映射到:

python 复制代码
pyautogui.hscroll(...)

或者在特定平台下用 Shift + 滚轮模拟横向滚动。

3. 支持先聚焦再滚动

某些场景可以配置:

text 复制代码
focus_before_scroll = True

但不要默认点击,以免触发副作用。

4. 支持滚动后验证

例如检测页面是否发生变化:

text 复制代码
滚动前截图
滚动后截图
对比内容变化
判断是否到达目标

二十四、drag 和 scroll 与传统 RPA 的区别

传统 RPA 的拖拽和滚动通常来自录制:

text 复制代码
录制时从 A 拖到 B
回放时继续从 A 拖到 B

UI-TARS 的方式是:

text 复制代码
模型观察当前截图
判断当前滑块、页面或目标区域在哪里
输出 start_box / end_box / direction
执行层还原真实坐标
pyautogui 执行拖拽或滚动
执行后重新截图

两者最终都可能调用:

python 复制代码
pyautogui.dragTo(...)
pyautogui.scroll(...)

但坐标来源不同。

传统 RPA 的坐标来自人工录制。

UI-TARS 的坐标来自模型对当前屏幕状态的动态理解。

这也是 GUI Agent 与传统坐标脚本的核心区别。


二十五、drag 和 scroll 的调试日志建议

对复杂动作,一定要记录完整日志。

drag 日志可以包含:

json 复制代码
{
  "action_type": "drag",
  "start_box": "[0.1562, 0.4630, 0.1562, 0.4630]",
  "end_box": "[0.3125, 0.4630, 0.3125, 0.4630]",
  "start_screen": [300, 500],
  "end_screen": [600, 500],
  "duration": 1.0,
  "before_screenshot": "step_003_before.png",
  "after_screenshot": "step_003_after.png"
}

scroll 日志可以包含:

json 复制代码
{
  "action_type": "scroll",
  "start_box": "[0.5, 0.6667, 0.5, 0.6667]",
  "screen_position": [960, 720],
  "direction": "down",
  "amount": -5,
  "before_screenshot": "step_004_before.png",
  "after_screenshot": "step_004_after.png"
}

这样一旦失败,可以快速判断:

text 复制代码
起点是否正确?
终点是否正确?
滚动区域是否正确?
方向是否正确?
滚动距离是否合适?
界面是否真的变化?

复杂 GUI 操作没有日志,很难排查。


二十六、从源码看 drag 和 scroll 的设计取舍

UI-TARS 当前源码实现比较轻量。

drag 的设计取舍是:

text 复制代码
使用 start_box + end_box;
统一取中心点;
固定 duration=1.0;
用 moveTo + dragTo 实现。

scroll 的设计取舍是:

text 复制代码
使用 start_box 表示滚动区域;
使用 direction 表示方向;
固定滚动量 5 / -5;
只实现 up/down;
不默认先点击滚动区域。

这些设计很适合 demo、研究和基础自动化链路。

但如果要做稳定产品,还需要增强:

text 复制代码
可配置拖拽速度;
可配置滚动距离;
支持横向滚动;
更安全的坐标解析;
滚动前焦点策略;
执行后视觉验证;
失败重试;
高风险区域拦截。

总结

这篇文章我们分析了 UI-TARS 中 dragselectscroll 的源码实现。

drag/select 的核心链路是:

text 复制代码
start_box + end_box
    ↓
分别取中心点
    ↓
乘以 image_width / image_height
    ↓
得到 sx, sy, ex, ey
    ↓
pyautogui.moveTo(sx, sy)
    ↓
pyautogui.dragTo(ex, ey, duration=1.0)

scroll 的核心链路是:

text 复制代码
start_box + direction
    ↓
取 start_box 中心点作为滚动区域
    ↓
direction=up   → pyautogui.scroll(5)
direction=down → pyautogui.scroll(-5)
    ↓
如果有 x/y,则在指定区域滚动

从源码可以看到,UI-TARS 把复杂 GUI 操作统一成了几个基础动作:

text 复制代码
拖拽:moveTo + dragTo
滚动:scroll

但真正难的不是 pyautogui 调用本身,而是:

text 复制代码
模型能不能找准拖拽起点;
模型能不能判断拖拽终点;
模型能不能选对滚动区域;
模型能不能判断滚动方向;
执行后能不能验证状态变化。

所以,dragscroll 是 GUI Agent 从"点按钮"走向"操作复杂界面"的关键能力。

相关推荐
BW.SU13 天前
瑞佑RUI--工业UI,拖拽即成
stm32·单片机·51单片机·gui·人机界面·单片机可视化设计
Molesidy14 天前
【GUI】【AWTK】基于开阳平台的GUI界面设计环境搭建
linux·gui·awtk
小bo波1 个月前
Java Swing 图形用户界面实验 —— 从算术练习到游戏开发的完整实践
java·课程设计·gui·游戏开发·扫雷·swing
DreamLife☼2 个月前
OpenBCI-特征提取技术:频域分析与时频分析
gui·脑机接口·fft·时域·频域·cyton·openbic
Irissgwe2 个月前
一、Qt 概述
c++·qt·gui·qt creator
灰灰老师2 个月前
Python Tkinter 基础详解
python·gui·tkinter
yivifu3 个月前
CustomTkinter的布局管理器介绍及应用
python·gui·customtkinter·pdf去水印
艺杯羹3 个月前
从零搭建CSDN博客爬虫:Python爬虫+多格式导出完整教程
开发语言·爬虫·python·开源·gui·csdn
Ulyanov3 个月前
《PySide6 GUI开发指南:QML核心与实践》 第二篇:QML语法精要——构建声明式UI的基础
java·开发语言·javascript·python·ui·gui·雷达电子对抗系统仿真