Files
paseo/public-docs/plugins/sidebar-views.zh-CN.md
T
Mohamed Boudra 62bd21214b feat(plugin): add sidebar view examples and ref actions
Keep sidebar layout and gestures inside the plugin while routing entity handles to their owning hosts. Examples cover empty projects, pinned workspaces, task sections, nested agents and device-local label folders.
2026-11-06 09:22:19 +01:00

8.1 KiB
Raw Blame History

构建侧边栏视图

侧边栏视图替换 Paseo 页眉与页脚之间的内容。插件接收主题令牌、当前实体引用和导航方法,使用 React Native 绘制自己的界面。安装侧边栏视图示例,体验项目列表、按任务分组的 Agent、嵌套 Agent 和标签文件夹。

注册视图

import type {
  PluginClientContext,
  PluginSidebarViewProps,
  WorkspaceRef,
} from "@getpaseo/plugin/client";
import { useWorkspaces, useWorkspace } from "@getpaseo/plugin/client";
import { Pressable, ScrollView, Text } from "react-native";

function WorkspaceRow({
  reference,
  navigation,
  theme,
}: {
  reference: WorkspaceRef;
  navigation: PluginSidebarViewProps["navigation"];
  theme: PluginSidebarViewProps["theme"];
}) {
  const workspace = useWorkspace(reference);
  if (!workspace) return null;
  return (
    <Pressable onPress={() => navigation.openWorkspace(reference)}>
      <Text style={{ color: theme.colors.foreground }}>{workspace.title ?? workspace.name}</Text>
    </Pressable>
  );
}
function Workspaces({ navigation, theme }: PluginSidebarViewProps) {
  const workspaces = useWorkspaces({ orderBy: "name" });
  return (
    <ScrollView>
      {workspaces.map((reference) => (
        <WorkspaceRow
          key={reference.key}
          reference={reference}
          navigation={navigation}
          theme={theme}
        />
      ))}
    </ScrollView>
  );
}
export default function contribute(client: PluginClientContext) {
  return client.addSidebarView({ id: "workspaces", title: "My workspaces", Component: Workspaces });
}

注册后,用户可以在设置 → 侧边栏 → 侧边栏视图中选择该视图。安装插件不会切换当前视图。页眉导航、插件页眉项、提示区域和页脚保持可见。所选视图缺失或被禁用时,显示默认视图。视图抛出异常时,显示带有使用默认视图按钮的占位界面。

视图同时展示所有已连接主机的数据。active.workspace 和 active.agent 是引用或 null。将引用传给 navigation.openWorkspace、openAgent、openTerminal 或 openProject;openProject 打开新建工作区流程。导航会关闭紧凑布局的侧边栏。如果插件自行执行导航,完成后调用 dismiss()。

查询引用,逐行读取

useWorkspaces、useAgents、useProjects 和 useTerminals 返回不可变的引用数组。引用包含 kind、hostId、id 和跨主机唯一的 key。在一次应用会话中,同一实体使用同一个冻结引用。在行组件内将它传给 useWorkspace、useAgent、useProject 或 useTerminal。实体删除后返回 null。

const projects = useProjects({ orderBy: "displayName" }); // Includes empty projects.
const agents = useAgents({
  hosts: [hostId],
  where: (agent) => agent.workspace.key === workspaceRef.key,
  orderBy: ["lastActivityAt", "desc"],
});
const folders = useWorkspaces({
  groupBy: (workspace) => workspace.labels, // A workspace can appear in several folders.
});

每个查询都支持 hosts(服务器 ID)、where、orderBy 和 groupBy。省略 hosts 时查询所有已连接主机。orderBy 接受快照字段、[field, "asc" | "desc"] 或比较函数。空值排在最后。比较函数会在数据变化时执行,应保持开销较小。

groupBy 返回字符串、{ key, title?, color? } 或这些值组成的数组。分组结果包含 key、title、color 和 items。分组按排序后首次出现的顺序排列;重复的键使用第一个标题。返回空数组表示该项不属于任何分组。

