Skip to content

Conversation

xunxiing
Copy link

@xunxiing xunxiing commented Sep 12, 2025

Motivation

让插件作者在配置面板中直接上传并引用文件,统一落盘到每个插件的专属数据目录,遵循 AstrBot 数据/代码分离的最佳实践。
降低插件实现“词表/模版/资源文件”类需求的门槛,开箱即用。

Modifications

后端

新增配置类型默认值映射:astrbot/core/config/default.py 增加 file: ""。
新增文件字段管理路由:astrbot/dashboard/routes/plugin_config_files.py
GET /api/plugin/{plugin}/config/filefield/list?field=...
POST /api/plugin/{plugin}/config/filefield/upload(multipart:field, file)
DELETE /api/plugin/{plugin}/config/filefield/delete?field=...&path=...
路由注册:astrbot/dashboard/routes/init.py、astrbot/dashboard/server.py
文件名模板扩展:支持 {timestamp} {uuid} {ext} {name} {original},以及 original/{original}/{filename} 直通保留原文件名(均做安全清洗)。
前端

前端

新增文件字段 UI 与 API
dashboard/src/components/config/fields/FileField.vue: 当前值、上传(自动应用)、文件列表(设为当前/删除/刷新)
dashboard/src/api/configFileField.ts: list/upload/delete 封装
统一上传按钮为可复用组件
dashboard/src/components/shared/FilePickButton.vue: 封装“隐藏 v-file-input + 触发按钮”,与插件安装页一致
FileField 改为复用该组件
渲染接入
dashboard/src/components/shared/AstrBotConfig.vue: 渲染 type: 'file'
dashboard/src/components/shared/AstrBotConfigV4.vue: 渲染 type: 'file'
上传按钮(单击弹出选择文件,选中即自动上传)
当前值展示(复制/清空)
文件列表(复制路径/设为当前/删除/刷新)
支持 multiple 基础交互(数组形式)
渲染接入:
插件配置:dashboard/src/components/shared/AstrBotConfig.vue
JSON 选择器/系统配置渲染器:dashboard/src/components/shared/AstrBotConfigV4.vue
其他

在插件 _conf_schema.json 中为目标字段声明如下(最小示例):

packages/your_plugin/_conf_schema.json:1
{
"custom_vocabulary": {
"description": "上传词表(.txt)",
"type": "file",
"hint": "会保存到插件数据目录/vocab",
"accept": [".txt"], // 可选:允许的后缀列表(前端过滤 + 后端校验)
"max_size_mb": 4, // 可选:大小上限(MB)
"dest_dir": "vocab", // 必填:相对插件数据目录的子目录
"name_template": "original", // 可选:文件命名模板,见下文
"multiple": false // 可选:是否以数组形式保存多个值
}
}
字段含义:

type: 固定写 "file"(配置值为相对路径字符串或数组)
dest_dir: 必填,存放文件的子目录(相对 StarTools.get_data_dir(plugin))
accept: 可选,后缀白名单,例:[".txt",".csv"]
max_size_mb: 可选,大小上限(MB)
name_template: 可选,命名模板
支持占位符:{timestamp}、{uuid}、{ext}、{name}、{original}
特殊值:original / {original} / {filename} 表示保留原始文件名(会做安全清洗)
默认:{timestamp}-{uuid}{ext}
multiple: 可选,true 则配置保存为数组,上传时追加;false(默认)保存为字符串(覆盖)
运行时保存值:

后端在上传成功后会将“相对路径”(相对插件数据目录)写回配置,如:vocab/my_vocab.txt
Backend APIs

列表:GET /api/plugin/{plugin}/config/filefield/list?field=...
上传:POST /api/plugin/{plugin}/config/filefield/upload(multipart:field, file)
删除:DELETE /api/plugin/{plugin}/config/filefield/delete?field=...&path=...
安全性:所有路径均以 StarTools.get_data_dir(plugin) 与 schema.dest_dir 为根,阻断越权;校验大小/后缀;命名清洗。
Frontend

在渲染到 type: 'file' 字段时显示“上传文件”按钮 + 文件库列表,并在设为当前/删除时同步更新配置值。
Compatibility
###功能展示
屏幕截图 2025-09-13 180928

