Skip to content

Conversation

@railgun19457
Copy link
Contributor

@railgun19457 railgun19457 commented Dec 26, 2025

解决插件配置中需要保存多组重复类型的配置问题
应用场景比如插件中保存多种api供应商(baseurl、key、model),或者人设相关插件,保存多套人设(头像、昵称、人格等)

Modifications / 改动点

修改内容

/dashboard/src/components/shared

  • AstrBotConfig.vue
  • AstrBotConfigV4.vue
  • ConfigItemRenderer.vue
    • 新增文件,提取了所有配置类型的渲染逻辑
  • TemplateListEditor.vue
    • 新增文件,管理模板配置列表

dashboard/src/i18n/locales/zh-CN/core/common.json
dashboard/src/i18n/locales/en-US/core/common.json

  • 添加 i18n 支持

astrbot/core/config/default.py
astrbot/core/config/astrbot_config.py

  • 添加新的template_list类型

astrbot/core/config/astrbot_config.py

  • 添加对应的数据校验

功能实现

插件开发者可以在_conf_schema中按照以下格式添加模板配置项(有点类似于原有的嵌套配置)

 "field_id": {
  "type": "template_list",
  "description": "Template List Field",
  "templates": {
    "template_1": {
        "name": "Template One",
        "hint":"hint",
        "items": {
          "attr_a": {
            "description": "Attribute A",
            "type": "int",
            "default": 10
          },
          "attr_b": {
            "description": "Attribute B",
            "hint": "This is a boolean attribute",
            "type": "bool",
            "default": true
          }
        }
      },
    "template_2": {
      "name": "Template Two",
      "hint":"hint",
      "items": {
        "attr_c": {
          "description": "Attribute A",
          "type": "int",
          "default": 10
        },
        "attr_d": {
          "description": "Attribute B",
          "hint": "This is a boolean attribute",
          "type": "bool",
          "default": true
        }
      }
    }
  }
}

保存后的config为

"field_id": [
    {
        "__template_key": "template_1",
        "attr_a": 10,
        "attr_b": true
    },
    {
        "__template_key": "template_2",
        "attr_c": 10,
        "attr_d": true
    }
]
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

image image image image #### 测试_conf_schema文件
{
  "test_templates": {
    "type": "template_list",
    "description": "测试模板列表配置",
    "templates": {
      "api_provider": {
        "name": "API 供应商",
        "hint": "配置一个 API 供应商",
        "items": {
          "base_url": {
            "description": "Base URL",
            "type": "string",
            "default": "https://api.openai.com/v1"
          },
          "api_key": {
            "description": "API Key",
            "type": "string",
            "default": "sk-xxxx"
          },
          "model": {
            "description": "模型名称",
            "type": "string",
            "default": "gpt-3.5-turbo"
          }
        }
      },
      "persona": {
        "name": "角色人设",
        "hint": "配置一个角色人设",
        "items": {
          "nickname": {
            "description": "昵称",
            "type": "string",
            "default": "小助手"
          },
          "avatar": {
            "description": "头像 URL",
            "type": "string",
            "default": ""
          },
          "personality": {
            "description": "性格描述",
            "type": "string",
            "default": "热情友好"
          },
          "is_active": {
            "description": "是否激活",
            "type": "bool",
            "default": true
          }
        }
      }
    }
  }
}

测试插件源码

from astrbot.api import logger
from astrbot.api.event import AstrMessageEvent, filter
from astrbot.api.star import Context, Star


class TestPlugin(Star):
    def __init__(self, context: Context, config: dict):
        super().__init__(context)
        self.config = config
        logger.info(f"AstrBot Test Plugin Loaded. Config: {self.config}")

    @filter.command("test_config")
    async def test_config(self, event: AstrMessageEvent):
        """查看当前插件的 template_list 配置内容"""
        template_list_config = self.config.get("test_templates", [])

        if not template_list_config:
            yield event.plain_result("当前没有配置任何模板项。")
            return

        msg = "当前 template_list 配置如下:\n"
        for index, item in enumerate(template_list_config):
            template_key = item.get("__template_key", "unknown")
            msg += f"\n[{index + 1}] 模板类型: {template_key}\n"
            for key, value in item.items():
                if key != "__template_key":
                    msg += f"  - {key}: {value}\n"

        yield event.plain_result(msg.strip())

实际存储的config文件

{
  "test_templates": [
    {
      "__template_key": "api_provider",
      "base_url": "https://api.openai.com/v1",
      "api_key": "sk-xxxx",
      "model": "gpt-3.5-turbo"
    },
    {
      "__template_key": "persona",
      "nickname": "小助手",
      "avatar": "",
      "personality": "热情友好",
      "is_active": true
    }
  ]
}

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

在仪表盘 UI 中新增对基于模板的列表配置类型的支持,并将配置字段渲染逻辑集中管理。

新功能:

  • 引入 template_list 配置类型,使插件能够基于预定义模板存储多条配置项。
  • 新增 TemplateListEditor UI 组件,用于管理基于模板的配置列表,包括条件字段和默认值。

