@xiongtao.xt/tau-plugins 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -2
  3. package/client/{chunk-ECEBBIHP.js → chunk-SVY5LBAT.js} +44 -0
  4. package/docs/README.md +43 -0
  5. package/docs/ai.md +169 -0
  6. package/docs/backend.md +168 -0
  7. package/docs/editor.md +457 -0
  8. package/docs/excerpts.md +126 -0
  9. package/docs/flashcards.md +127 -0
  10. package/docs/getting-started.md +134 -0
  11. package/docs/mcp.md +173 -0
  12. package/docs/plugin-basics.md +209 -0
  13. package/docs/providers/README.md +108 -0
  14. package/docs/providers/images.md +65 -0
  15. package/docs/providers/publish.md +144 -0
  16. package/docs/providers/sync.md +75 -0
  17. package/docs/providers/tts.md +62 -0
  18. package/docs/providers/upload.md +77 -0
  19. package/docs/providers/video.md +118 -0
  20. package/docs/release.md +130 -0
  21. package/docs/services.md +159 -0
  22. package/docs/site-data.md +90 -0
  23. package/docs/skills.md +129 -0
  24. package/docs/storage-and-network.md +62 -0
  25. package/docs/ui.md +335 -0
  26. package/docs/user-data.md +389 -0
  27. package/docs/whiteboard.md +290 -0
  28. package/package.js +1 -1
  29. package/package.json +1 -1
  30. package/publish-artifacts.js +1 -1
  31. package/types/packages/plugins/src/categories.d.ts +3 -0
  32. package/types/packages/plugins/src/contract.d.ts +0 -2
  33. package/types/packages/plugins/src/index.d.ts +0 -1
  34. package/types/packages/plugins/src/package.d.ts +2 -0
  35. package/types/packages/ui/src/primitives/Tabs.d.ts +1 -3
  36. package/AI.md +0 -116
  37. package/API.md +0 -881
  38. package/BACKEND.md +0 -68
  39. package/DEVELOPMENT.md +0 -173
  40. package/EDITOR.md +0 -192
  41. package/FLASHCARDS.md +0 -92
  42. package/IMAGES.md +0 -16
  43. package/MCP.md +0 -118
  44. package/NOTIFICATIONS.md +0 -48
  45. package/PUBLISH-DATA.md +0 -48
  46. package/PUBLISH.md +0 -50
  47. package/SERVICES.md +0 -119
  48. package/SYNC.md +0 -37
  49. package/TTS.md +0 -20
  50. package/UPLOAD.md +0 -35
  51. package/VIDEO.md +0 -65
  52. package/WHITEBOARD.md +0 -115
  53. package/types/packages/plugins/src/skills.d.ts +0 -8
