Skip to content

feat(webui): split dashboard into module pages for 3.0.1#112

Open
EterUltimate wants to merge 2 commits into
NickCharlie:mainfrom
EterUltimate:main
Open

feat(webui): split dashboard into module pages for 3.0.1#112
EterUltimate wants to merge 2 commits into
NickCharlie:mainfrom
EterUltimate:main

Conversation

@EterUltimate
Copy link
Copy Markdown
Collaborator

@EterUltimate EterUltimate commented May 23, 2026

Summary

  • bump plugin version to 3.0.1 across metadata, package marker, docs, and badges
  • split the WebUI dashboard into a module home plus dedicated hash pages for overview, AI insights, monitoring, reviews, learning content, graphs, and settings
  • keep the existing lightweight single-template runtime and add static regression coverage for the new module navigation
  • retain the AstrBot provider type classification fixes for v2 Embedding and Reranker selectors

Tests

  • python -m pytest tests\integration\test_webui_static_assets.py
  • python -m py_compile init.py
  • python -c "from pathlib import Path; files=['metadata.yaml','init.py','README.md','README_EN.md','docs/README.md','CHANGELOG.md']; print('version check ok' if all('3.0.1' in Path(f).read_text(encoding='utf-8') for f in files) else 'version check failed')"
  • node -e "const fs=require('fs'); const html=fs.readFileSync('web_res/static/html/dashboard.html','utf8'); const scripts=[...html.matchAll(/<script[^>]>([\s\S]?)</script>/gi)].map(m=>m[1]); for (const s of scripts) new Function(s); console.log('scripts parsed', scripts.length);"
  • git diff --check

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 23, 2026

Reviewer's Guide

Classifies WebUI provider configuration and dashboard controls by normalized AstrBot provider types, enriching provider metadata with type labels and sourcing options from both runtime provider instances and AstrBot provider config, while adding backend and frontend tests to ensure correct grouping and filtering.

Sequence diagram for dashboard provider field rendering by normalized type

sequenceDiagram
    participant Browser
    participant DashboardJS as dashboard_html_js
    participant Backend as ConfigService

    Browser->>DashboardJS: requestConfigSchema()
    DashboardJS->>Backend: get_config_schema
    Backend-->>DashboardJS: payload with provider_options_by_type

    DashboardJS->>DashboardJS: normalizeProviderType(field.provider_type)
    DashboardJS->>DashboardJS: providerOptionsForField(field)
    DashboardJS->>DashboardJS: providerTypeLabel(field.provider_type)

    DashboardJS->>Browser: render select with
    DashboardJS-->>Browser: option.label + providerTypeLabel(option.provider_type)
Loading

Flow diagram for backend provider option aggregation by normalized type

flowchart TD
    A["_provider_options(expected_type)"] --> B["normalize_provider_type(expected_type)"]
    B --> C[get_service_factory]
    C --> D[get context]
    D --> E{expected in chat_completion group?}
    E -->|yes| F[_provider_option for context.get_all_providers]
    E -->|yes| G[_provider_option for provider_manager.provider_insts]
    D --> H{expected in embedding group?}
    H -->|yes| I[_provider_option for context.get_all_embedding_providers]
    H -->|yes| J[_provider_option for provider_manager.embedding_provider_insts]
    D --> K{expected in rerank group?}
    K -->|yes| L[_provider_option for rerank providers]
    D --> M["_provider_source_types(provider_manager)"]
    M --> N["_provider_option_from_config for provider_manager.providers_config"]
    N --> O[filter by expected type]
    F --> P[_dedupe_options]
    G --> P
    I --> P
    J --> P
    L --> P
    O --> P
    P --> Q[(provider options)]
Loading

File-Level Changes

Change Details Files
Normalize and label provider types in ConfigService and include type-aware provider options from AstrBot configs.
  • Introduce provider type alias and label maps for chat_completion, embedding, and rerank types.
  • Add a _normalize_provider_type helper and use it in _provider_type_value to unify provider type handling.
  • Extend provider option structures with provider_type_label and support building options from providers_config using provider_source_types mapping.
  • Add _provider_source_types helper to derive provider types from provider_sources_config and merge config-derived options into _provider_options.
  • Populate provider_type and provider_type_label for provider widget fields, preferring field-level expected type or schema _provider_type.
webui/services/config_service.py
Update backend tests to cover AstrBot provider classification and enriched provider metadata.
  • Extend test container context with provider_insts and embedding_provider_insts to mirror new provider sources.
  • Assert provider_type_label presence for embedding, rerank, and chat_completion provider fields in config schema.
  • Add regression test verifying provider options and labels sourced from provider_sources_config and providers_config are correctly classified and exposed in schema.