增强改进:

  • 将配置字段的渲染逻辑重构为可复用的 ConfigItemRenderer 组件,并在 AstrBotConfigAstrBotConfigV4 中统一使用,以实现一致的行为。
  • 扩展后端配置解析与校验逻辑,以支持 template_list 字段,并针对不同模板进行条目级校验和合理的默认值处理。
  • 更新默认配置类型映射和 i18n 资源,以识别新的与 template_list 相关的 UI 和文案。
Original summary in English

Summary by Sourcery

Add support for a new template-based list configuration type and centralize config field rendering in the dashboard UI.

New Features:

  • Introduce a template_list configuration type that lets plugins store multiple entries based on predefined templates.
  • Add a TemplateListEditor UI component to manage template-based configuration lists, including conditional fields and defaults.

Enhancements:

  • Refactor config field rendering into a reusable ConfigItemRenderer component used by AstrBotConfig and AstrBotConfigV4 for consistent behavior.
  • Extend backend config parsing and validation to handle template_list fields with per-template item validation and sensible defaults.
  • Update default config type mappings and i18n resources to recognize the new template_list-related UI and messages.

Summary by Sourcery

添加对新的基于模板的列表配置类型的支持,并在仪表盘和后端配置处理中统一配置项的渲染。

新功能:

  • 为插件 schema 引入 template_list 配置类型,允许基于预定义模板创建多条配置条目。
  • 在仪表盘中新增 TemplateListEditor UI 组件,用于管理基于模板的配置列表。

增强改进:

  • 将配置字段渲染逻辑抽取为可复用的 ConfigItemRenderer 组件,并在 AstrBotConfigAstrBotConfigV4 中采用,以实现行为一致。
  • 扩展后端配置解析与校验逻辑,以支持 template_list 字段,包括对每个条目的模板校验以及合理的默认值处理。
  • 扩展默认配置类型映射和 i18n 资源,以识别与 template_list 相关的 UI 与消息。
Original summary in English

Summary by Sourcery

Add support for a new template-based list configuration type and centralize config item rendering in both dashboard and backend configuration handling.

New Features:

  • Introduce a template_list configuration type for plugin schemas, allowing multiple entries based on predefined templates.
  • Add a TemplateListEditor UI component to manage template-based configuration lists in the dashboard.

Enhancements:

  • Extract config field rendering into a reusable ConfigItemRenderer component and adopt it in AstrBotConfig and AstrBotConfigV4 for consistent behavior.
  • Extend backend config parsing and validation to handle template_list fields with per-entry template validation and sensible defaults.
  • Expand the default config type map and i18n resources to recognize template_list-related UI and messages.

…lication

- Create ConfigItemRenderer.vue to centralize rendering logic for various config types (string, int, bool, selectors, etc.)
- Refactor TemplateListEditor.vue to use the new renderer for entry fields
- Refactor AstrBotConfig.vue and AstrBotConfigV4.vue to simplify metadata-driven rendering
- Resolve circular dependency by decoupling TemplateListEditor from the base renderer
@railgun19457 railgun19457 marked this pull request as ready for review December 26, 2025 11:24
@auto-assign auto-assign bot requested review from Fridemn and anka-afk December 26, 2025 11:24
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend area:webui The bug / feature is about webui(dashboard) of astrbot. labels Dec 26, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • TemplateListEditor.vue 中,ensureEntryDefaults/applyDefaults 会就地修改 props.modelValue 的条目;建议克隆数组/对象并通过 emit 发送更新后的值,以保持 prop 的严格单向数据流,并避免一些微妙的响应式问题。
  • ConfigItemRenderer.vue 按需引入了 TemplateListEditor,但从未在模板中渲染(模板中没有 template_list 分支);可以移除这个 import,或者合并相关处理逻辑,以避免未使用的代码。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
-`TemplateListEditor.vue` 中,`ensureEntryDefaults`/`applyDefaults` 会就地修改 `props.modelValue` 的条目;建议克隆数组/对象并通过 emit 发送更新后的值,以保持 prop 的严格单向数据流,并避免一些微妙的响应式问题。
- `ConfigItemRenderer.vue` 按需引入了 `TemplateListEditor`,但从未在模板中渲染(模板中没有 `template_list` 分支);可以移除这个 import,或者合并相关处理逻辑,以避免未使用的代码。

## Individual Comments

### Comment 1
<location> `astrbot/dashboard/routes/config.py:64` </location>
<code_context>
             if value is None:
                 data[key] = DEFAULT_VALUE_MAP[meta["type"]]
                 continue