package/docs/ui.md ADDED
@@ -0,0 +1,335 @@
1
+ # 界面
2
+
3
+ 插件可以通过四种方式出现在 Tau 的界面里:
4
+
5
+ - **命令**:出现在 Ctrl+K 命令面板中,用户搜索后执行。
6
+ - **页面**:一个完整的 React 页面,和 Tau 自己的页面一样参与前进、后退。
7
+ - **侧边栏入口**:在侧边栏放一个条目,点击打开某个页面。
8
+ - **设置**:插件详情页「设置」中的一个区域。
9
+
10
+ 另外,插件可以随时发送**通知**。
11
+
12
+ 这些都在 `activate(context)` 里注册,插件停用时自动撤销。
13
+
14
+ ## 命令
15
+
16
+ ```ts
17
+ context.commands.register({
18
+ id: "reset-count",
19
+ title: "重置计数",
20
+ run: async () => {
21
+ await context.storage.set("count", 0);
22
+ return null;
23
+ },
24
+ });
25
+ ```
26
+
27
+ | 字段 | 说明 |
28
+ | ------- | ------------------------------------------------------------------- |
29
+ | `id` | 局部 ID,见 [ID 规则](plugin-basics.md#id-规则) |
30
+ | `title` | 命令标题,用户在命令面板里按它搜索 |
31
+ | `run` | 执行函数,可以是异步的。返回值必须是 JSON 值,没有结果时返回 `null` |
32
+
33
+ - 注册时不会执行命令,用户选中它时才调用 `run`。
34
+ - `run` 的参数 `input` 不保证有内容,命令面板执行时不会传自定义参数。需要用户输入时,打开一个页面或设置让用户填写。
35
+ - `run` 抛出异常表示执行失败。耗时较长的命令应该在界面上给出进度或结果反馈。
36
+
37
+ ## 页面
38
+
39
+ ```tsx
40
+ import type { PluginPageProps } from "@tau/plugins";
41
+
42
+ function StatsPage({ context }: PluginPageProps) {
43
+ return <h1>统计</h1>;
44
+ }
45
+
46
+ // activate 内
47
+ const stats = context.pages.register({ id: "stats", title: "统计", component: StatsPage });
48
+ await stats.open();
49
+ ```
50
+
51
+ | 字段 | 说明 |
52
+ | ----------- | -------------------------------------------------------------------------- |
53
+ | `id` | 局部 ID |
54
+ | `title` | 页面标题 |
55
+ | `component` | React 组件,收到 `context`、`params`、`pageContext` 三个属性 |
56
+ | `match` | 可选。默认 `"single"`,页面只有一个实例;`"params"` 表示按参数区分多个实例 |
57
+ | `getId` | 可选。`match: "params"` 时,用它从参数算出实例身份;默认按全部参数区分 |
58
+
59
+ `register` 返回页面句柄,提供 `open(params?)` 和 `dispose()`。用 `open` 打开页面,不要自己操作浏览器地址或历史记录。
60
+
61
+ ### 带参数的页面
62
+
63
+ 同一个页面需要展示不同对象(例如不同的书)时,使用 `match: "params"`:
64
+
65
+ ```tsx
66
+ function BookPage({ params }: PluginPageProps) {
67
+ return <h1>书籍:{String(params.bookId)}</h1>;
68
+ }
69
+
70
+ const book = context.pages.register({
71
+ id: "book",
72
+ title: "书籍",
73
+ component: BookPage,
74
+ match: "params",
75
+ getId: params => String(params.bookId),
76
+ });
77
+
78
+ await book.open({ bookId: "42" });
79
+ ```
80
+
81
+ - 参数是一个扁平对象,值只能是字符串、有限数字或布尔值,不支持数组、嵌套对象和 `null`。
82
+ - 已经打开的同一实例会被复用,不会重复打开。
83
+ - 有了 `getId`,`{ bookId: "42", tab: "notes" }` 和 `{ bookId: "42" }` 会打开同一个实例;没有 `getId` 时它们是两个实例。
84
+
85
+ ### 向 AI 提供页面上下文
86
+
87
+ 用户在你的页面上和 AI 对话时,AI 可以通过 Tau 内置的「获取当前上下文」工具了解用户正在看什么。用 `pageContext.register` 告诉 AI 当前页面的状态:
88
+
89
+ ```tsx
90
+ import { useEffect, useState } from "react";
91
+ import type { PluginPageProps } from "@tau/plugins";
92
+
93
+ function BookPage({ params, pageContext }: PluginPageProps) {
94
+ const [chapter, setChapter] = useState(1);
95
+
96
+ useEffect(() => {
97
+ const registration = pageContext.register(() => ({
98
+ bookId: String(params.bookId),
99
+ chapter,
100
+ }));
101
+ return () => {
102
+ registration.dispose();
103
+ };
104
+ }, [pageContext, params.bookId, chapter]);
105
+
106
+ // ...
107
+ }
108
+ ```
109
+
110
+ - 提供函数返回任意 JSON 值,内容和形状完全由你决定。它应该只读取现有状态,不发起请求,也不修改数据。
111
+ - 状态变化时像示例一样重新注册,避免提供函数读到旧值。
112
+ - 每个页面实例同一时间只有一个提供函数。页面隐藏、卸载或插件停用时会自动清理。
113
+ - 只有当前显示的页面实例会被读取。
114
+
115
+ 页面上下文只是让 AI 知道用户在看什么。需要让 AI 能对页面执行操作,另外用 [MCP 工具](mcp.md)提供。
116
+
117
+ ## 侧边栏
118
+
119
+ 侧边栏条目关联一个已注册的页面,点击时打开它,并在该页面显示时高亮。
120
+
121
+ ```ts
122
+ import { SIDEBAR_GROUPS } from "@tau/plugins";
123
+
124
+ const page = context.pages.register({ id: "counter", title: "计数", component: CounterPage });
125
+ context.sidebar.registerItem({
126
+ id: "counter",
127
+ title: "计数",
128
+ icon: CounterIcon,
129
+ group: SIDEBAR_GROUPS.daily,
130
+ page,
131
+ });
132
+ ```
133
+
134
+ | 字段 | 说明 |
135
+ | -------- | -------------------------------------- |
136
+ | `id` | 局部 ID |
137
+ | `title` | 入口名称 |
138
+ | `page` | 本次启用中注册的页面句柄 |
139
+ | `params` | 可选。点击时传给页面的参数 |
140
+ | `icon` | 可选。图标组件,省略时使用默认插件图标 |
141
+ | `group` | 可选。所在分组,省略时放进「插件」分组 |
142
+
143
+ ### 分组
144
+
145
+ 放进 Tau 已有的分组时,使用 `SIDEBAR_GROUPS` 常量:
146
+
147
+ | 常量 | 分组 |
148
+ | -------------------------- | ---- |
149
+ | `SIDEBAR_GROUPS.daily` | 日常 |
150
+ | `SIDEBAR_GROUPS.knowledge` | 知识 |
151
+ | `SIDEBAR_GROUPS.learning` | 学习 |
152
+ | `SIDEBAR_GROUPS.ai` | AI |
153
+ | `SIDEBAR_GROUPS.sharing` | 分享 |
154
+ | `SIDEBAR_GROUPS.plugins` | 插件 |
155
+
156
+ 也可以创建自己的分组,它会显示在 Tau 的分组之后:
157
+
158
+ ```ts
159
+ const group = context.sidebar.registerGroup({ id: "habits", title: "习惯" });
160
+ context.sidebar.registerItem({ id: "water", title: "喝水", group: group.id, page });
161
+ ```
162
+
163
+ 引用自建分组时使用返回的 `group.id`,不要直接写 `"habits"`。没有条目的分组不显示;分组被撤销后,其中的条目移到「插件」分组。
164
+
165
+ 同一个带参数的页面可以注册多个入口,分别传不同的 `params`。
166
+
167
+ ### 图标
168
+
169
+ `icon` 接收组件本身(`icon: CounterIcon`),不是 `<CounterIcon />`,也不是图片地址。图标是一个线性单色的 SVG 组件:
170
+
171
+ ```tsx
172
+ import type { PluginIconProps } from "@tau/plugins";
173
+
174
+ export function WaterDropIcon({ size = 24, strokeWidth = 2, ...props }: PluginIconProps) {
175
+ return (
176
+ <svg
177
+ width={size}
178
+ height={size}
179
+ viewBox="0 0 24 24"
180
+ fill="none"
181
+ stroke="currentColor"
182
+ strokeWidth={strokeWidth}
183
+ strokeLinecap="round"
184
+ strokeLinejoin="round"
185
+ {...props}
186
+ >
187
+ <path d="M12 3C9 7 5 11 5 15a7 7 0 0 0 14 0c0-4-4-8-7-12Z" />
188
+ </svg>
189
+ );
190
+ }
191
+ ```
192
+
193
+ - Tau 会传入 `size`、`strokeWidth`、`className` 和 `aria-hidden`,图标要把它们透传给 `<svg>`。
194
+ - 颜色用 `currentColor`,这样会跟随明暗主题和选中状态。不要写死宽高和颜色。
195
+ - 也可以直接使用 [lucide-react](https://lucide.dev) 等图标库的组件(先 `pnpm add lucide-react`)。
196
+
197
+ ## 设置
198
+
199
+ ```tsx
200
+ import type { PluginSettingsProps } from "@tau/plugins";
201
+
202
+ function Preferences({ context }: PluginSettingsProps) {
203
+ // 用 context.storage 读写设置值
204
+ }
205
+
206
+ const preferences = context.settings.register({
207
+ id: "preferences",
208
+ title: "计数设置",
209
+ description: "调整计数的步长",
210
+ component: Preferences,
211
+ });
212
+ ```
213
+
214
+ | 字段 | 说明 |
215
+ | ------------- | --------------------------------------------------------------- |
216
+ | `id` | 局部 ID |
217
+ | `title` | 设置区域的标题 |
218
+ | `description` | 可选。说明文字 |
219
+ | `component` | React 组件,收到 `context` |
220
+ | `target` | 服务提供方的配置表单才需要,见[服务提供方](providers/README.md) |
221
+
222
+ 设置区域显示在插件详情页的「设置」里。`register` 返回的句柄提供 `open()`,可以从命令或页面里跳转过去:
223
+
224
+ ```ts
225
+ context.commands.register({
226
+ id: "open-preferences",
227
+ title: "打开计数设置",
228
+ run: async () => {
229
+ await preferences.open();
230
+ return null;
231
+ },
232
+ });
233
+ ```
234
+
235
+ 设置值不会自动保存,组件需要自己用 [`context.storage`](storage-and-network.md) 读写。页面和设置同时显示同一份数据时,保存后要刷新另一处的状态,`storage` 本身不会通知变化。模板用 React Query 管理读取、保存和失败状态,可以照着写。
236
+
237
+ ## 通知
238
+
239
+ ```ts
240
+ context.notifications.send({
241
+ title: "导入完成",
242
+ body: "新增了 12 条摘录。",
243
+ tone: "ok",
244
+ });
245
+ ```
246
+
247
+ | 字段 | 说明 |
248
+ | --------- | ---------------------------------------------- |
249
+ | `title` | 标题,必填,最多 200 字 |
250
+ | `body` | 可选。正文,最多 5000 字 |
251
+ | `tone` | 可选。`neutral`(默认)、`ok`、`danger` |
252
+ | `mode` | 可选。通知出现的位置,见下表 |
253
+ | `sticky` | 可选。为 `true` 时一直显示,直到用户处理或关闭 |
254
+ | `actions` | 可选。最多两个按钮,其中最多一个主按钮 |
255
+
256
+ | `mode` | 出现的位置 |
257
+ | -------------- | ------------------------------------------------------ |
258
+ | `auto`(默认) | Tau 窗口在前台时显示在窗口内,否则显示在独立的通知窗口 |
259
+ | `in-app` | 总是显示在窗口内 |
260
+ | `system` | 总是显示在独立的通知窗口 |
261
+
262
+ 通知的来源固定显示为你的插件名称。
263
+
264
+ ### 带按钮的通知
265
+
266
+ ```ts
267
+ context.notifications.send({
268
+ title: "扫描完成",
269
+ body: "发现 3 处断开的链接。",
270
+ sticky: true,
271
+ actions: [
272
+ {
273
+ label: "查看",
274
+ primary: true,
275
+ onClick: async () => {
276
+ await reportPage.open();
277
+ },
278
+ },
279
+ ],
280
+ });
281
+ ```
282
+
283
+ | 按钮字段 | 说明 |
284
+ | -------------- | --------------------------------------------------------- |
285
+ | `label` | 按钮文字,最多 100 字 |
286
+ | `onClick` | 点击时执行,可以是异步的;执行期间按钮显示加载状态 |
287
+ | `primary` | 可选。是否为主按钮 |
288
+ | `resolvedText` | 可选。`onClick` 成功后在通知里显示的结果文字,最多 500 字 |
289
+
290
+ `onClick` 失败时,错误显示在通知里。插件停用后,旧通知上的按钮不再可用。
291
+
292
+ ### 撤销通知
293
+
294
+ `send` 返回一个 `Disposable`,调用 `dispose()` 撤销这一条通知。插件停用时,它发出的通知会自动撤销。
295
+
296
+ 发送通知不需要权限,也不需要用户操作触发,适合在异步任务完成时提醒用户。返回句柄只表示通知已经发出,不代表用户已经看到。
297
+
298
+ ## 界面组件与样式
299
+
300
+ 插件界面用 React 编写,可以使用 `@tau/ui` 提供的组件,它们会自动适配 Tau 的主题:
301
+
302
+ ```tsx
303
+ import { useState } from "react";
304
+ import { Button, Dialog, DialogBody, Panel } from "@tau/ui";
305
+
306
+ export function Help() {
307
+ const [open, setOpen] = useState(false);
308
+ return (
309
+ <Panel>
310
+ <Button variant="ghost" onClick={() => setOpen(true)}>
311
+ 查看说明
312
+ </Button>
313
+ <Dialog open={open} onOpenChange={setOpen} title="使用说明">
314
+ <DialogBody>数据保存在当前工作区。</DialogBody>
315
+ </Dialog>
316
+ </Panel>
317
+ );
318
+ }
319
+ ```
320
+
321
+ | 组件 | 常用属性 |
322
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
323
+ | `Button` | `onClick`、`disabled`、`loading`、`loadingText`;`variant` 可选 `primary`、`secondary`、`soft`、`ghost`、`danger`、`danger-soft` |
324
+ | `Panel` | 内容容器 |
325
+ | `Dialog` | `open`、`onOpenChange`、`title`,`footer` 放操作按钮 |
326
+ | `DialogBody` | 弹窗正文 |
327
+
328
+ 完整的组件列表和属性见 `@tau/ui` 的类型提示。
329
+
330
+ 自己写的样式放在 CSS 文件里,在入口中 `import`:
331
+
332
+ - 选择器加上插件专属前缀(如 `.yourname-counter`),不要修改 `body` 或 Tau 自身元素的样式。
333
+ - 颜色、圆角等使用 Tau 的主题变量(如 `var(--tau-accent)`),这样会跟随用户的主题切换。
334
+ - SVG 图片可以直接在 CSS 里用 `url()` 引用,构建时会内联进样式,不需要额外复制文件。
335
+ - 如果你用 Tailwind 构建插件样式,可以导入 `@tau/plugins/tailwind.css`,它把 Tau 的主题变量映射成 Tailwind 语义类(如 `bg-surface`、`text-ink-1`)。Tau 不会替插件生成 Tailwind 样式,需要你在构建时自己编译。