助手入口、布局与任务入口
pnpm tutorial:dev v3-13 --locale zh源码examples/tutorials/v3-13-assistant-surfaces-layout-and-task-entry-points/如果助手永远只是一个浮动小球,它和页面之间会有明显的距离感。设置页可能希望右侧常驻一个工作栏;数据看板可能更适合紧凑的浮层;某一行订单则需要一个“解释风险”的按钮。它们是人找到助手的几种方式,不是几个不同的助手。
Pling 把助手的“外壳”和“运行时”分开。外壳决定对话出现在哪里、怎样展开;运行时保存消息、动作、审批和页面上下文。换外壳不能产生第二段历史,也不能绕过原有的权限边界。
这一章在待办页上增加两件事:
<AgentDock>可以保持浮动,也可以变成贴边的侧栏。- 人可以通过
v-task从普通按钮发起任务,也可以通过pling.ask()从应用代码发起任务。
两种入口都把请求交给已经挂载的对话;它们不会变成模型可以调用的工具。
为什么不能给每个入口建一个对话
在列表旁放一个助手,在详情抽屉里再放一个,在每一行后面再加一个,看起来都很方便;放在一起却会带来几个本可避免的问题:
| 多个独立助手 | 一个助手、多个入口 |
|---|---|
| 局部外壳卸载,对话也跟着消失 | 布局和路由切换后,对话仍在 |
| 每份副本都要单独配置模型与限额 | 模型、限额和观测由一份运行时管理 |
| 用户可能要在不同窗口重复交代背景 | 所有入口看到同一段对话 |
| 审计与审批散落在多处 | 动作经过同一道策略边界 |
可以用一个有多个窗口的桌面应用来理解:窗口可以移动、折叠或改变大小,但背后的文档只有一份。
<AgentDock> 是包在 <AgentInline> 外面的位置外壳。浮动按钮、侧栏边缘、展开状态和任务路由由 dock 负责;真正的对话在 inline 组件里。多数应用直接用 dock 即可。只有当业务布局需要助手占据固定一栏时,才适合自己挂载 AgentInline。
默认浮动入口
最短的写法仍然是:
<template>
<main>
<TaskList />
<AgentDock layout="dock" title="Todo assistant" />
</main>
</template>layout="dock" 把启动按钮放在页面角落。展开时显示对话,收起时 runtime 和历史仍然保留,因为对话组件没有随之卸载。
外壳只提供展示选择,不改变助手能做什么。动作仍来自当前页面的 v-tool 声明,确认仍由 kind 与 confirm 决定。
有固定工作栏时使用侧栏
页面本来就有清晰的左右结构时,侧栏通常比浮层更像产品的一部分:
<AgentDock
layout="sidebar"
side="right"
launcher-placement="bottom"
launcher-label="Ask Pling"
title="Todo assistant"
/>可用的位置参数如下:
| 参数 | 取值 | 作用 |
|---|---|---|
layout | "dock" / "sidebar" | 浮动角落或视口边缘 |
side | "right" / "left" | 侧栏贴在哪一边 |
launcher-placement | "bottom" / "center" | 收起后的入口位置 |
launcher-label | 字符串 | 收起入口上显示的文字 |
default-open | 布尔值 | 初始是否展开 |
side、launcher-placement 和 launcher-label 只对侧栏生效。应用可以把用户选择存进自己的偏好设置,在运行时切换外壳,而无需重建对话。
如果页面要自己控制栅格,可以把 AgentInline 放进一栏:
<section class="workspace">
<OrderTable />
<AgentInline class="copilot-column" title="Order copilot" />
</section>此时宽度、滚动和响应式行为由外层负责。开发者仍然不需要另写一套消息循环。
增加面向人的任务入口
通用启动按钮适合“我想问点什么”。业务页面还需要更具体的入口:“总结未完成待办”“解释这笔订单”“复核队列”。这些按钮属于使用页面的人,不属于 agent。
用 v-task 声明入口:
<button
type="button"
class="smart-action"
v-task="copy.openTasksPrompt"
>
Summarize open tasks
</button>也可以传对象:
v-task="{ prompt: 'Explain the risk in order SO-2041.', surface: 'dock' }"surface 可以省略;页面只有一个助手时不需要指定。只有同时挂载了多个具名 surface、确实需要分流时,才应填写。
入口本身已经在应用逻辑里时,可以用 pling.ask():
function askFromPage(): void {
void pling.ask("What is still open on this page?").catch(() => undefined);
}pling.ask() 同样接受可选的 surface 与 source。source 指向触发任务的元素,供自定义 surface 与诊断定位入口。
v-task 和 pling.ask() 刻意不会出现在模型的动作清单里。若把它们注册成工具,模型会看到一个“给自己再发一句提示词”的工具,结果只会是动作菜单变噪、轮数递归增加,并产生没有业务价值的审批。
共享同一段对话
这一章的页面同时有布局切换、“总结未完成待办”和“从页面发起提问”。三者背后只有一个 <AgentDock>。
关键不在按钮数量,而在没有按按钮复制运行时:
页面按钮 ─┐
页面代码 ─┼─> 已挂载的任务目标 ─> 一份 AgentInline 运行时
启动按钮 ─┘dock 只登记一次任务目标。v-task 和 pling.ask() 找到这个目标并调用它的 send()。外壳展开,请求进入原有历史,后面的审批与结果也出现在同一段对话中。
因此,入口在 dock 收起时也可以使用。发送任务会自动展开外壳,不需要用户先找到启动按钮。
按产品约束选择外壳
布局通常由应用场景决定,而不是由个人偏好决定:
| 场景 | 合理默认值 |
|---|---|
| 内容页上的偶尔求助 | 浮动 dock |
| 长时间编辑、工作区信息密集 | 侧栏 |
| 助手就是主要工作栏 | 页面栅格中的 AgentInline |
| 单条记录上的一个动作 | 记录上的 v-task |
| 已有工具栏或命令面板 | 从代码调用 pling.ask() |
不要因为“页面有好几个区域”就挂载多个 AgentDock。一个外壳可以随应用状态移动,多个任务入口可以指向它。只有当产品确实需要具有不同权限、不同历史或不同受众的助手时,多运行时才有意义。
验证
启动示例:
pnpm tutorial:dev v3-13依次检查:
- 在 Floating dock 与 Page sidebar 之间切换。外壳改变后,已经收到的回复仍在。
- 选侧栏后,切换左右边缘和收起入口的位置。
- 收起外壳,再点“总结未完成待办”。外壳展开并发送请求。
- 从页面发起一次提问,再打开启动按钮。两条消息在同一段历史中。
- 查看页面动作。两个任务按钮没有出现在模型工具清单里。
运行确定性检查:
pnpm tutorial:check v3-13测试覆盖外壳切换、侧栏参数、任务入口不注册为动作、共享历史和中英文文案。
常见失败
每个区域各挂一个助手。 两个 dock 看起来像一个产品,直到用户在两处问同一个问题,得到两段互不相干的历史。更稳妥的做法是移动一个外壳,或把多个任务入口指向同一个 surface。
把任务按钮伪装成工具。 不要为了让模型“能叫自己”而给 v-task 按钮再加一条 v-tool。若模型需要执行某个业务操作,应直接声明那个操作;若按钮是给人用的,就让它留在动作清单之外。
把布局参数当成权限。 side="left" 或自定义 launcher-label 只改变展示。限制助手能力要靠动作声明、策略、审批和模型网关。

