feat(admin): 搜索提供商下拉/多选/表格显示中文名(值仍是 ASCII id)
主人报障:「为什么中文搜索接口……下拉菜单里全给的英文名?你甚至不愿意带个括号
把中文名写上去」。根因不是漏写文案,而是整条链路没有「显示名」的位置:字段
声明只有 optionsFrom,候选动作回的就是 KnownNames() 的纯 id 数组。
- 后端:候选改为对象数组 {value,label};label 表与接入位表同源(键用
NameSearXNG 等常量),未知 id 原样回。value 逐字等于 KnownNames()。
- 前端:下拉/多选「显示 label、写 value」;表格单元格按 value→label 索引显示,
查不到回落显示原值。多选从裸输入框改成「候选开关 + 落盘值输入框」。
解码器本就兼容裸字符串/{value,label}/{id} 三形状 ⇒ 其他插件零影响。
中文名逐字:SearXNG 本地兜底(searxng)/ 智谱 BigModel(zhipu)/
阿里云 IQS 信息查询(aliyun_iqs)/ 博查 Bocha(bocha)/
火山引擎·豆包搜索(volcengine)/ 百度千帆 AI 搜索(baidu_qianfan)。
验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;search 包测试绿;
tsc --noEmit 0 错;新渲染测试 26 checks + 既有 plugin panel 55 / data 90 /
schema values 40 / channel config 70 全绿。我用 sed 把 values.ts 的
option.value 全换成 option.label ⇒ 渲染测试红(「点选把候选项的 id 追加到
降级链末尾」got="bocha, 智谱 BigModel(zhipu)")—— 这正是会把配置文件写坏的
错法;还原后 md5 一致且绿。
⚠️ 契约面变更(我在 Docs/api-client.md 登记):plugin.providers 候选响应形状
字符串数组 → 对象数组;label 仅供显示、value 才是落盘值。
⚠️ 已知边界:viewer 角色看不到 form 区块 ⇒ 表格对 viewer 仍显示裸 id(未做)。
⚠️ 真机浏览器点击未跑(项目无 jsdom)⇒ 建议刷新管理页点一次确认观感。
This commit is contained in:
1 parent
8051906afa
commit
c46053e276
12 files changed
+500
-49
No files matched your search
@@ -393,6 +393,9 @@ func TestWebSearchActionsRejectUnknownProviderAndBadPayload(t *testing.T) {
|
||||
|
||||
// TestWebSearchOptionsActionFeedsDeclaredOptionsFrom 锁死「声明的下拉一定拉得到候选」:
|
||||
// 声明里每个 optionsFrom 映射出的动作名都**已登记且已声明**(否则前端只能降级成文本框)。
|
||||
//
|
||||
// 候选的形状是 `{value,label}`(切片 LBL-1):这里连**显示名非空**一起钉住 —— 只有值的
|
||||
// 候选会让面板下拉与表格里全是英文 id(主人 2026-10-06 报障的那个形状)。
|
||||
func TestWebSearchOptionsActionFeedsDeclaredOptionsFrom(t *testing.T) {
|
||||
env := newActionTestEnv(t, "WEB_SEARCH_PROVIDER="+search.NameSearXNG)
|
||||
|
||||
@@ -413,16 +416,31 @@ func TestWebSearchOptionsActionFeedsDeclaredOptionsFrom(t *testing.T) {
|
||||
t.Errorf("候选来源 %q 调不动: %v", src, err)
|
||||
continue
|
||||
}
|
||||
options, _ := out.(map[string]any)["options"].([]string)
|
||||
options, _ := out.(map[string]any)["options"].([]search.ProviderOption)
|
||||
if len(options) == 0 {
|
||||
t.Errorf("候选来源 %q 回了空候选(表单下拉会空着)", src)
|
||||
}
|
||||
if strings.Join(options, ",") != strings.Join(search.ProviderOptions(), ",") {
|
||||
t.Errorf("候选 = %v, want %v", options, search.ProviderOptions())
|
||||
got, want := optionValues(options), optionValues(search.ProviderOptions())
|
||||
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Errorf("候选值 = %v, want %v(value 逐字等于 KnownNames())", got, want)
|
||||
}
|
||||
for _, o := range options {
|
||||
if strings.TrimSpace(o.Label) == "" {
|
||||
t.Errorf("候选来源 %q 的 %q 没有显示名(下拉里会是空白项)", src, o.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// optionValues 取出结构化候选的**值**(落盘真源),丢掉显示名。
|
||||
func optionValues(opts []search.ProviderOption) []string {
|
||||
out := make([]string, 0, len(opts))
|
||||
for _, o := range opts {
|
||||
out = append(out, o.Value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestWebSearchActionsUnwiredAnswers501 锁定降级口径:**没有配置**(测试夹具/装配未完成)
|
||||
// 时不登记任何处理器 ⇒ 已声明的动作诚实回 501(ErrActionUnavailable),
|
||||
// 绝不假装成功地回一份空列表。
|
||||
|
||||
Reference in new issue
Block a user