tests/unit/test_config_service.py
Enhance dashboard UI to filter provider selects by AstrBot provider type and display localized type labels.
  • Add PROVIDER_TYPE_LABELS and normalization utilities in dashboard.html to align frontend provider types with backend classification.
  • Introduce providerOptionsForField to select and filter options from provider_options_by_type and field.options based on normalized provider_type.
  • Update provider field badge and select option rendering to use localized provider type labels and include type in option labels.
  • Change provider text input placeholder to mention missing provider of a specific type and instruct manual ID entry.
web_res/static/html/dashboard.html
Add integration coverage for dashboard provider filtering behavior and schema wiring.
  • Add integration test ensuring dashboard.html embeds providerOptionsForField logic, provider_options_by_type references, type labels, and updated placeholder text.
tests/integration/test_webui_static_assets.py
Adjust configuration schema JSON to align with new provider classification metadata (details not fully shown in diff).
  • Update _conf_schema.json to reflect provider_type/provider_type_label usage or related schema changes.
_conf_schema.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@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 - I've found 3 issues, and left some high level feedback:

  • The provider option construction logic in _provider_option and _provider_option_from_config is very similar; consider extracting a shared helper to avoid subtle drift in how labels and type metadata are built for different provider sources.
  • The provider type label mappings are duplicated between _PROVIDER_TYPE_LABELS in Python and PROVIDER_TYPE_LABELS in the dashboard JS; if possible, derive the labels from the backend‐supplied schema to avoid future divergence when types or labels change.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The provider option construction logic in `_provider_option` and `_provider_option_from_config` is very similar; consider extracting a shared helper to avoid subtle drift in how labels and type metadata are built for different provider sources.
- The provider type label mappings are duplicated between `_PROVIDER_TYPE_LABELS` in Python and `PROVIDER_TYPE_LABELS` in the dashboard JS; if possible, derive the labels from the backend‐supplied schema to avoid future divergence when types or labels change.

## Individual Comments

### Comment 1
<location path="web_res/static/html/dashboard.html" line_range="3174-3176" />
<code_context>
                     badges.push('重启后生效');
                 }
                 if (field.widget === 'provider') {
-                    badges.push('Provider');
+                    badges.push(providerTypeLabel(field.provider_type || field.provider_type_label));
                 }
                 if (field.nullable) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Re-wrapping provider_type_label with providerTypeLabel is redundant and may be fragile.

Using `providerTypeLabel(field.provider_type || field.provider_type_label)` means we may pass an already-localized `provider_type_label` back into a function that expects a type key, which can break if labels are non-lowercase or change. Instead, use `field.provider_type_label` directly and only call `providerTypeLabel(field.provider_type)` as a fallback when the label is absent.

```suggestion
                if (field.widget === 'provider') {
                    // Prefer the pre-localized label if present; fall back to deriving it from provider_type
                    badges.push(field.provider_type_label || providerTypeLabel(field.provider_type));
                }
```
</issue_to_address>

