Homepage 自定义 Widget 开发指南:从组件到代理的完整实现路径
Widget(小组件)是 Homepage 的核心组成部分,负责展示关于你的系统、服务与环境的关键信息。无论是为某个自托管服务新增一块状态面板,还是把第三方 API 的数据呈现在首页上,本质上都是在编写一个 Widget。本篇指南以 Homepage 官方 Widget 开发文档(docs/widgets/authoring/index.md)为主线,结合仓库内的真实源码与测试,完整讲解一个自定义 Widget 从目录创建、元数据定义、组件渲染、API 拉取、代理处理器到国际化落地的全过程。读完本文,你将能够独立为 Homepage 编写并注册一个可用的服务型 Widget,并理解其背后的安全白名单机制与数据校验流程。
上图展示了 Homepage Widget 组件各区块(Block)的布局结构,出自官方组件指南 component.md。
一、Widget 在 Homepage 中的定位
在进入编码之前,先明确 Widget 的类型与适用场景。Homepage 共有两类 Widget(详见 docs/widgets/index.md):
- 服务型 Widget(Service Widget):用于展示某个服务(通常是 Web 服务或 API)的状态,在
services.yaml中定义。例如为 Plex、Uptime Kuma 等配置的监控面板即属此类。 - 信息型 Widget(Info Widget):用于在页面头部展示系统或环境信息,在
widgets.yaml中定义,例如 openmeteo 天气、系统资源占用等。
本文聚焦于服务型 Widget 的开发,其官方教程位于 tutorial.md。整个作者指南(Authoring)系列还包括:
| 子文档 | 主题 | 仓库路径 |
|---|---|---|
| 完整教程 | 从零搭建一个 Widget | tutorial.md |
| 组件指南 | 如何构建 Widget UI | component.md |
| 元数据指南 | 端点、映射、校验规则 | metadata.md |
| API 指南 | 如何发起 API 请求 | api.md |
| 代理指南 | 内置与自定义代理处理器 | proxies.md |
| 国际化指南 | 翻译与本地化 | translations.md |
二、开发环境准备
开发 Widget 前,需要先把 Homepage 跑在本地。项目使用 pnpm 管理依赖(仓库根目录存在 pnpm-lock.yaml 与 pnpm-workspace.yaml),详见 getting-started.md:
pnpm install # 安装依赖
pnpm dev # 启动开发服务器,打开 http://localhost:3000
Homepage 是一个 Next.js 应用,因此 Widget 组件遵循 React/Next.js 的组件模型。代码质量方面还提供了:
pnpm lint # ESLint 检查
pnpm test # Vitest 单元/组件测试
pnpm test:coverage
三、核心教程:创建你的第一个 Widget
官方教程使用 yourwidget 作为占位名称(只能包含小写字母、不含空格),并假设目标 API 的 v1/info 端点返回如下 JSON:
{ "key1": 123, "key2": 456, "key3": 789 }
3.1 定义 Widget 元数据(widget.js)
在 src/widgets 目录下新建 yourwidget 文件夹,并创建 widget.js:
import genericProxyHandler from "utils/proxy/handlers/generic";
const widget = {
api: "{url}/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
info: {
endpoint: "v1/info",
},
},
};
export default widget;
各字段含义如下:
api:API 端点模板。{url}与{endpoint}是运行时占位符,分别替换为服务配置中的url和 mapping 中定义的endpoint。这里最终会拼出http://127.0.0.1:1337/v1/info。proxyHandler:负责实际发起请求的代理处理器函数。genericProxyHandler用于免认证请求,也可以选择其他内置处理器或自定义处理器(详见第七节)。mappings:端点映射对象,每个 key 对应一个端点。组件中通过useWidgetAPI(widget, "info")调用名为info的映射。endpoint:映射实际请求的端点路径。除endpoint外,映射还可携带method、headers、body等属性(见第五节)。
安全警告:所有需要从动态端点拉取数据的 Widget,都必须声明
mappings或allowedEndpoints属性,这是 Homepage 的白名单安全机制,防止 Widget 任意访问外部地址(详见第五节)。
3.2 创建组件(component.jsx)
在 src/widgets/yourwidget 目录下创建 component.jsx。先从导入依赖开始:
import { useTranslation } from "next-i18next"; // 访问翻译字符串
import Container from "components/services/widget/container"; // 组件容器
import Block from "components/services/widget/block"; // 键值对区块
import useWidgetAPI from "utils/proxy/use-widget-api"; // 数据获取 hook
随后逐步构建组件主体:
export default function Component({ service }) {
const { t } = useTranslation(); // 翻译函数
const { widget } = service; // 从 service prop 中解构 widget 元数据
const { data, error } = useWidgetAPI(widget, "info"); // 拉取数据
// 出错时交给 Container 渲染错误信息
if (error) {
return <Container service={service} error={error} />;
}
// 无数据时渲染骨架屏(placeholder)
if (!data) {
return (
<Container service={service}>
<Block label="yourwidget.key1" />
<Block label="yourwidget.key2" />
<Block label="yourwidget.key3" />
</Container>
);
}
// 有数据时渲染真实值,并用 common.number 做数字本地化
return (
<Container service={service}>
<Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />
<Block label="yourwidget.key2" value={t("common.number", { value: data.key2 })} />
<Block label="yourwidget.key3" value={t("common.number", { value: data.key3 })} />
</Container>
);
}
关键点解析:
useWidgetAPI(widget, "info")返回{ data, error }。调用名info必须与widget.js中mappings的 key 一致——mapping 名与真实端点常常相同,但无论如何都必须显式定义。Block的label属性对应翻译 key;无value时显示骨架占位(Block内部通过value === undefined触发animate-pulse动画,见 block.jsx)。t("common.number", { value: ... })使用 Homepage 内置的数字格式化翻译,可以自动适配逗号/小数点的本地化习惯。
3.3 添加翻译字符串
Widget 中所有文本与数值内容都应国际化。编辑 public/locales/en/common.json,在列表末尾为你的 Widget 新增对象:
"yourwidget": {
"key1": "Value 1",
"key2": "Value 2",
"key3": "Value 3"
}
即使你母语不是英语,也只需添加英文翻译;合并后再通过 Crowdin 补充其他语言(详见第八节)。
3.4 注册 Widget
要让 Homepage 认识新 Widget,需要修改两个注册文件(均需保持字母序):
① src/widgets/widgets.js——注册元数据:
import yourwidget from "./yourwidget/widget";
const widgets = {
// ...
yourwidget: yourwidget,
// ...
};
② src/widgets/components.js——注册懒加载组件(使用 Next.js dynamic 导入):
const components = {
// ...
yourwidget: dynamic(() => import("./yourwidget/component")),
// ...
};
这两处注册是 Widget 生效的必要步骤:前者让 services.yaml 中的 type: yourwidget 能解析到元数据,后者让组件能够被按需渲染。
3.5 在 services.yaml 中使用
编辑你的 services.yaml:
- Services:
- Your Widget:
icon: yourwidget.svg
href: https://example.com/
widget:
type: yourwidget
url: http://127.0.0.1:1337
这里的 url 会替换 api 模板中的 {url},最终请求地址为 http://127.0.0.1:1337/v1/info。至此,一个自定义 Widget 已经可以运行在首页上。
四、组件构建原理(Component 深入)
Homepage 的 Widget 组件基于 React,官方提供了一系列 hook 与 UI 组件来保证布局一致。完整的组件示例与逐行拆解见 component.md。
4.1 两个核心 Hook
useTranslation:来自next-i18next,用于翻译文本与数值内容。所有文本和数值都应通过它本地化。useWidgetAPI:负责经代理拉取数据。该 hook 是安全关键路径——所有数据都必须经由服务端代理,避免在浏览器端直接跨域请求(详见第六节)。
4.2 两个核心 UI 组件
<Container>:Widget 的外层包裹组件,提供统一布局。可接收 error prop 渲染错误态:
<Container service={service} error={error}></Container>
从源码 container.jsx 可以看到,Container 还承担了两项额外职责:
- 支持通过
service.widget.fields过滤可见区块(fields数组可以是"resources.cpu"这类带.的通用翻译 key,也可以是"widget_type.field"形式); - 支持基于
blockHighlights设置构建高亮配置,通过BlockHighlightContext传递给子Block。
<Block>:渲染一个键值对区块,接收 label 与 value props:
<Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />
无 value 时渲染骨架占位布局:
<Container service={service}>
<Block label="yourwidget.key1" />
<Block label="yourwidget.key2" />
<Block label="yourwidget.key3" />
</Container>
label 用于在翻译文件中查找翻译 key。从 block.jsx 源码可见,Block 还会读取高亮配置,对命中条件的值附加高亮样式类,实现阈值告警之类的视觉效果。
五、Widget 元数据(Metadata)全面参考
元数据是一个 JS 对象,包含 API 端点、代理处理器与端点映射等全部信息,是 Widget 的数据蓝图。以下属性参考 metadata.md。
5.1 api
字符串,API 端点模板。运行时占位符会被实际值替换:{url} 替换为 Widget 配置中的 url,{endpoint} 替换为 mapping 中的 endpoint 值:
const widgetExample = {
api: "{url}/api/{endpoint}",
};
从 api-helpers.js 的 formatApiCall 实现可以看到,所有 {...} 占位符都会被替换,且 url 会先去除末尾斜杠。模板中的任意 key(如 {key}、{username})都可以从 Widget 配置中取值。
5.2 proxyHandler
负责发起 API 请求的函数。Homepage 内置了多个开箱即用的处理器(位于 src/utils/proxy/handlers),详细对比见第七节。基本用法:
const widgetExample = {
api: "{url}/api/{endpoint}",
proxyHandler: genericProxyHandler,
};
5.3 mappings 与各子属性
mappings 是端点映射对象,可包含多个端点,各自独立配置。
const widgetExample = {
api: "{url}/api/{endpoint}",
mappings: {
// 最终请求:/api/stats?start=...&end=...
stats: {
endpoint: "stats",
validate: ["total", "average"],
params: ["start", "end"],
},
// 最终请求:/api/notices
notices: {
endpoint: "notices",
map: (data) => {
total: asJson(data).length;
},
},
},
};
各子属性详解:
endpoint
字符串,实际请求的端点名,用于替换 api 中的 {endpoint} 占位符。
validate
字符串数组,声明 API 响应中必须存在的 key。若响应缺失任一 key,Widget 不会渲染。其底层实现在 validate-widget-data.js:找到与 endpoint 匹配的 mapping 后,遍历 validate 数组检查 dataParsed[key] === undefined;校验失败会记录错误日志并返回 { error: { message: "Invalid data", ... } }。此外该文件还处理了 Buffer 数据的 JSON 解析(含去除空白字符后的二次尝试)。
mappings: {
stats: {
endpoint: "stats",
validate: ["total", "average"],
},
}
params
字符串数组,声明允许作为查询参数传给 API 的 key。这是白名单机制——未在 params 中声明的参数不会传给上游,防止任意参数注入。组件侧配合 useWidgetAPI 传入值:
const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats", {
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
end: new Date(),
});
服务端在 proxy.js 中解析 req.query.query,仅拼接 mappingParams(以及 optionalParams 中确实传入了值的部分)到端点 URL。
map
函数,在 API 原始响应传给组件之前进行数据变换。配合 api-helpers.js 中的 asJson、jsonArrayFilter 等工具使用:
import { asJson } from "utils/proxy/api-helpers";
mappings: {
notices: {
endpoint: "notices",
map: (data) => {
total: asJson(data).length;
},
},
warnings: {
endpoint: "notices",
map: (data) => {
total: asJson(data).filter((alert) => alert.type === "warning").length;
},
},
}
注意 map 函数以 (data) => { total: ...; } 的对象字面量简写形式编写(即 (data) => ({ total: ... }))。map 在服务端代理处理中于校验通过后执行,见 generic.js。
method
请求使用的 HTTP 方法,默认 GET。注意:POST 请求不允许通过 Widget API 直接发起,需要自定义代理处理器(见第七节)。mappings 中声明了 method 时,服务端 proxy.js 会校验请求方法一致性,并据此改写 req.method 与 req.body。
headers
对象,附加到请求的额外请求头:
mappings: {
stats: {
endpoint: "stats",
headers: {
"Content-Type": "application/json",
},
},
}
body
对象,请求体内容,仅在 method 为 POST/PUT 时使用。典型场景是 GraphQL 查询:
mappings: {
stats: {
endpoint: "graphql",
body: {
query: `
query {
stats {
total
average
}
}
`,
},
headers: {
"Content-Type": "application/json",
},
},
}
5.4 allowedEndpoints
当端点校验规则简单到可以用正则表达时,可用 allowedEndpoints 替代 mappings:
const widgetExample = {
api: "{url}/api/{endpoint}",
allowedEndpoints: /^stats|notices$/,
};
服务端 proxy.js 用 RegExp.test() 校验请求端点,不匹配则返回 403 "Unmapped proxy request."。仓库中已有多个实际案例,例如 glances/widget.js 使用 allowedEndpoints: /^\d+\/(quicklook|diskio|...)$/ 限制访问路径,gamedig/widget.js 与 fritzbox/widget.js 使用 /status/。
安全提示(再次强调):
mappings或allowedEndpoints是动态端点 Widget 的必填项。Homepage 采用白名单策略,确保 Widget 只能访问被明确允许的端点。
六、通过 useWidgetAPI 发起 API 请求
useWidgetAPI 是组件与数据之间的桥梁,实现位于 use-widget-api.js。它接收三个参数:
| 参数 | 说明 |
|---|---|
widget |
Widget 元数据对象,通常从 service prop 传入 |
endpoint |
要请求的端点名(即 metadata 中 mapping 的 key);省略时直接请求 api 定义的端点 |
params(可选) |
传给 API 的查询参数对象,必须已在 metadata 的 params 中白名单化 |
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { data, error } = useWidgetAPI(widget, "stats", { start: "2021-01-01", end: "2021-12-31" });
}
底层流程:
formatProxyUrl(见 api-helpers.js)把调用构造成/api/services/proxy?group=...&service=...&index=...&endpoint=...&query=...;- 该请求由 src/pages/api/services/proxy.js 的服务端 handler 接管,它查找 Widget 元数据、执行 mapping 分发(
endpoint重写、params白名单过滤、headers附加、map变换); - 最终由
proxyHandler发起真实的上游请求并返回结果。
因为所有上游请求都发生在服务端代理内,Widget 组件本身从不直接触碰外部 API,这就是官方强调的"useWidgetAPI 必须走代理、对安全至关重要"的原因。该 hook 基于 swr 实现(支持 refreshInterval 配置),并统一把数据层的 error 提升为顶层返回,便于组件判断。
七、代理处理器(Proxy Handler)指南
Homepage 内置的代理处理器位于 src/utils/proxy/handlers 目录,每个处理器都附带对应测试文件(*.test.js)。
7.1 genericProxyHandler
发起免认证请求的通用处理器,是最常用的选择:
import genericProxyHandler from "utils/proxy/handlers/generic";
const widgetExample = {
api: "{url}/api/{endpoint}",
proxyHandler: genericProxyHandler,
};
它同样支持从 Widget 配置中传递 API key 等参数(通过 api 模板占位符注入):
// widget.js
const widgetExample = {
api: "{url}/api/{endpoint}?key={key}",
proxyHandler: genericProxyHandler,
};
# services.yaml
- Your Widget:
icon: yourwidget.svg
href: https://example.com/
widget:
type: yourwidget
url: http://example.com
key: your-api-key
从 generic.js 源码可见,该处理器还支持从配置读取 username/password 自动生成 Authorization: Basic ... 头,并支持 requestBody 配置;请求后若响应为非 2xx 状态,会返回带脱敏 URL(sanitizeErrorURL)的错误对象。
7.2 credentialedProxyHandler
通过请求头携带凭据的认证处理器。凭据从 Widget 配置中读取,默认将 key 作为 X-API-Key 头传递;若目标 API 需要其他头名,需要为 credentialedProxyHandler 增加分支或新建处理器:
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widgetExample = {
api: "{url}/api/{endpoint}?key={key}",
proxyHandler: credentialedProxyHandler,
};
- Your Widget:
widget:
type: yourwidget
url: http://127.0.0.1:1337
key: your-api-key
实际实现 credentialed.js 内部针对不同 Widget 类型分发不同的认证头:例如 coinmarketcap 使用 X-CMC_PRO_API_KEY、gotify 使用 X-gotify-Key、大量 Widget(argocd、authentik、linkwarden、tailscale、vikunja 等)使用 Bearer Token、proxmox 使用 PVEAPIToken=、gitlab 使用 PRIVATE-TOKEN,未匹配类型则回落到 X-API-Key。这也是文档中"如需不同头名,请为该处理器增加 case 或新建处理器"的由来。
7.3 jsonrpcProxyHandler
用于发起带认证的 JSON-RPC 请求,支持用户名+密码或 API Token 两种认证方式。endpoint 即要调用的方法名,queryParams 即参数:
// widget.js
import jsonrpcProxyHandler from "utils/proxy/handlers/jsonrpc";
const widgetExample = {
api: "{url}/api/jsonrpc",
proxyHandler: jsonrpcProxyHandler,
mappings: {
total: { endpoint: "total" },
average: { endpoint: "average" },
trigger: { endpoint: "trigger.get" },
},
};
组件中调用:
const { data, error } = useWidgetAPI(widget, 'trigger', { "triggerids": "14062", "output": "extend", "selectFunctions": "extend" });
对应 services.yaml 配置(二选一):
- Your Widget:
widget:
type: yourwidget
url: http://127.0.0.1:1337
username: your-username
password: your-password
- Your Widget:
widget:
type: yourwidget
url: http://127.0.0.1:1337
key: your-api-token
实现位于 jsonrpc.js,基于 json-rpc-2.0 客户端构建请求,有凭据时自动附加 Basic 或 Bearer 头,并把 JSON-RPC 错误对象规范化返回。
7.4 synologyProxyHandler
专用于 Synology DSM 服务的认证处理器,请求格式遵循 Synology WebAPI 约定:
import synologyProxyHandler from "utils/proxy/handlers/synology";
const widgetExample = {
api: "{url}/webapi/{cgiPath}?api={apiName}&version={maxVersion}&method={apiMethod}",
proxyHandler: synologyProxyHandler,
mappings: {
system_storage: {
apiName: "SYNO.Core.System",
apiMethod: 'info&type="storage"',
endpoint: "system_storage",
}
},
};
7.5 创建自定义代理处理器
当内置处理器无法满足需求时(例如需要 POST、特殊签名或非标准认证),可以编写自定义处理器。代理处理器是一个函数,接收三个参数:
req:请求对象;res:响应对象;map:将 API 响应映射为 Widget 数据的函数。
函数应返回一个 resolve 到 API 响应的 Promise:
import createLogger from "utils/logger";
import { httpProxy } from "utils/proxy/http";
const logger = createLogger("customProxyHandler");
export default async function customProxyHandler(req, res, map) {
const { url } = req.query;
const [status, contentType, data] = await httpProxy(url);
return res.status(status).send(data);
}
Widget 专属代理放在 src/widgets/<widget>/proxy.js,并在 metadata 中通过 proxyHandler 或 mapping 级 proxyHandler 引用。代理处理器是较复杂的主题,需要良好的 JS 功底和对 Homepage 代码库的理解;新手建议优先使用内置处理器。
7.6 测试代理处理器
代理处理器是回归问题的常见来源(涉及认证、请求格式化、上游 API 的怪癖行为),新增或修改时必须配套测试,重点覆盖:
- 请求构造:正确的 URL/路径、查询参数、请求头与认证信息,且秘密信息不会被意外记入日志;
- 响应映射:Widget/组件期望的载荷结构(包括可选/缺失字段);
- 错误处理:上游非 200、非法 JSON、超时、意外载荷都应产生可预期结果。
测试位置约定:
- 共享处理器位于 src/utils/proxy/handlers,测试文件同名(如
generic.test.js); - Widget 专属代理位于
src/widgets/<widget>/proxy.js,测试文件为src/widgets/<widget>/proxy.test.js。
八、国际化与本地化(Translations)
Widget 内所有文本与数值内容都应翻译并本地化。英文为默认语言,其他语言通过 Crowdin 协作平台维护。Homepage 基于 next-i18next 实现翻译,并扩展了数字、日期等格式化能力,详见 translations.md。
在组件中使用:
import { useTranslation } from "next-i18next";
export default function Component() {
const { t } = useTranslation();
return (
<Container service={service}>
<Block label="yourwidget.key1" />
<Block label="yourwidget.key2" />
<Block label="yourwidget.key3" />
</Container>
);
}
翻译字符串添加在 public/locales/en/common.json 中。
8.1 常用数字格式化翻译
Homepage 提供一组开箱即用的通用翻译 key(实际定义于 public/locales/en/common.json,例如 "bytes": "{{value, bytes}}"、"number": "{{value, number}}"):
| Key | 示例输出 | 说明 |
|---|---|---|
common.bytes |
1,000 B |
字节数格式化 |
common.bits |
1,000 bit |
位数格式化 |
common.bbytes |
1 KiB |
二进制字节 |
common.bbits |
1 Kibit |
二进制位 |
common.byterate |
1,000 B/s |
字节速率 |
common.bibyterate |
1 KiB/s |
二进制字节速率 |
common.bitrate |
1,000 bit/s |
位速率 |
common.bibitrate |
1 Kibit/s |
二进制位速率 |
common.percent |
50% |
百分比 |
common.number |
1,000 |
数字格式化 |
common.ms |
1,000 ms |
毫秒 |
common.date |
2024-01-01 |
日期 |
common.relativeDate |
1 day ago |
相对日期 |
common.duration |
1 day, 1 hour |
时长 |
用法:t("common.bytes", { value: 1024 })。这些 key 配合 i18next 的格式化机制,可自动适配不同地区的数字分隔符。
8.2 常用文本翻译
| Key | 翻译 | 说明 |
|---|---|---|
resources.cpu |
CPU |
CPU 使用率 |
resources.mem |
MEM |
内存使用率 |
resources.total |
Total |
总量 |
resources.free |
Free |
空闲量 |
resources.used |
Used |
已用量 |
resources.load |
Load |
负载值 |
resources.temp |
TEMP |
温度值 |
resources.max |
Max |
最大值 |
resources.uptime |
UP |
运行时间 |
九、测试规范与质量保障
Homepage 使用 Vitest 做单元与组件测试,Widget 开发的测试约定(来自 getting-started.md):
- 组件测试:新增/修改 Widget 应在组件旁提供
component.test.jsx,覆盖加载/占位状态、错误状态和代表性"快乐路径"渲染; - 元数据测试:改动
src/widgets/<widget>/widget.js时,同步更新widget.test.js覆盖配置与映射行为; - 代理测试:有自定义代理时提供
proxy.test.js,验证请求构造、响应映射与错误路径; - 目录注意:测试文件不要放在
src/pages/**下(Next.js 会把其中的文件当作路由),页面测试应放在src/__tests__/pages/**。仓库现有测试即遵循此约定,如 src/tests/pages/api/services/proxy.test.js 中对allowedEndpoints正则匹配的验证。
十、开发新 Widget 的社区准则
为了保证 Widget 生态的协调性与可维护性,官方在 getting-started.md 中对新 Widget 提出了明确的开发规范:
- 以自托管开源软件为优先:Homepage 定位是自托管服务仪表盘,Widget 应主要服务于自托管工具;
- 提交门槛:服务型 Widget 通常需要对应 feature request 讨论获得至少 20 个 up-vote,且拒绝为过年轻(如 <1 年)或影响力过小(如低 Star 数)的项目维护 Widget;
- 布局约束:Widget 只能是一行 Block,宽度不超过 4 个 Block,并遵循其他 Widget 的样式与设计选择;
- 性能约束:尽量减少 API 调用次数;除非绝对必要,避免使用自定义代理;Widget 应保持只读(不得通过 API 进行写入变更);不允许用户手动覆盖"刷新间隔"设置,以防误配置导致性能问题。
遵循这些约束,既能保证 Widget 与 Homepage 整体风格一致,也能避免给上游服务与页面带来不必要的负担。
结语
从 widget.js 元数据、component.jsx 组件、两处注册入口,到 useWidgetAPI 数据拉取、内置代理处理器与国际化落地,本文完整覆盖了 Homepage 自定义 Widget 的全部开发环节。官方完整流程可对照 tutorial.md,属性细节可查阅 metadata.md,代理进阶可阅读 proxies.md。掌握了这套框架,你就能为任意自托管服务打造专属的状态面板,让 Homepage 真正成为"你的"首页。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