引用序列不变时,数组对象保持不变。分组的元数据和项序列不变时,分组对象保持不变。工作区状态变化只更新对应行,不会重新渲染整个列表。分组变化会更新受影响的分组。查询对象和函数可以内联编写,无需自行管理订阅。

实体钩子支持选择器和可选的相等比较函数:

const workspace = useWorkspace(ref); // Interned snapshot, compared by identity.
const name = useWorkspace(ref, (item) => ({ title: item.title })); // Shallow equality.
const selected = useWorkspace(ref, selector, equals);

没有选择器时,自定义相等比较函数接收快照;有选择器时接收选择结果。现有工作区面板仍可向 useWorkspace 和 useAgent 传入主机本地的字符串 ID。跨主机视图应使用引用。useHosts() 保留现有的 serverId、label 和连接 status 字段。

工作区快照包含 project、pinnedAt、labels、background、branch、lastPromptAt、parentAgent 和 createdAt(旧主机上为 null)。使用 indicator 和本地化的 indicatorLabel 显示 Paseo 的状态。status 保持 daemon 原始状态。Agent 快照包含工作区和父 Agent 引用。项目快照提供 displayName、rootPath、kind 和 iconRevision。

列出终端

终端示例支持选择主机、列出终端、聚焦终端标签页,以及通过句柄关闭终端。useTerminals({ hosts: [hostId] }) 要求主机声明 terminalCollection 能力。查询不支持该能力的主机时,终端读取会抛出要求更新主机的错误;其他实体钩子继续工作。将主机选择器放在错误边界之外,方便用户切换主机。省略 hosts 时,所有已连接主机都必须支持该能力。

终端快照包含 workspace、title、isScript 和 createdAt。在时间戳跟踪引入前启动的 worker 所保留的终端,其 createdAt 为 null。脚本归属来自工作区脚本运行时条目。主机观察跟踪终端创建、关闭、标题变化和重新连接,并在最后一个终端读取订阅者离开时释放。

通过引用执行操作

usePaseo() 将基于引用的操作发送到引用所属的主机:

const paseo = usePaseo();
await paseo.workspaces.ref(workspaceRef).setPinned(true);
await paseo.workspaces.ref(workspaceRef).setLabel("Later", { assigned: true });
await paseo.workspaces.ref(workspaceRef).setLabel("Inbox", { assigned: false });
paseo.projects.ref(projectRef).open();
await paseo.projects.ref(projectRef).createWorkspace();
paseo.terminals.ref(terminalRef).focus();
await paseo.terminals.ref(terminalRef).kill();

工作区和 Agent 句柄保留现有方法。分配新标签会创建该标签;分配已有标签会保留其颜色。标签文件夹示例先添加目标标签,再移除源标签,因此分配失败时仍保留原文件夹。插件负责移动手势和错误显示。

在设备上保存布局选择

定义 scope: "device" 的设置文档,保存折叠文件夹或手动排序。useSettings 通过与主机设置相同的修订版本 API 读取和写入。设备文档属于插件安装和设置 ID,在应用重启后保留,并由该安装的客户端界面共享。它们不会同步到其他设备。需要由客户端通过 daemon 共享的数据使用 scope: "host"。

匹配应用的信息密度

使用 React Native 原语绘制界面。SDK 不导出侧边栏视图的行、分区、列表、徽标或拖拽辅助组件。现有页眉和页脚组件包属于独立界面。

紧凑行的最小高度为 28,水平内边距为 12;主标签字号为 12、行高为 16,次标签字号为 11。使用以下主题令牌:

行元素 主题令牌
主标签 colors.foreground
次标签、分区标题 colors.foregroundMuted
选中行背景 colors.surface2
分隔线 colors.border
操作或焦点强调色 colors.accent
成功指示 colors.statusSuccess
需要输入指示 colors.statusWarning
失败指示 colors.statusDanger

为可点击行提供无障碍标签,并在触摸布局中显示操作控件。示例仅使用插件自己的组件和传给视图的主题令牌。