Check

  • 😊 我的 Commit Message 符合良好的规范
  • 👀 我的更改经过良好的测试
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。
  • 😮 我的更改没有引入恶意代码

Sourcery 总结

为插件引入一个新的“file”配置字段类型,允许用户在仪表板 UI 中上传、列出、设置和删除文件,具有安全的存储和命名规范,并添加对本地构建的仪表板的检测,以方便开发。

新功能:

  • 添加插件配置字段类型“file”,使得在仪表板 UI 中能够进行文件上传和管理
  • 实现后端 API 路由,用于列出、上传和删除每个插件专用数据目录下的文件
  • 提供 FileField 和 FilePickButton Vue 组件,用于在 v3 和 v4 配置面板中处理文件选择、显示和操作

改进:

  • 扩展默认配置映射以包含“file”类型
  • 支持带占位符的文件名模板,并清理名称以确保安全性和一致性
  • 增强主启动逻辑,以检测并复制存在的本地构建的 dashboard/dist
Original summary in English

Summary by Sourcery

Introduce a new "file" config field type for plugins, allowing users to upload, list, set, and delete files in the dashboard UI with secure storage and naming conventions, and add detection of a locally built dashboard for development convenience.

New Features:

  • Add plugin config field type "file" enabling file uploads and management in the dashboard UI
  • Implement backend API routes to list, upload, and delete files under each plugin’s dedicated data directory
  • Provide FileField and FilePickButton Vue components to handle file selection, display, and operations in both v3 and v4 config panels

Enhancements:

  • Extend default config mapping to include the "file" type
  • Support filename templating with placeholders and sanitize names to enforce security and consistency
  • Enhance main startup logic to detect and copy a locally built dashboard/dist when present

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.

你好 - 我已经审查了你的更改,它们看起来很棒!

AI 代理的提示
请处理此代码审查中的评论:
## 单独评论

