# 02 · 技术方案 — dsh-free-models-hub > 依据 DSH 官方文档(developer preview,2026-08):插件开发 basic 系列、publish、settings-card cookbook、client-modules / ui-sidebar 参考手册编写。 --- ## 1. 总体架构 ``` ┌─────────────────────────────── 浏览器(DSH Web UI 页面) ───────────────────────────────┐ │ dsh-free-models-hub · client 半边(lib/client.cjs, 懒加载 CJS bundle) │ │ ├─ slots.inject('sidebar.workspaces') → 左侧「免费模型榜」面板 │ │ ├─ 分页器 / 展开详情 / 复制 / toast │ │ ├─ fetch(站长后端 /api/models.php) ←── CORS 白名单 │ │ └─ 一键配置:ctx.settingsScope → llm-pi-ai.providers 写入;失败→YAML 引导弹窗 │ └──────────────────────────────────────────────────────────────────────────────────────┘ │ GET JSON(只读、无密钥) ▲ settings 写入走本机 DSH Host ┌───────▼─────────────────────────┐ │ 站长后端 server-php/ │ │ api/models.php 公开分页 API │ PHP 7.4 + PDO SQLite │ admin/* 管理后台 │ (登录/CSRF/限速/预编译语句) │ data/*.db SQLite 库 │ └─────────────────────────────────┘ ``` 数据流要点: 1. **模型清单**:站长后台维护 → SQLite → 公开 API → 面板渲染。浏览器直连站长站点,需 CORS 放行 DSH 来源。 2. **配置写入**:不经过站长后端。Client 半边通过本机 DSH 的设置作用域直接写 `llm-pi-ai.providers`。 3. **API Key**:用户在 DSH 官方 设置 → 模型 里填写(write-only,存 `$DSH_HOME/.credentials.yaml`)。本插件任何一端都不出现 Key。 --- ## 2. 插件包结构(仓库根 = 包根) > `dsh plugin add github:/` 从仓库根读取 `package.json`,因此包必须在根目录。 ``` / # = npm 包根 ├── package.json # dsh.bundle + dsh.client 声明;files 只发布 lib/ ├── cordis.patch.yml # bundle 补丁层:insert 插件行 ├── src/index.js # Host 半边入口(Node, ESM) ├── src/client/index.js # Client 半边源码(零依赖单文件) ├── lib/index.js # 预构建产物(随仓库提交,免 allowBuilds) ├── lib/client.cjs # 懒加载 CJS 工厂格式 bundle ├── test/ # node:test 单测 ├── scripts/build.mjs # 构建(生成 lib/) ├── docs/ # 本文档集 ├── server-php/ # 配套 PHP 后端(独立部署到站长网站) └── .github/workflows/ci.yml ``` ### 2.1 package.json 关键字段 ```jsonc { "name": "dsh-free-models-hub", "version": "0.1.0", "type": "module", "main": "lib/index.js", "exports": { ".": "./lib/index.js", "./client": "./lib/client.cjs" }, "files": ["lib", "cordis.patch.yml", "README.md"], "dsh": { "bundle": { "patch": "./cordis.patch.yml" }, "client": { "platform": "web" } } } ``` - `dsh.bundle.patch` 缺失时包会"装了但不激活"(官方文档明确警告),此字段为激活开关。 - `dsh.client.platform: 'web'` 让 client-modules 扫描器把 `lib/client.cjs` 挂进 `window.__DSH_BOOT__` 图谱并经 `/plugins//client.js` 下发。 ### 2.2 cordis.patch.yml ```yaml - insert: - id: free-models-hub name: dsh-free-models-hub # 引用包名,Node 解析找到 lib/index.js config: backendUrl: '' pageSize: 10 requestTimeoutMs: 10000 uiSlot: sidebar.workspaces providerIdPrefix: freehub footerLinks: - label: 技术笔记 url: http://blog.4wc.cn - label: 插件开发 url: https://blog.gd7.cn/ - label: 联系站长 url: http://web.wuyiyun.cn/ ``` 一个 Loader 行同时驱动 Node 半边与 client 扫描(扫描按"启用的 Loader 条目里声明了 dsh.client 的包"进行)。 --- ## 3. Host 半边(src/index.js) 职责(刻意保持薄,兼容性优先): 1. **配置规范化**:不导出 Config schema(避免硬依赖 schemastery 的版本矩阵),在 apply 内做白名单校验 + 默认值回填,非法值 fail-loud 打印可操作错误。 2. **设置分区注册(可选增强)**:动态 `import('@deepseek-ai/dsh-settings')` 尝试 `installSettingsSection(ctx, NS, …)`,把 backendUrl 等暴露到设置页「插件配置」页签;import 失败静默跳过(老版本仍可用,仅少设置卡片)。 3. **日志**:统一 `[free-models-hub]` 前缀,级别受 `debug` 配置控制。 4. **可逆性**:所有副作用走 ctx 注册/effect,卸载自动清理。 依赖策略:`dependencies: {}`(零运行时依赖)。对宿主服务一律 feature-detect + try/catch,杜绝因内部 API 漂移导致 fiber FAIL。 ## 4. Client 半边(src/client → lib/client.cjs) ### 4.1 形态 - 导出 `{ name, inject, apply }`;构建为懒加载 CJS 工厂文本(`module.exports = …`),匹配 `/plugins//client.js` 的消费方式。 - `inject = ['slots']`;其余能力(settingsScope 等)运行时探测 `ctx.settingsScope`,避免声明式注入在不提供该服务的 profile 上直接失败。 ### 4.2 UI 组件(纯 DOM,零框架) | 组件 | 说明 | |---|---| | Panel | 标题行 + 列表容器 + 分页器 + 底部菜单;class 前缀 `fmh-`,样式经 `