Files
Cyrene_v2/plugins/tools/io/search/panel.go
T
AskaEth 120d926cf8 feat(search): 搜索 provider 可变源 + 插件声明面板(PLG-4)
- 新增 $DATA_DIR/config/plugins/web-search.json(0600 + temp+rename,文件名 = 插件 id)
- 组合根改「先读可变源、缺失回落 .env 播种」;**不新增任何 env 键**(.env 已冻结)
- 多实例:每家 provider 独立 key/baseURL/启用态 + 「测试连通」探针
- 用 PLG-1 的声明库给出该面板的 adminPanels 声明;动作名交给 PLG-2 的通用动作端点分发
  (本片自己不开路由)

⚠️ 本片自认越界:改了 cmd/cyrene/wire_web_search_test.go(既有测试,为适配 DataDir);
  另有 design/28 的相关注册留给主会话。
2026-10-06 09:04:04 +08:00

110 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package search
// 本文件是 web-search 插件向**面板**声明的管理页面:design/09 §5.2 的 `adminPanels`,
// 形状即 `pkg/plugins/sdk` 的 `Panel`。
//
// 声明只说三件事:**要什么字段、怎么渲染、动作叫什么**。它
//
// - 不注入任何 JS/CSS/HTML、
// - 不给 URL(只有动作名,走宿主的通用动作端点分发)、
// - 不开任何路由。
//
// 这三条是 design/09 §5.2.1 的红线:声明而非编码、声明里塞不进请求面(防 SSRF)、
// 面板数据一律经插件管理器的端点。渲染与流程由宿主负责,本文件只负责「说得清楚」。
import "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
// PanelPluginID 是本插件在面板体系里的 id。它与可变源的文件名**同源**:
// `$DATA_DIR/config/plugins/web-search.json`(design/13 §2.1:文件名 = 插件 id),
// 两边由 panel_test.go 钉住。
const PanelPluginID = "web-search"
// PanelIDProviders 是「搜索提供商」页的面板 id。
// 前端路由为 `/admin/plugin/web-search/providers`(design/09 §5.7 C)。
const PanelIDProviders = "providers"
// 本插件声明的**全部**动作名(design/09 §5.2 示例给出的那四个)。
//
// 表里的动作名一律取自这里,通用动作端点也只接受这几个名字(未声明 ⇒ 404/403,
// design/09 §5.2.1 B)。把「声明」与「动作名」放同一个文件,是为了让「表里写了一个
// 没实现的动作」这种半渲染在 review 时一眼可见 —— 它们就挨着。
const (
// ActionListProviders 列出全部 provider 配置(表格区块的数据源)。
ActionListProviders = "plugin.listProviders"
// ActionSaveProvider 新增 / 修改一条 provider 配置(表单提交)。
// 语义是 upsert:按 `provider` 名就位替换或追加。
ActionSaveProvider = "plugin.saveProvider"
// ActionTestProvider 测试连通:对一条 provider 配置真发一次最小检索(见 probe.go)。
ActionTestProvider = "plugin.testProvider"
// ActionReauthProvider 重配凭据:把该条带回表单。写侧的「未改动」必须用显式哨兵表达,
// **不能**靠空字符串(那会静默清空凭据,design/09 §5.4.2)。
ActionReauthProvider = "plugin.reauthProvider"
)
// ActionNames 返回本插件声明的动作名,顺序与 AdminPanels 里出现的顺序一致
// (表格数据源 → 表单提交 → 行内动作)。调用方(插件管理器的动作端点)据此建白名单:
// 白名单外的动作名一律拒,绝不「先执行再看结果」。
func ActionNames() []string {
return []string{ActionListProviders, ActionSaveProvider, ActionTestProvider, ActionReauthProvider}
}
// AdminPanels 返回本插件声明的管理页面(design/09 §5.2 示例的 providers 页)。
//
// 两处相对示例的**明确增补**(示例是形状示范,不是字段清单的穷举;两处都被本片的任务书
// 要求):
// - 表单多一个 `enabled` 开关:任务书要求每家 provider **独立启用态**;
// - 表单的 `baseUrl` 用示例的 `endpoint` 语义(searxng 的端点 = 那家的基地址),
// 名字跟可变源的字段名走,免得面板字段名与文件字段名对不上。
func AdminPanels() []sdk.Panel {
return []sdk.Panel{{
ID: PanelIDProviders,
Title: "搜索提供商",
Description: "为 web-search 能力配置多个第三方搜索后端:每条独立凭据与基地址," +
"可单独启停并测试连通。改动落 $DATA_DIR/config/plugins/web-search.json,重启后仍然生效。",
Group: sdk.GroupPlugins,
Order: 20,
Icon: "search",
// 未启用的插件 ⇒ 入口不出现(design/09 §5.2.1 nav.item)。web-search 是编进二进制的
// 内置能力,没有卸载路径 ⇒ 恒为启用;这里写条件是为了让「启用事实」由插件管理器
// 给出(单一事实来源),而不是由面板猜。
When: &sdk.When{Enabled: boolFlag(true)},
Sections: []sdk.Section{
{
Type: sdk.SectionForm,
ID: "add",
Title: "添加 / 修改提供商",
Fields: []sdk.Field{
{Key: "provider", Label: "提供商", Type: sdk.FieldSelect,
OptionsFrom: "plugin:providers", Required: true},
// secret 字段刻意不标 required:它同时承担「编辑既有条目」的输入,
// 而编辑时不重填凭据是常态(写侧哨兵语义,见 ActionReauthProvider)。
{Key: "apiKey", Label: "API Key", Type: sdk.FieldSecret,
Placeholder: "留空 = 保持原值"},
{Key: "baseUrl", Label: "接口地址", Type: sdk.FieldText,
Placeholder: "https://..."},
{Key: "enabled", Label: "启用", Type: sdk.FieldSwitch},
},
Submit: &sdk.Submit{Action: ActionSaveProvider},
},
{
Type: sdk.SectionTable,
ID: "list",
Title: "已配置的提供商",
Source: &sdk.Source{Action: ActionListProviders},
// 列 key 与 panelrow.go 的行字段同源;凭据不进任何列(读侧无明文)。
Columns: []sdk.Column{
{Key: "provider", Label: "提供商"},
{Key: "state", Label: "状态"},
},
RowActions: []sdk.RowAction{
{ID: "test", Label: "测试连通", Action: ActionTestProvider},
{ID: "reauth", Label: "重配凭据", Action: ActionReauthProvider},
},
},
},
}}
}
// boolFlag 是 `&true` / `&false` 的简写(sdk.When.Enabled 是三态指针)。
func boolFlag(v bool) *bool { return &v }