Skip to content

助手入口、布局与任务入口 ​

可运行示例在浏览器中运行本章示例

这里运行与本地夹具相同的 Vue 3 源码。模型请求由服务端网关转发,供应商密钥不会进入浏览器。

本地运行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。

默认浮动入口 ​

最短的写法仍然是:

vue
<template>
  <main>
    <TaskList />
    <AgentDock layout="dock" title="Todo assistant" />
  </main>
</template>

layout="dock" 把启动按钮放在页面角落。展开时显示对话,收起时 runtime 和历史仍然保留,因为对话组件没有随之卸载。

外壳只提供展示选择,不改变助手能做什么。动作仍来自当前页面的 v-tool 声明,确认仍由 kind 与 confirm 决定。

有固定工作栏时使用侧栏 ​

页面本来就有清晰的左右结构时,侧栏通常比浮层更像产品的一部分:

vue
<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 放进一栏:

vue
<section class="workspace">
  <OrderTable />
  <AgentInline class="copilot-column" title="Order copilot" />
</section>

此时宽度、滚动和响应式行为由外层负责。开发者仍然不需要另写一套消息循环。

增加面向人的任务入口 ​

通用启动按钮适合“我想问点什么”。业务页面还需要更具体的入口:“总结未完成待办”“解释这笔订单”“复核队列”。这些按钮属于使用页面的人,不属于 agent。

用 v-task 声明入口:

vue
<button
  type="button"
  class="smart-action"
  v-task="copy.openTasksPrompt"
>
  Summarize open tasks
</button>

也可以传对象:

ts
v-task="{ prompt: 'Explain the risk in order SO-2041.', surface: 'dock' }"

surface 可以省略;页面只有一个助手时不需要指定。只有同时挂载了多个具名 surface、确实需要分流时,才应填写。

入口本身已经在应用逻辑里时,可以用 pling.ask():

ts
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>。

关键不在按钮数量,而在没有按按钮复制运行时:

text
页面按钮 ─┐
页面代码 ─┼─> 已挂载的任务目标 ─> 一份 AgentInline 运行时
启动按钮 ─┘

dock 只登记一次任务目标。v-task 和 pling.ask() 找到这个目标并调用它的 send()。外壳展开,请求进入原有历史,后面的审批与结果也出现在同一段对话中。

因此,入口在 dock 收起时也可以使用。发送任务会自动展开外壳,不需要用户先找到启动按钮。

按产品约束选择外壳 ​

布局通常由应用场景决定,而不是由个人偏好决定:

场景合理默认值
内容页上的偶尔求助浮动 dock
长时间编辑、工作区信息密集侧栏
助手就是主要工作栏页面栅格中的 AgentInline
单条记录上的一个动作记录上的 v-task
已有工具栏或命令面板从代码调用 pling.ask()

不要因为“页面有好几个区域”就挂载多个 AgentDock。一个外壳可以随应用状态移动,多个任务入口可以指向它。只有当产品确实需要具有不同权限、不同历史或不同受众的助手时,多运行时才有意义。

验证 ​

启动示例:

bash
pnpm tutorial:dev v3-13

依次检查:

  1. 在 Floating dock 与 Page sidebar 之间切换。外壳改变后,已经收到的回复仍在。
  2. 选侧栏后,切换左右边缘和收起入口的位置。
  3. 收起外壳,再点“总结未完成待办”。外壳展开并发送请求。
  4. 从页面发起一次提问,再打开启动按钮。两条消息在同一段历史中。
  5. 查看页面动作。两个任务按钮没有出现在模型工具清单里。

运行确定性检查:

bash
pnpm tutorial:check v3-13

测试覆盖外壳切换、侧栏参数、任务入口不注册为动作、共享历史和中英文文案。

常见失败 ​

每个区域各挂一个助手。 两个 dock 看起来像一个产品,直到用户在两处问同一个问题,得到两段互不相干的历史。更稳妥的做法是移动一个外壳,或把多个任务入口指向同一个 surface。

把任务按钮伪装成工具。 不要为了让模型“能叫自己”而给 v-task 按钮再加一条 v-tool。若模型需要执行某个业务操作,应直接声明那个操作;若按钮是给人用的,就让它留在动作清单之外。

把布局参数当成权限。 side="left" 或自定义 launcher-label 只改变展示。限制助手能力要靠动作声明、策略、审批和模型网关。