### 评论 1
<location> `dashboard/src/components/config/fields/FileField.vue:98` </location>
<code_context>
+  refresh();
+});
+
+watch(() => props.pluginName + props.fieldKey, () => refresh());
+
+function humanSize(n: number) {
</code_context>

<issue_to_address>
将 pluginName 和 fieldKey 拼接用于 watch 可能会导致不必要的刷新。

观察拼接后的字符串可能会在只有一个值更改时触发不必要的刷新。将 [pluginName, fieldKey] 作为数组观察将确保仅当其中任何一个值更改时才触发刷新。
</issue_to_address>

### 评论 2
<location> `astrbot/dashboard/routes/plugin_config_files.py:98` </location>
<code_context>
+    return safe
+
+
+class PluginConfigFileFieldRoute(Route):
+    """Per-plugin file field manager (list/upload/delete)."""
+
</code_context>

<issue_to_address>
考虑将错误处理、模式验证和文件名清理重构为可重用的助手和库,以减少样板代码并提高可维护性。

- 将 HTTP 层样板代码(try/except + `Response()` 包装)提取到装饰器中,以使每个处理程序保持 DRY:

```python
# utils/http.py
import traceback
from functools import wraps
from .route import Response
from astrbot.core import logger

def with_error_response(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Response().error(str(e)).__dict__
    return wrapper
```

```python
# In PluginConfigFileFieldRoute
from utils.http import with_error_response

    @with_error_response
    async def list_files(self, plugin_name: str):
        # body now has no try/except or Response() wrapping
```

- 将重复的“加载插件元数据 + 验证模式字段”合并到一个助手函数中:

```python
# utils/plugin_schema.py
from astrbot.core.star.star import star_registry
from pathlib import Path

def load_field_schema(plugin_name: str, field: str) -> Tuple[Any, Path, Dict]:
    md = next((m for m in star_registry if m.name == plugin_name), None)
    if not md or not md.config:
        raise ValueError(f"插件 {plugin_name} 未注册配置")
    schema = md.config.schema or {}
    field_schema = schema.get(field)
    if not field_schema or field_schema.get("type") != "file":
        raise ValueError(f"字段 {field} 未定义为 file 类型")
    root = Path(StarTools.get_data_dir(plugin_name))
    dest = (root / field_schema["dest_dir"]).resolve()
    dest.mkdir(parents=True, exist_ok=True)
    if not dest.is_relative_to(root):
        raise ValueError("非法目录")
    return md, root, dest, field_schema
```

```python
# then in each handler
md, root, target_root, field_schema = load_field_schema(plugin_name, field)
```

- 使用经过实战检验的库(例如 python-slugify)替换自定义清理逻辑:

```python
# utils/filename.py
from slugify import slugify
import uuid, datetime, os

def gen_safe_filename(tmpl: str, original: str) -> str:
    name, ext = os.path.splitext(original.lower())
    ctx = {
        "timestamp": datetime.utcnow().strftime("%Y%m%d-%H%M%S"),
        "uuid": uuid.uuid4().hex,
        "ext": ext,
        "name": slugify(name),
        "original": slugify(name) + ext
    }
    result = tmpl.format(**ctx)
    safe = slugify(result, separator="_") + (ext if not result.endswith(ext) else "")
    return safe
```

- 使用 `Path.is_relative_to()` 而不是手动字符串检查:

```python
# In ensure_inside_root or inline
if not dest_path.is_relative_to(root):
    raise ValueError("非法路径")
```

这些重构为每个处理程序减少了约 100 行的管道代码,集中了错误处理/验证,并利用现有库进行文件名清理和路径安全检查。
</issue_to_address>

Sourcery 对开源免费 - 如果您喜欢我们的评论,请考虑分享它们 ✨
帮助我更有用!请在每条评论上点击 👍 或 👎,我将使用这些反馈来改进您的评论。
Original comment in English

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `dashboard/src/components/config/fields/FileField.vue:98` </location>
<code_context>
+  refresh();
+});
+
+watch(() => props.pluginName + props.fieldKey, () => refresh());
+
+function humanSize(n: number) {
</code_context>

<issue_to_address>
Concatenating pluginName and fieldKey for watch may cause unnecessary refreshes.

Watching the concatenated string may trigger refreshes unnecessarily when only one value changes. Watching [pluginName, fieldKey] as an array will ensure refreshes occur only when either value changes.
</issue_to_address>

### Comment 2
<location> `astrbot/dashboard/routes/plugin_config_files.py:98` </location>
<code_context>
+    return safe
+
+
+class PluginConfigFileFieldRoute(Route):
+    """Per-plugin file field manager (list/upload/delete)."""
+
</code_context>

<issue_to_address>
Consider refactoring error handling, schema validation, and filename sanitization into reusable helpers and libraries to reduce boilerplate and improve maintainability.

- Extract HTTP‐layer boilerplate (try/except + `Response()` wrapping) into a decorator to DRY up each handler:

```python
# utils/http.py
import traceback
from functools import wraps
from .route import Response
from astrbot.core import logger

def with_error_response(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Response().error(str(e)).__dict__
    return wrapper
```

```python
# In PluginConfigFileFieldRoute
from utils.http import with_error_response

    @with_error_response
    async def list_files(self, plugin_name: str):
        # body now has no try/except or Response() wrapping
```

- Consolidate repeated “load plugin‐md + validate schema field” into one helper:

```python
# utils/plugin_schema.py
from astrbot.core.star.star import star_registry
from pathlib import Path

def load_field_schema(plugin_name: str, field: str) -> Tuple[Any, Path, Dict]:
    md = next((m for m in star_registry if m.name == plugin_name), None)
    if not md or not md.config:
        raise ValueError(f"插件 {plugin_name} 未注册配置")
    schema = md.config.schema or {}
    field_schema = schema.get(field)
    if not field_schema or field_schema.get("type") != "file":
        raise ValueError(f"字段 {field} 未定义为 file 类型")
    root = Path(StarTools.get_data_dir(plugin_name))
    dest = (root / field_schema["dest_dir"]).resolve()
    dest.mkdir(parents=True, exist_ok=True)
    if not dest.is_relative_to(root):
        raise ValueError("非法目录")
    return md, root, dest, field_schema
```

```python
# then in each handler
md, root, target_root, field_schema = load_field_schema(plugin_name, field)
```

- Replace custom sanitize logic with a battle‐tested library (e.g. python‐slugify):

```python
# utils/filename.py
from slugify import slugify
import uuid, datetime, os

def gen_safe_filename(tmpl: str, original: str) -> str:
    name, ext = os.path.splitext(original.lower())
    ctx = {
        "timestamp": datetime.utcnow().strftime("%Y%m%d-%H%M%S"),
        "uuid": uuid.uuid4().hex,
        "ext": ext,
        "name": slugify(name),
        "original": slugify(name) + ext
    }
    result = tmpl.format(**ctx)
    safe = slugify(result, separator="_") + (ext if not result.endswith(ext) else "")
    return safe
```

- Use `Path.is_relative_to()` instead of manual string checks:

```python
# In ensure_inside_root or inline
if not dest_path.is_relative_to(root):
    raise ValueError("非法路径")
```

These refactorings remove ~100 lines of plumbing per handler, centralize error‐handling/validation, and leverage existing libraries for filename‐sanitization and path safety.
</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.

refresh();
});