### Comment 2
<location path="web_res/static/html/dashboard.html" line_range="3228-3237" />
<code_context>
+                } else if (field.widget === 'provider' && providerOptionsForField(field).length) {
</code_context>
<issue_to_address>
**suggestion:** providerOptionsForField re-computes payload and filtering on every render and could be cached or simplified.

In the provider branch of `renderConfigField`, `providerOptionsForField(field)` is invoked twice (once for the length check and once for the options themselves), each time re-reading the schema payload and re-filtering. Consider calling it once, storing the result in a local variable, and reusing it to simplify control flow and avoid any future inconsistency if the payload becomes mutable.

Suggested implementation:

```
                if (field.nullable) {
                    badges.push('可空');
                            ${field.editable ? '' : 'disabled'}
                        >${selectOptions.join('')}</select>
                    `;
                } else if (field.widget === 'provider') {
                    const options = providerOptionsForField(field);
                    if (!options.length) {
                        return '';
                    }
                    const currentValue = displayValue || '';
                    const optionSet = options.some((option) => option.value === currentValue);
                    const selectOptions = [
                        `<option value="">未设置</option>`,
                        ...options.map((option) => {
                            const optionValue = option.value ?? '';
                            const label = option.label || optionValue;

```

If `renderConfigField` should not early-return an empty string for provider fields without options (for example, if surrounding code expects a specific default markup instead of nothing), adjust the `return ''` line accordingly. You may instead no-op or fall back to the standard text widget rendering, depending on your existing conventions in this function.
</issue_to_address>

### Comment 3
<location path="web_res/static/html/dashboard.html" line_range="3261" />
<code_context>
                             type="text"
                             value="${escapeHtml(displayValue)}"
-                            placeholder="未配置"
+                            placeholder="未找到 ${escapeHtml(providerTypeLabel(field.provider_type))} Provider,可手动填写 Provider ID"
                             data-config-key="${escapeHtml(field.key)}"
                             data-config-type="${escapeHtml(field.type)}"
</code_context>
<issue_to_address>
**suggestion:** Placeholder ignores provider_type_label and may show a generic or raw type instead of the localized label.

Here you rely only on `providerTypeLabel(field.provider_type)` for the placeholder, but other parts of the UI also use `field.provider_type_label`. If the backend only sets `provider_type_label` or the type isn’t recognized, this will fall back to a generic label or internal key. To stay consistent with the badge/select labels and avoid confusing text, use `field.provider_type_label || providerTypeLabel(field.provider_type)` instead.

```suggestion
                            placeholder="未找到 ${escapeHtml(field.provider_type_label || providerTypeLabel(field.provider_type))} Provider,可手动填写 Provider ID"
```
</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.

Comment on lines 3174 to 3176
if (field.widget === 'provider') {
badges.push('Provider');
badges.push(providerTypeLabel(field.provider_type || field.provider_type_label));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Re-wrapping provider_type_label with providerTypeLabel is redundant and may be fragile.

Using providerTypeLabel(field.provider_type || field.provider_type_label) means we may pass an already-localized provider_type_label back into a function that expects a type key, which can break if labels are non-lowercase or change. Instead, use field.provider_type_label directly and only call providerTypeLabel(field.provider_type) as a fallback when the label is absent.

Suggested change
if (field.widget === 'provider') {
badges.push('Provider');
badges.push(providerTypeLabel(field.provider_type || field.provider_type_label));
}
if (field.widget === 'provider') {
// Prefer the pre-localized label if present; fall back to deriving it from provider_type
badges.push(field.provider_type_label || providerTypeLabel(field.provider_type));
}

Comment on lines +3228 to +3237
} else if (field.widget === 'provider' && providerOptionsForField(field).length) {
const currentValue = displayValue || '';
const options = safeArray(field.options || []);
const options = providerOptionsForField(field);
const optionSet = options.some((option) => option.value === currentValue);
const selectOptions = [
`<option value="">未设置</option>`,
...options.map((option) => {
const optionValue = option.value ?? '';
const label = option.label || optionValue;
return `<option value="${escapeHtml(optionValue)}" ${optionValue === currentValue ? 'selected' : ''}>${escapeHtml(label)}</option>`;
const optionType = providerTypeLabel(option.provider_type || field.provider_type);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: providerOptionsForField re-computes payload and filtering on every render and could be cached or simplified.

In the provider branch of renderConfigField, providerOptionsForField(field) is invoked twice (once for the length check and once for the options themselves), each time re-reading the schema payload and re-filtering. Consider calling it once, storing the result in a local variable, and reusing it to simplify control flow and avoid any future inconsistency if the payload becomes mutable.

Suggested implementation:

                if (field.nullable) {
                    badges.push('可空');
                            ${field.editable ? '' : 'disabled'}
                        >${selectOptions.join('')}</select>
                    `;
                } else if (field.widget === 'provider') {
                    const options = providerOptionsForField(field);
                    if (!options.length) {
                        return '';
                    }
                    const currentValue = displayValue || '';
                    const optionSet = options.some((option) => option.value === currentValue);
                    const selectOptions = [
                        `<option value="">未设置</option>`,
                        ...options.map((option) => {
                            const optionValue = option.value ?? '';
                            const label = option.label || optionValue;

If renderConfigField should not early-return an empty string for provider fields without options (for example, if surrounding code expects a specific default markup instead of nothing), adjust the return '' line accordingly. You may instead no-op or fall back to the standard text widget rendering, depending on your existing conventions in this function.

type="text"
value="${escapeHtml(displayValue)}"
placeholder="未配置"
placeholder="未找到 ${escapeHtml(providerTypeLabel(field.provider_type))} Provider,可手动填写 Provider ID"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Placeholder ignores provider_type_label and may show a generic or raw type instead of the localized label.

Here you rely only on providerTypeLabel(field.provider_type) for the placeholder, but other parts of the UI also use field.provider_type_label. If the backend only sets provider_type_label or the type isn’t recognized, this will fall back to a generic label or internal key. To stay consistent with the badge/select labels and avoid confusing text, use field.provider_type_label || providerTypeLabel(field.provider_type) instead.

Suggested change
placeholder="未找到 ${escapeHtml(providerTypeLabel(field.provider_type))} Provider,可手动填写 Provider ID"
placeholder="未找到 ${escapeHtml(field.provider_type_label || providerTypeLabel(field.provider_type))} Provider,可手动填写 Provider ID"

@EterUltimate EterUltimate changed the title fix(webui): classify v2 providers by AstrBot type feat(webui): split dashboard into module pages for 3.0.1 May 23, 2026
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