+            if meta["type"] == "template_list":
+                if not isinstance(value, list):
+                    errors.append(
</code_context>

<issue_to_address>
**issue (complexity):** 考虑将 `validate` 中的 `template_list` 处理逻辑和类型检查拆分成一些小的辅助函数,以减少嵌套并将这部分逻辑隔离出来。

你可以在保持现有行为不变的前提下,通过将 `template_list` 相关逻辑提取到一个专门的 helper 中,并复用一个小的类型检查 helper,来降低 `validate` 的复杂度。这样可以拉平条件嵌套,并把分支逻辑局部化。

For example:

```python
def _expect_type(value, expected_type, path_key, errors, expected_name=None):
    if not isinstance(value, expected_type):
        errors.append(
            f"错误的类型 {path_key}: 期望是 {expected_name or expected_type.__name__}, "
            f"得到了 {type(value).__name__}"
        )
        return False
    return True


def _validate_template_list(value, meta, path_key, errors):
    if not _expect_type(value, list, path_key, errors, "list"):
        return

    templates = meta.get("templates")
    if not isinstance(templates, dict):
        templates = {}

    for idx, item in enumerate(value):
        item_path = f"{path_key}[{idx}]"
        if not _expect_type(item, dict, item_path, errors, "dict"):
            continue

        template_key = item.get("__template_key") or item.get("template")
        if not template_key:
            errors.append(f"缺少模板选择 {item_path}: 需要 __template_key")
            continue

        template_meta = templates.get(template_key)
        if not template_meta:
            errors.append(f"未知模板 {item_path}: {template_key}")
            continue

        validate(
            item,
            template_meta.get("items", {}),
            path=f"{item_path}.",
        )
```

然后 `validate` 可以保持为一个精简的分发器:

```python
if meta["type"] == "template_list":
    _validate_template_list(value, meta, f"{path}{key}", errors)
    continue

if meta["type"] == "list" and not isinstance(value, list):
    errors.append(
        f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}",
    )
```

这样可以保留当前的全部行为(包括错误信息),同时去掉 `validate` 中多层嵌套的 `if`,让 `template_list` 逻辑更易于测试,并统一类型错误信息的格式。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In TemplateListEditor.vue, ensureEntryDefaults/applyDefaults mutate props.modelValue entries in place; consider cloning the array/object and emitting an updated value instead to keep prop usage strictly one-way and avoid subtle reactivity issues.
  • ConfigItemRenderer.vue lazily imports TemplateListEditor but never renders it (template has no template_list branch); this import can be removed or the handling consolidated to avoid unused code.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TemplateListEditor.vue`, `ensureEntryDefaults`/`applyDefaults` mutate `props.modelValue` entries in place; consider cloning the array/object and emitting an updated value instead to keep prop usage strictly one-way and avoid subtle reactivity issues.
- `ConfigItemRenderer.vue` lazily imports `TemplateListEditor` but never renders it (template has no `template_list` branch); this import can be removed or the handling consolidated to avoid unused code.

## Individual Comments

### Comment 1
<location> `astrbot/dashboard/routes/config.py:64` </location>
<code_context>
             if value is None:
                 data[key] = DEFAULT_VALUE_MAP[meta["type"]]
                 continue
+            if meta["type"] == "template_list":
+                if not isinstance(value, list):
+                    errors.append(
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting the `template_list` handling and type checks from `validate` into small helpers to flatten nesting and isolate that logic.

You can keep the behavior but reduce complexity in `validate` by extracting the `template_list` logic into a focused helper and reusing a small type-check helper. This flattens the nesting and localizes the branching.

For example:

```python
def _expect_type(value, expected_type, path_key, errors, expected_name=None):
    if not isinstance(value, expected_type):
        errors.append(
            f"错误的类型 {path_key}: 期望是 {expected_name or expected_type.__name__}, "
            f"得到了 {type(value).__name__}"
        )
        return False
    return True


def _validate_template_list(value, meta, path_key, errors):
    if not _expect_type(value, list, path_key, errors, "list"):
        return

    templates = meta.get("templates")
    if not isinstance(templates, dict):
        templates = {}

    for idx, item in enumerate(value):
        item_path = f"{path_key}[{idx}]"
        if not _expect_type(item, dict, item_path, errors, "dict"):
            continue

        template_key = item.get("__template_key") or item.get("template")
        if not template_key:
            errors.append(f"缺少模板选择 {item_path}: 需要 __template_key")
            continue

        template_meta = templates.get(template_key)
        if not template_meta:
            errors.append(f"未知模板 {item_path}: {template_key}")
            continue

        validate(
            item,
            template_meta.get("items", {}),
            path=f"{item_path}.",
        )
```

Then `validate` can stay as a thin dispatcher:

```python
if meta["type"] == "template_list":
    _validate_template_list(value, meta, f"{path}{key}", errors)
    continue

if meta["type"] == "list" and not isinstance(value, list):
    errors.append(
        f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}",
    )
```

This keeps all current behavior (including error messages) but removes multiple nested `if`s from `validate`, makes `template_list` logic easier to test, and standardizes the type error formatting.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Frontend: Fix one-way data flow in TemplateListEditor.vue by cloning entries before applying defaults and emitting updates instead of in-place modification.
- Frontend: Remove unused TemplateListEditor import in ConfigItemRenderer.vue.
- Backend: Refactor validate_config in config.py by extracting _expect_type and _validate_template_list helpers to reduce nesting and complexity.
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Dec 29, 2025
@Soulter Soulter changed the title Feature/template list config feat: add template_list config type to support multiple repeated core/plugin config sets Dec 29, 2025
@Soulter Soulter merged commit 79d0487 into AstrBotDevs:master Dec 29, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:webui The bug / feature is about webui(dashboard) of astrbot. lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants