语言版本: English
Local API 不只适合脚本,也适合“本地托管的插件前端”:
- 文档编辑器加载项
- Obsidian 辅助面板
- 本地 Web 控制台
- 自动化工具配置页
- 任何需要在本机展示 UI、同时访问 Lattice 文献数据的插件
Lattice 除了 /api/v1/... 之外,还会暴露:
/plugins/{name}/...
这意味着你可以把一个插件当作“纯静态网站”部署到 Lattice 的插件目录里,然后由 Lattice 自己通过本地 HTTP 服务把它托管出来。
示例:
http://127.0.0.1:29467/plugins/my-plugin/index.html
http://127.0.0.1:29467/plugins/my-plugin/app.js
http://127.0.0.1:29467/plugins/my-plugin/assets/icon.png
好处很直接:
- 插件页面和
/api/v1同源 - 不需要额外起一个本地 Web 服务器
- 不需要处理额外的跨域问题
- 打包和分发方式简单
- 更容易做成“用户装完就能跑”的桌面插件
对沙盒版 Lattice 来说,插件会被放进应用容器里的 Application Support 目录。典型路径如下:
~/Library/Containers/com.aurelian.Lattice/Data/Library/Application Support/Plugins/<plugin-name>/
部署完成后,对应访问地址就是:
http://127.0.0.1:<port>/plugins/<plugin-name>/...
Lattice 对插件内容没有强制的框架要求。你只需要准备静态资源即可。
一个简单插件可以长这样:
my-plugin/
index.html
app.js
styles.css
assets/
icon.png
如果你的源码仓库里还保留了构建源文件、模板、脚本等,建议在安装阶段把“最终可直接访问的静态文件”复制到插件目录根部。
Local API 已经覆盖插件常用的资源类型,包括:
.html.js.css.json.png.svg.xml.csl
因此一个带前端、样式、图标、CSL 模板的插件可以直接跑起来。
如果插件页面本身就是从 /plugins/{name}/... 打开的,推荐始终使用相对路径调用 API:
async function requestJson(path, init = {}) {
const headers = {
Accept: "application/json",
...(init.headers ?? {})
};
const response = await fetch(`/api/v1${path}`, {
...init,
method: init.method ?? "GET",
headers
});
if (!response.ok) {
const payload = await response.json().catch(() => null);
throw new Error(payload?.error || `${response.status} ${response.statusText}`);
}
return await response.json();
}
export async function getBridgeStatus() {
return requestJson("/status");
}
export async function searchLattice(query, limit = 10) {
return requestJson(`/search?q=${encodeURIComponent(query ?? "")}&limit=${encodeURIComponent(String(limit))}`);
}
export async function fetchPaperSnapshot(id) {
return requestJson(`/papers/${encodeURIComponent(id)}`);
}
export async function getCollections() {
return requestJson("/collections");
}
export async function getTags() {
return requestJson("/tags");
}
export async function lookupMetadata(params) {
const query = new URLSearchParams();
for (const [key, value] of Object.entries(params ?? {})) {
if (value) {
query.set(key, value);
}
}
return requestJson(`/metadata?${query.toString()}`);
}
export async function createPaper(body) {
return requestJson("/papers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
}
export async function uploadPaperPdf(id, pdfBytes, { force = false } = {}) {
const suffix = force ? "?force=true" : "";
const response = await fetch(`/api/v1/papers/${encodeURIComponent(id)}/pdf${suffix}`, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: pdfBytes
});
if (!response.ok) {
const payload = await response.json().catch(() => null);
throw new Error(payload?.error || payload?.reason || `${response.status} ${response.statusText}`);
}
return await response.json();
}这也是推荐的标准接入方式。
不要假设所有写接口在所有用户机器上都可用。
推荐模式:
- 先调
/api/v1/status - 检查
capabilities - 只有当其中包含
create-paper时,才展示创建 UI 或发送创建请求 - 只有当其中包含
pdf-upload时,才展示原始 PDF 字节上传 UI - 如果你依赖重复策略与更丰富的成功响应,检查
create-paper-v2
示例:
export async function getCapabilities() {
const status = await getBridgeStatus();
return new Set(status.capabilities ?? []);
}
export async function canCreatePapers() {
return (await getCapabilities()).has("create-paper");
}
export async function canUploadPdfBytes() {
return (await getCapabilities()).has("pdf-upload");
}对于写入型插件,公开 Local API 已经暴露了常用基础能力:
- 调
/api/v1/collections填充文献集选择器 - 调
/api/v1/tags填充标签选择器或自动补全 - 调
/api/v1/metadata,传入doi、arxivId、isbn、title之一,先预填创建或替换表单
先用 curl 或浏览器验证:
curl http://127.0.0.1:29467/api/v1/status你可以用任意技术栈:
- 原生 HTML/CSS/JS
- React / Vue / Svelte 构建后的静态产物
- Office Add-in 前端
- 其他可以产出静态文件的方案
把最终产物放到:
.../Application Support/Plugins/<plugin-name>/
http://127.0.0.1:29467/plugins/<plugin-name>/index.html
例如:
- 某些宿主需要 manifest 或注册文件
- 某些桌面工具可能需要自定义协议或 WebView 入口
一个非常稳的模式是:
- UI 页面由 Lattice 托管在
/plugins/<name>/... - 页面通过相对路径访问
/api/v1/... - 宿主程序只负责打开该页面
- 插件内部自己缓存必要的文献详情与样式配置
这种模式的优点是耦合简单:
- Lattice 只负责本地数据与静态资源托管
- 插件自己负责 UI、状态、渲染和宿主交互
当前读接口是面向引用场景设计的,返回的数据刻意比 Lattice 内部完整文献模型更小。
读接口当前不会暴露:
abstractcollectionstagspdfPathpdfURLlatticeURL
这带来几个直接结论:
- 如果插件只需要在 Lattice 中打开一篇文献,可以根据
id自行拼出lattice://paper/{id} - 如果插件需要真实的文件系统 PDF 路径,当前 Local API 不提供
pdfPath目前是POST /api/v1/papers接受的输入字段,不是GET /api/v1/papers/{id}返回的字段- 原始 PDF 字节上传是单独的
PUT /api/v1/papers/{id}/pdf流程,并由pdf-uploadcapability 控制,不属于文献详情负载本身
默认不适合。Local API 设计成“本地来源、本地集成”的模型,不是给远程网页公开调用的。
不适合。写入能力是可选的,用户必须显式关闭 Read-Only Mode,客户端也应该以 /status.capabilities 为准做能力判断。
也不适合。当前 Local API 仍然以引用、搜索、选文献和受控创建文献为核心目标。
- 先用
/api/v1/status判断是不是连通性问题 - 搜索不符合预期时,先直接请求
/api/v1/search - 插件页面打不开时,先检查插件资源是否真的复制到了插件目录
- 静态页面能打开但 API 失败时,优先检查当前端口是否和 Lattice 设置页一致
- 如果
POST /api/v1/papers返回403,优先检查用户是否已关闭Read-Only Mode - 如果创建成功但
pdfAttached是false,查看返回的warnings数组里是否有 Trusted Folders 或 PDF 校验失败原因 - 如果
PUT /api/v1/papers/{id}/pdf返回409,决定你的 UI 是否应使用?force=true重试 - 如果
PUT /api/v1/papers/{id}/pdf返回503,先让用户配置可用的 PDF 上传目标 - 如果宿主程序缓存了入口地址,修改端口后通常需要重新注册或重新安装插件