watch(() => props.pluginName + props.fieldKey, () => refresh());
Copy link
Contributor

Choose a reason for hiding this comment

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

吹毛求疵: 将 pluginName 和 fieldKey 拼接用于 watch 可能会导致不必要的刷新。

观察拼接后的字符串可能会在只有一个值更改时触发不必要的刷新。将 [pluginName, fieldKey] 作为数组观察将确保仅当其中任何一个值更改时才触发刷新。

Original comment in English

nitpick: Concatenating pluginName and fieldKey for watch may cause unnecessary refreshes.

Watching the concatenated string may trigger refreshes unnecessarily when only one value changes. Watching [pluginName, fieldKey] as an array will ensure refreshes occur only when either value changes.

return safe


class PluginConfigFileFieldRoute(Route):
Copy link
Contributor

Choose a reason for hiding this comment

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

问题 (复杂性): 考虑将错误处理、模式验证和文件名清理重构为可重用的助手和库,以减少样板代码并提高可维护性。

  • 将 HTTP 层样板代码(try/except + Response() 包装)提取到装饰器中,以使每个处理程序保持 DRY:
# utils/http.py
import traceback
from functools import wraps
from .route import Response
from astrbot.core import logger

def with_error_response(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Response().error(str(e)).__dict__
    return wrapper
# In PluginConfigFileFieldRoute
from utils.http import with_error_response

    @with_error_response
    async def list_files(self, plugin_name: str):
        # body now has no try/except or Response() wrapping
  • 将重复的“加载插件元数据 + 验证模式字段”合并到一个助手函数中:
# utils/plugin_schema.py
from astrbot.core.star.star import star_registry
from pathlib import Path

def load_field_schema(plugin_name: str, field: str) -> Tuple[Any, Path, Dict]:
    md = next((m for m in star_registry if m.name == plugin_name), None)
    if not md or not md.config:
        raise ValueError(f"插件 {plugin_name} 未注册配置")
    schema = md.config.schema or {}
    field_schema = schema.get(field)
    if not field_schema or field_schema.get("type") != "file":
        raise ValueError(f"字段 {field} 未定义为 file 类型")
    root = Path(StarTools.get_data_dir(plugin_name))
    dest = (root / field_schema["dest_dir"]).resolve()
    dest.mkdir(parents=True, exist_ok=True)
    if not dest.is_relative_to(root):
        raise ValueError("非法目录")
    return md, root, dest, field_schema
# then in each handler
md, root, target_root, field_schema = load_field_schema(plugin_name, field)
  • 使用经过实战检验的库(例如 python-slugify)替换自定义清理逻辑:
# utils/filename.py
from slugify import slugify
import uuid, datetime, os

def gen_safe_filename(tmpl: str, original: str) -> str:
    name, ext = os.path.splitext(original.lower())
    ctx = {
        "timestamp": datetime.utcnow().strftime("%Y%m%d-%H%M%S"),
        "uuid": uuid.uuid4().hex,
        "ext": ext,
        "name": slugify(name),
        "original": slugify(name) + ext
    }
    result = tmpl.format(**ctx)
    safe = slugify(result, separator="_") + (ext if not result.endswith(ext) else "")
    return safe
  • 使用 Path.is_relative_to() 而不是手动字符串检查:
# In ensure_inside_root or inline
if not dest_path.is_relative_to(root):
    raise ValueError("非法路径")

这些重构为每个处理程序减少了约 100 行的管道代码,集中了错误处理/验证,并利用现有库进行文件名清理和路径安全检查。

Original comment in English

issue (complexity): Consider refactoring error handling, schema validation, and filename sanitization into reusable helpers and libraries to reduce boilerplate and improve maintainability.

  • Extract HTTP‐layer boilerplate (try/except + Response() wrapping) into a decorator to DRY up each handler:
# utils/http.py
import traceback
from functools import wraps
from .route import Response
from astrbot.core import logger

def with_error_response(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Response().error(str(e)).__dict__
    return wrapper
# In PluginConfigFileFieldRoute
from utils.http import with_error_response

    @with_error_response
    async def list_files(self, plugin_name: str):
        # body now has no try/except or Response() wrapping
  • Consolidate repeated “load plugin‐md + validate schema field” into one helper:
# utils/plugin_schema.py
from astrbot.core.star.star import star_registry
from pathlib import Path

def load_field_schema(plugin_name: str, field: str) -> Tuple[Any, Path, Dict]:
    md = next((m for m in star_registry if m.name == plugin_name), None)
    if not md or not md.config:
        raise ValueError(f"插件 {plugin_name} 未注册配置")
    schema = md.config.schema or {}
    field_schema = schema.get(field)
    if not field_schema or field_schema.get("type") != "file":
        raise ValueError(f"字段 {field} 未定义为 file 类型")
    root = Path(StarTools.get_data_dir(plugin_name))
    dest = (root / field_schema["dest_dir"]).resolve()
    dest.mkdir(parents=True, exist_ok=True)
    if not dest.is_relative_to(root):
        raise ValueError("非法目录")
    return md, root, dest, field_schema
# then in each handler
md, root, target_root, field_schema = load_field_schema(plugin_name, field)
  • Replace custom sanitize logic with a battle‐tested library (e.g. python‐slugify):
# utils/filename.py
from slugify import slugify
import uuid, datetime, os

def gen_safe_filename(tmpl: str, original: str) -> str:
    name, ext = os.path.splitext(original.lower())
    ctx = {
        "timestamp": datetime.utcnow().strftime("%Y%m%d-%H%M%S"),
        "uuid": uuid.uuid4().hex,
        "ext": ext,
        "name": slugify(name),
        "original": slugify(name) + ext
    }
    result = tmpl.format(**ctx)
    safe = slugify(result, separator="_") + (ext if not result.endswith(ext) else "")
    return safe
  • Use Path.is_relative_to() instead of manual string checks:
# In ensure_inside_root or inline
if not dest_path.is_relative_to(root):
    raise ValueError("非法路径")

These refactorings remove ~100 lines of plumbing per handler, centralize error‐handling/validation, and leverage existing libraries for filename‐sanitization and path safety.

Comment on lines +203 to +205
if isinstance(max_mb, (int, float)) and max_mb > 0:
if len(content) > max_mb * 1024 * 1024:
return Response().error("文件过大").__dict__
Copy link
Contributor

Choose a reason for hiding this comment

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

建议 (代码质量): 合并嵌套的 if 条件 (merge-nested-ifs)

Suggested change
if isinstance(max_mb, (int, float)) and max_mb > 0:
if len(content) > max_mb * 1024 * 1024:
return Response().error("文件过大").__dict__
if isinstance(max_mb, (int, float)) and max_mb > 0 and len(content) > max_mb * 1024 * 1024:
return Response().error("文件过大").__dict__


解释过多的嵌套会使代码难以理解,在 Python 中尤其如此,因为没有括号来帮助区分不同的嵌套级别。

阅读深度嵌套的代码令人困惑,因为您必须跟踪哪些条件与哪些级别相关。因此,我们力求在可能的情况下减少嵌套,而使用 and 组合两个 if 条件的情况是一个简单的胜利。

Original comment in English

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if isinstance(max_mb, (int, float)) and max_mb > 0:
if len(content) > max_mb * 1024 * 1024:
return Response().error("文件过大").__dict__
if isinstance(max_mb, (int, float)) and max_mb > 0 and len(content) > max_mb * 1024 * 1024:
return Response().error("文件过大").__dict__


ExplanationToo much nesting can make code difficult to understand, and this is especially
true in Python, where there are no brackets to help out with the delineation of
different nesting levels.

Reading deeply nested code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

Comment on lines +18 to +21
for md in star_registry:
if md.name == plugin_name:
return md
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

建议 (代码质量): 使用内置函数 next 而不是 for 循环 (use-next)

Suggested change
for md in star_registry:
if md.name == plugin_name:
return md
return None
return next((md for md in star_registry if md.name == plugin_name), None)
Original comment in English

suggestion (code-quality): Use the built-in function next instead of a for-loop (use-next)

Suggested change
for md in star_registry:
if md.name == plugin_name:
return md
return None
return next((md for md in star_registry if md.name == plugin_name), None)

original_clean = f"{name_clean}{ext}"

# Support direct original name keepers
if tmpl.strip().lower() in ("original", "{original}", "{filename}"):
Copy link
Contributor

Choose a reason for hiding this comment

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

建议 (代码质量): 在检查字面量集合的成员资格时使用集合 (collection-into-set)

Suggested change
if tmpl.strip().lower() in ("original", "{original}", "{filename}"):
if tmpl.strip().lower() in {"original", "{original}", "{filename}"}:
Original comment in English

suggestion (code-quality): Use set when checking membership of a collection of literals (collection-into-set)

Suggested change
if tmpl.strip().lower() in ("original", "{original}", "{filename}"):
if tmpl.strip().lower() in {"original", "{original}", "{filename}"}:

logger.error(traceback.format_exc())
return Response().error(str(e)).__dict__

async def upload_file(self, plugin_name: str):
Copy link
Contributor

Choose a reason for hiding this comment

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

问题 (代码质量): 在 PluginConfigFileFieldRoute.upload_file 中发现代码质量低 - 25% (low-code-quality)


解释此函数的质量得分低于 25% 的质量阈值。
此得分是方法长度、认知复杂度和工作内存的组合。

您如何解决这个问题?

可能值得重构此函数,使其更短、更易读。

  • 通过将功能片段提取到自己的函数中来减少函数长度。这是您可以做的最重要的事情——理想情况下,一个函数应该少于 10 行。
  • 减少嵌套,也许可以通过引入卫语句来提前返回。
  • 确保变量的作用域紧密,以便使用相关概念的代码在函数内部集中在一起,而不是分散开来。
Original comment in English

issue (code-quality): Low code quality found in PluginConfigFileFieldRoute.upload_file - 25% (low-code-quality)


ExplanationThe quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

  • Reduce the function length by extracting pieces of functionality out into
    their own functions. This is the most important thing you can do - ideally a
    function should be less than 10 lines.
  • Reduce nesting, perhaps by introducing guard clauses to return early.
  • Ensure that variables are tightly scoped, so that code using related concepts
    sits together within the function rather than being scattered.

Comment on lines +274 to +276
else:
if new_conf.get(field) == rel_path:
new_conf[field] = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

建议 (代码质量): 将 else 子句的嵌套 if 语句合并到 elif 中 (merge-else-if-into-elif)

Suggested change
else:
if new_conf.get(field) == rel_path:
new_conf[field] = ""
elif new_conf.get(field) == rel_path:
new_conf[field] = ""
Original comment in English

suggestion (code-quality): Merge else clause's nested if statement into elif (merge-else-if-into-elif)

Suggested change
else:
if new_conf.get(field) == rel_path:
new_conf[field] = ""
elif new_conf.get(field) == rel_path:
new_conf[field] = ""

@xunxiing xunxiing changed the title 在插件配置文件加入type="file",允许用户在配置界面上传文件 feat:在插件配置文件加入type="file",允许用户在配置界面上传文件 Sep 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant