- Add "Upload Plugin" and "提交插件" contact links to the new issue page
- Point both links to [AstrBot Cloud](https://cloud.astrbot.app) so users can submit plugins without opening a regular issue
* fix: reject unresolvable image references instead of faking a local path
A plugin can hand over a media reference such as
`/api/v1/files/tokens/<token>`. Neither an existing file nor a valid
payload, it was still returned as a local file, so `to_path()` resolved it
into a path that does not exist on disk (on Windows
`C:\api\v1\files\tokens\<token>`). That fabricated path reached
`req.image_urls` and the prompt, and the real failure only surfaced later
as a warning while preparing model images.
`_materialize_media_ref()` now enforces its own contract:
- A reference that exists but is not a regular file (directory, FIFO,
socket, device) raises ValueError for every media family.
- Image references must resolve to an existing file, URL or base64 payload.
Images are read right after resolution, so a missing source is a failure
rather than a file that may appear later.
- Audio, video and generic file references keep returning a local path that
does not exist yet: `ensure_wav()` and the STT retry loop rely on that for
platform races such as NapCat handing over a voice path before it is
readable.
Supporting changes:
- `to_base64_data(strict=False)` still degrades to `None` when an image ref
cannot be materialized, which payload assembly relies on to skip bad refs.
- `describe_media_ref()` reports unresolved refs as such instead of calling
them a local media path.
- Unavailable image warnings now include the original reference.
Refs #10264
* fix: keep pathlib tolerance for over-long and non-regular media refs
`_materialize_media_ref()` replaced `path.exists()` with a bare `path.stat()`
and caught only OSError. pathlib's `exists()` also swallows ValueError, which
Windows raises for paths past the 32767 character limit and for embedded NUL
bytes. A bare base64 payload longer than that limit failed with
`ValueError: stat: path too long for Windows` instead of being decoded, and
audio/video/file references lost the pending-path behaviour for the same input.
`to_base64_data()` had the matching gap: it caught only OSError around
resolution, so a directory or empty reference escaped as ValueError even in
non-strict mode. Payload assembly (openai/anthropic/gemini sources) relies on
`None` there to skip one bad image instead of failing the whole request, and
`is_recoverable_image_error()` already classifies ValueError as recoverable.
Both sites now catch what pathlib and the module's own recovery predicate treat
as a missing or unusable local file. Regression tests cover an oversized bare
base64 image, an oversized pending audio path, and directory/empty references
through the non-strict image path.
* docs: describe the media reference error contract precisely
Review feedback on the pull request:
- `_materialize_media_ref()` only raises FileNotFoundError for a missing file
URI when the reference is an image. Audio, video and file references return
the pending path instead, which the `Raises:` section now states instead of
reading as if it applied to every media family.
- `Image.convert_to_file_path()` can raise ValueError as well: for an existing
non-regular path, and already for an empty url/file before this branch.
Docstrings only, no behaviour change.
* fix: send Discord video components as attachments
* fix: stream Discord video attachments from disk
* fix: preserve Discord video filenames from URLs
* fix(provider): merge custom server tools with registered tools in Anthropic requests
* fix(provider): dedupe tool names and tolerate null custom body
Address review findings on the custom-server-tools merge:
- A custom tool sharing a name with a registered tool (or another
custom tool) produced duplicate definitions that the API rejects.
Merge by name instead; the custom entry wins, matching the override
authority extra_body already had for every other top-level key.
- An explicit null custom_extra_body crashed on .get("tools"). Null
or non-dict values now normalize to an empty body.
* fix(provider): avoid shadowed id in Anthropic streaming tool merge
---------
Co-authored-by: zouyonghe <62183434+zouyonghe@users.noreply.github.com>
* fix: fall back from a failed context summary without long retries
The summary request in LLMSummaryCompressor used the provider's default
retries, so an exhausted quota (HTTP 429) was retried five times with
backoff before compression fell back to truncation, holding up the reply
even after switching the chat model.
Request the summary once, since a failure already falls back to
truncation. When the context still exceeds the limit, drop whole rounds,
oldest first, and recount after each one until it fits, instead of
halving the messages once without checking again. The latest round is
always kept.
Fixes#10433
* fix: leave the token count out of the fallback warning
CodeQL flags values from count_tokens() as sensitive data, so logging the
count in the new warning raised a clear-text logging alert. The count is
already in the "Compress completed" line just before, so drop it here.
* fix: log how many rounds the context fallback dropped
* fix(provider): keep Anthropic tool calls whose streamed input is empty
* fix(provider): keep the input from the Anthropic tool_use start event
The tool_use buffer always started from an empty dict, so a provider
that puts the arguments in the content_block_start event instead of
streaming them lost them. Start from that input and let streamed JSON
replace it only when there is any.
The Agent Computer Use settings moved from 'Config -> Normal Config ->
AI -> Agent Computer Use' to 'Config -> AI -> Capabilities -> Agent
Computer Use'. Update the three permission-denied messages returned to
the model/user so the remediation path matches the current WebUI
navigation, consistent with the existing docs.
On platforms without a local process sandbox (e.g. Windows), the local
permission matrix collapses into a single access-mode dropdown. The
'File access only' and 'Unrestricted access' options previously shared
the same host-scope subtitle, making them look identical. Give each
mode its own hint that spells out whether Shell/Python execution and
network access are allowed.
* feat: ship astrbot CLI in Docker image for in-container password reset
Install the project itself into the image so the `astrbot` console
script (including `astrbot password` and `astrbot conf set`) is
available inside the container, and mark /AstrBot as an initialized
AstrBot root via the .astrbot marker file.
This lets Docker users reset a forgotten dashboard password with:
docker exec -it astrbot astrbot password
Document the workflow in the zh/en Docker deployment guides, including
the required container restart for the new password to take effect.
* docs: reorganize FAQ password reset section by deployment method
Split the forgot-password FAQ entry into Docker, uv/pip package, and
other deployment subsections. The Docker entry uses the in-container
CLI shipped with the image; the uv/pip entry covers `astrbot password`
and `astrbot run --reset-password`; the manual cmd_config.json edit
remains as the universal fallback.
* docs: mention --username option in uv/pip FAQ password reset entry
* docs: drop --reset-password mention from FAQ to keep reset steps unambiguous
* docs: clarify that the first astrbot in docker exec is the container name
* feat: use editable install for astrbot CLI in Docker image
Install the project with 'uv pip install -e .' so the astrbot console
script always reflects the source tree baked into the image, avoiding
drift between the CLI and the running server code.
* refactor(dashboard): complete the plugin pages to views rename
Finish the remaining Pages -> Views rename on the dashboard:
- Fix the plugin card "Open View" button crash by pointing
openPluginWebui at the renamed PluginView route
- Rename PluginPagePage.vue to PluginViewPage.vue and update its
internals (loadPluginView, pluginViewTitle, .plugin-view-* classes)
- Rename openPluginWebui -> openPluginView and the open-webui emit to
open-view; pluginPageTitle/Description -> pluginViewTitle/Description;
isPluginPageRoute -> isPluginViewRoute
- Rename i18n keys (pluginWebui -> pluginView, openWebui -> openView,
pluginPageType -> pluginViewType, messages) across en/zh/ja/ru locales
The postMessage bridge channel name (astrbot-plugin-page) is kept
unchanged because it is part of the wire protocol shared with
plugin_page_bridge.js.
* feat(dashboard): authenticate plugin view assets with scoped path tokens
Serve plugin view assets from
/api/v1/plugins/{id}/views/{page}/_t/{token}/... so the scoped token
travels in the URL path. Relative URLs inside the view then inherit the
token through normal path resolution, which removes the need for
server-side HTML/CSS/JS URL rewriting on this route.
- Add the _t routes with a require_plugin_view_token dependency
(v1 paths bypass the dashboard auth middleware)
- Extend the plugin page asset token to a session-length TTL (7 days,
aligned with the dashboard session) with a page_session purpose claim,
still scoped to a single plugin and page
- Serialize the view entry content_path as the _t URL, keeping the
asset_token query for plugins that parse location.search
- Keep the legacy /api/plugin/page/content/...?asset_token= form working
during the transition
- Rename the OpenAPI tag Plugin Pages -> Plugin Views, document the new
route, regenerate the frontend API client, and update the zh/en
plugin-pages docs
* refactor(dashboard): remove the legacy plugin page content route and URL rewriting
The path-token view route makes the compatibility surface unnecessary:
- Drop /api/plugin/page/content/... (query asset_token form)
- Drop the redundant authenticated asset routes /plugins/page/assets,
/plugins/view/assets, and /plugins/{id}/(pages|views)/{page}/assets/...
which had no callers outside the generated client
- Remove the HTML/CSS/JS URL rewriting helpers; view assets are always
served raw and only HTML gets bridge SDK injection plus theme handling
- Slim PluginPageAuth down to the bridge SDK endpoint, the only
non-v1 path still authenticating scoped query tokens
- serialize_plugin_page no longer falls back to the legacy content path
- Update the plugin-pages docs (zh/en) and the OpenAPI spec, and
regenerate the frontend API client
* refactor(dashboard): drop legacy plugin page API aliases
Remove the page-named plugin view API aliases now that the views rename
is complete and legacy compatibility is intentionally dropped:
- DELETE /api/v1/plugins/page(s) and /plugins/{plugin_id}/pages{,/{page_name}}
aliases; /plugins/view(s) and /plugins/{plugin_id}/views{,/{page_name}}
are the only entry config routes
- Remove the unused /api/v1/plugins/page-bridge-sdk.js route; the live
bridge SDK stays at /api/plugin/page/bridge-sdk.js
- Dashboard calls the view-named endpoints; regenerate the API client
and the OpenAPI scopes docs
* refactor(dashboard): keep plugin page APIs as deprecated view aliases
Restore the page-named plugin view routes removed in the previous commit
and keep them for a transition period, with view as the canonical API:
- /plugins/page(s) and /plugins/{plugin_id}/pages{,/{page_name}} serve as
aliases of the corresponding /views routes (same handlers)
- The OpenAPI spec marks the page operations deprecated and points new
clients at the view routes; the v1 page-bridge-sdk.js route is restored
- The dashboard keeps calling the view endpoints by default
* refactor(dashboard): rename the plugin view path parameter to view_name
The view entry and path-token asset routes now use {view_name} instead of
{page_name} in the OpenAPI-visible path, and the service layer renames its
internal page_name variables to match. Wire-compatible surfaces stay
unchanged: the page_name JWT claim, the pageName JSON field, query
parameters, and the deprecated /pages aliases. Error messages and the
plugin view docs (zh/en) complete the Page to View rename.
The Gemini Embedding provider ships enabled by default with
gemini-embedding-exp-03-07 as both the config default and the
source-level fallback. Google retired that experimental model when
gemini-embedding-001 went GA, so a fresh deployment that enables the
provider fails on its first request.
gemini-embedding-001 keeps the configured output_dimensionality path
intact: it supports MRL, so the default 768 dimensions still hold.
Ref #10317
* perf(dashboard): lazy-load VAD dependencies on demand
Load onnxruntime-web and vad-web via dynamic script injection when the
VAD initializes instead of render-blocking script tags in index.html,
so they no longer delay the first paint on every page load.
* perf(dashboard): shrink first-screen bundle and gzip responses
- Lazy-load Monaco Editor (~3.3 MB) via an async wrapper component
instead of importing it in main.ts; the editor bundle now loads only
when an editor is first mounted.
- Load i18n locale data on demand: each language becomes its own async
chunk instead of bundling all four locales (~1.4 MB of JSON) into the
entry.
- Register vue3-apexcharts locally in StatsPage instead of globally in
main.ts, moving ~470 KB out of the entry chunk.
- Add Starlette GZipMiddleware to the dashboard ASGI app so static
assets and API responses are compressed (SSE is excluded by default).
Entry chunk: 4.5 MB -> 648 KB (gzip: 1.25 MB -> 210 KB).
* fix: match command group names as whole words
CommandGroupFilter matched messages with a plain str.startswith, so a
group named "math" also matched "mathematics". Once the group was
restricted to admins, non-admins got the permission-denied reply for any
message that merely started with the group name, such as a normal
private chat message or another plugin's command.
Match group names the same way CommandFilter does: collapse whitespace,
then require an exact match or the name followed by a space.
* fix: normalize command group equality whitespace
Click a staged image thumbnail in the ChatUI input area to open a full-screen preview overlay, matching the behavior of sent message images. The preview state is self-contained within ChatInput so both Chat and StandaloneChat get it without extra wiring.
* docs: sync Lark/Feishu guide with current capabilities and permissions
The Lark guide still described the adapter as of v4.15.0:
- Receiving voice, video, and files has been supported since v4.16.0, and quoted (reply) message contents are parsed as well.
- Documented config field names (encrypt_key, verification_token) did not match the actual lark_encrypt_key / lark_verification_token.
- The permission list recommended im:message:send and im:resource:upload, which Feishu no longer accepts for new applications; im:message covers sending and im:resource covers resource upload/download. The one-click permission URL is updated accordingly.
- Group chats only receive @-mentioned messages unless im:message.group_msg is granted, so the wake-prefix wording and the final /help step were corrected.
- Long connections are supported for both Lark China and Lark International; webhook is only a fallback, not a requirement for international Lark.
* docs: correct Lark media support in plugin platform matrix
The zh plugin platform matrix marked Lark Record and Video as unsupported, but the adapter receives and sends both.
* docs: clarify Lark group delivery and wake conditions
The group-chat wording only mentioned @ mentions. Delivery (which events Lark pushes to AstrBot) and waking (which received messages AstrBot responds to) are separate layers: a group chat only pushes messages that @ mention the bot unless im:message.group_msg is granted, while AstrBot also responds to @all (unless ignore_at_all is set), replies to its own messages, and wake-prefixed messages.
* fix(slack): preserve code block and quote text
* fix(slack): preserve quote boundaries around mentions
* fix(slack): preserve leading mention wake targeting
* test(slack): trim rich text regression coverage
* test(slack): focus rich text tests on message conversion
* fix(slack): preserve whitespace-only block content
* fix(dashboard): store chat attachments under unique names
Uploads were saved under their original filename, so two attachments with
the same name (every pasted screenshot is image.png) overwrote each other,
and deleting one session removed a file another session still used.
Store each upload as <timestamp id>_<name> and return the original name as
`filename` for display, with the on-disk name in `stored_filename`.
Fixes#10352
* fix(dashboard): keep long-suffix attachment names within 255 bytes
* fix: keep a single editable system default persona
Materialize the built-in default persona as a real DB record (adopt an existing persona named "default", otherwise seed it), resolve it by name in both lookup paths, and prevent deleting it. The WebUI shows it as a normal, editable persona without a delete action. Adds debug logs around persona resolution to make both lookup paths observable.
* fix: avoid logging persona prompt content
Only log the persona id and resolution source; system prompts may contain sensitive content and these paths run on every main-agent and subagent persona lookup.
* fix: preserve WebChat default system prompt contract
The seeded system default persona made resolve_selected_persona always return a persona for the implicit default path, so _ensure_persona_and_skills never reached the WebChat ChatUI default branch: enable_default_system_prompt=False could no longer disable the prompt and the default text changed.
Treat the config-fallback-to-default case on WebChat as the implicit system default and keep using CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT (flag-gated). Non-WebChat and explicit selection keep resolving to the editable DB default persona. Add unit and integration coverage for the resolution and injection paths.
* refactor: extract SYSTEM_DEFAULT_PERSONA_ID constant
* fix(dashboard): keep default persona pinned in persona selector
* chore: delete unnecessary logs
---------
Co-authored-by: Inertia <164627892+NonInertialObserver@users.noreply.github.com>
* fix(dashboard): restore plugin README anchor navigation
The plugin detail page renders README via v-html but never generated heading ids and never intercepted in-page anchors. Since the router uses hash mode, clicking a table-of-contents link such as [section](#section) replaced the route hash and skipped scrolling.
Generate heading ids while rendering Markdown and add a delegated click handler that scrolls to the target element without touching the router hash. Apply the same handler to the README and changelog sections, and add scroll-margin-top so the sticky header does not cover the target heading.
* fix(dashboard): harden plugin README anchor handling
Address review feedback on the anchor fix:
- Assign heading ids against a set of all used ids and increment the suffix
until the candidate is free, so a heading named like an existing duplicate
suffix (A, A-1, A) no longer collides.
- Always preventDefault on a[href^="#"] before resolving the target, so empty
(href="#"), malformed, or missing-fragment links cannot reach the hash-mode
router.
- Guard decodeURIComponent with try/catch so malformed percent-encoded
fragments no longer abort the handler.
Display the origin of each skill in the Local Skills list so users can
tell independently installed skills apart from plugin-provided and
sandbox-preset ones without reading the file path.
- Add a source chip in the skill card title area:
- plugin skills show "Plugin: {name}", preferring the plugin display
name and falling back to the source label / plugin name
- local-only, local + sandbox and sandbox-preset skills get their own
labels
- Reuse the existing skills.sourcePlugin/sourceBoth/sourceSandboxOnly/
sourceLocalOnly i18n keys that are already present in all four locales.
- Narrow the existing "preset" chip condition to skill.preset so it no
longer duplicates the sandbox-preset source label.
- Keep the disabled and read-only behavior unchanged.
build_chat_stream accepted a client-supplied session_id and only
checked that the session exists, without verifying that the caller
is its creator. An authenticated user could inject messages (and the
bot's replies) into another user's session history, with the LLM run
executing in the victim's conversation context.
Apply the same creator check used by ensure_chat_session and the
sibling ChatService operations before any message is accepted, fail
closed when the session lookup errors, and add a two-user regression
test.
An empty Record (no file/url/path) or a failed audio conversion raised a bare Exception from convert_to_file_path() and aborted request assembly. In the local agent path the raw exception text was then sent to chat as the reply, and in the third-party agent path the whole turn failed silently.
Degrade the same way images already do: log a warning and append a placeholder instead of propagating. The recoverability gate used by the image branches does not apply here because Record and the audio converter raise bare exceptions, not the OSError/ValueError family.
Co-authored-by: KazusaUwU <204833675+JosephTian876@users.noreply.github.com>
* feat(dashboard): add log search and keyword highlighting to console
- Add a debounced search field to ConsoleDisplayer, gated behind a new
showSearch prop so it stays opt-in for other callers.
- Filter visible log lines by keyword, case-insensitively and ignoring
ANSI color codes, while keeping the existing level/category filters.
- Highlight matched keywords in the prefix, level and message spans.
- Enable the search box on the Console page via show-search.
- Add features/console.search translations for en-US, ja-JP, ru-RU and
zh-CN.
* fix(dashboard): align log search highlighting with ANSI-stripped text
Strip ANSI escape sequences in appendHighlightedText before locating keyword matches, using the same stripped text as matchesKeyword. This keeps highlight offsets aligned so color-coded log lines highlight the actual keyword instead of wrapping the wrong characters or including control codes.
ChatUI rendered the raw name 'default' for the default configuration profile in the '+' menu and its selection dialog, while the config page showed a localized label. Reuse the features/config i18n key 'configSelection.defaultConfig' when id is 'default', matching ConfigProfileMenu.
Co-authored-by: NonInertialObserver <164627892+NonInertialObserver@users.noreply.github.com>
The rename merged without updating the suites that assert the old
names, leaving the pytest matrix red on master:
- test_plugin_manager: assert StarMetadata.views; the metadata.yaml
case now uses the views: key, plus a new test for the pages: alias
the rename kept for compatibility
- test_dashboard: the injected bridge snippet now reads
window.AstrBotPluginView?.__setInitialContext; the served bridge
keeps the AstrBotPluginPage alias, so that assertion stays
- regenerate docs/public/openapi.json (and the two openapi-scopes
tables) with docs/scripts/update_openapi_json.py so the public
spec matches the runtime spec again after the six /plugins/view(s)
alias routes
Fixes#10318
* feat(dashboard): edit config profiles from a side drawer in the bot editor
Add a cog button next to each config profile selector in the session
config section of the bot editor (default route config and per-session
binding). Clicking it slides out a right-side drawer with the full
profile editor (reusing AstrBotCoreConfigWrapper, same as the platform
creation dialog), so the selected profile can be viewed and modified
inline without leaving the page. Changes are saved through the existing
config profile API with success/error feedback.
* feat(dashboard): use an arrow-up-right icon for the profile drawer buttons
* feat(dashboard): drop the square background from the profile drawer buttons
* feat(dashboard): open the profile editor from each dropdown item
- Move the arrow-up-right button into each config profile dropdown item
(right side of every option); clicking it opens the editor for that
profile without selecting the option
- Restyle ConfigProfileDrawer to match the platform-creation config
drawer: v-overlay with slide-x-reverse transition, scrim, and a
right-aligned elevated card embedding the full ConfigPage via its
initial-config-id prop
* feat(dashboard): contain the config toolbar to the content column
The sticky config toolbar's background and divider previously spanned the
full viewport width; both now match the content column below. When the
config page is embedded in the side drawer, the toolbar sticks to the
drawer's top edge instead of the app-bar offset, removing the stray gap
above it.
* feat(dashboard): remove the divider under the config toolbar
The content-width divider read as a floating orphaned line; the sticky
toolbar's background and spacing already separate it from the content.
The loading progress bar is kept.
* fix(dashboard): cover the drawer padding strip above the embedded toolbar
Scrolled content bled through the 16px padding strip between the drawer
header and the sticky toolbar. In embedded mode the toolbar now sticks
flush to the drawer content top with its own solid background.
* feat(dashboard): drop the profile name subtitle from the config drawer header
* feat(dashboard): compact the config drawer header and enlarge its title
* feat(dashboard): shrink the config drawer header further
* refactor(dashboard): reuse ConfigProfileDrawer in the platform creation dialog
AddNewPlatform had its own duplicated config drawer implementation
(overlay, card, header, embedded ConfigPage). Replace it with the shared
ConfigProfileDrawer component so all config drawers share one style, and
remove the now-dead code, styles, and imports.
Also set the drawer title to 18px semibold (between the original h6 and
the too-large h3).
* feat(dashboard): show the profile id as a subtitle in the config management list
The floating test-chat button on the config page was hardcoded to
"secondary" since it was introduced, so it ignored the customized
primary color while the neighboring save and edit-JSON buttons followed
it.
* feat(dashboard): regroup the sidebar into labeled System and Extensions sections
- Add static group headers (System / Extensions) to the sidebar, in the
style of AI Studio's labeled sections
- System: welcome, platforms, providers, extensions, config profiles,
data & logs, custom rules
- Extensions: knowledge base, cron, subagent orchestration, personas;
plugin pages are flattened into this section and the collapsible
plugin-pages group is removed
- Dissolve the collapsible More Features group; headers hide in rail mode
- Skip the legacy sidebar customization when building the menu (the
feature is slated for removal)
- Update zh/en docs for the new locations
* feat(dashboard): place personas before knowledge base in the extensions group
* feat(dashboard): make the extensions sidebar group collapsible
Add a chevron toggle to the extensions group header; collapsing hides
the group's items and the state persists in localStorage. Rail mode is
unaffected and always shows every icon.
* feat(dashboard): pin extension sidebar items to the top of their group
Extension group items show a pin button on hover; pinned items move to
the top of the extensions group (hidden together with the group when
collapsed) and the pinned set is persisted in localStorage.
* feat(dashboard): keep the unpin icon always visible on pinned items
Pinned items now show a PinOff icon in the primary color without
requiring hover; unpinned items still reveal the Pin icon on hover.
* feat(dashboard): show a hover card with plugin details on plugin page items
Hovering a plugin-provided page in the extensions group reveals a card
with the page name, plugin ID, and author. The data comes from the
existing plugin list API (no backend change); static items and rail
mode are unaffected.
* fix(dashboard): add author and version to the PluginEntry type
* feat(dashboard): refine the plugin hover card
- Show the card immediately on hover (no open delay) and drop the
expand animation
- Plugin row shows an icon plus 'display_name (id)' with the id muted,
or just the id when the plugin has no display name
- Author row gets its own icon
* feat(dashboard): add a type row to the plugin hover card
Defaults to '插件可视化视图' (localized in all four locales).
* feat: rename plugin Pages to Views and group plugin views by plugin
Rename (Views preferred, Pages kept as a compatible alias everywhere):
- StarMetadata.pages -> views; metadata.yaml accepts views: (pages: alias)
- Plugin view directory views/ preferred, pages/ still accepted
- v1 API gains /plugins/view(s) alias routes alongside /plugins/page(s)
- Bridge global window.AstrBotPluginView with AstrBotPluginPage as alias
- Plugin i18n keys views.<name>.* preferred, pages.<name>.* alias
- Frontend route /plugin-view/... with /plugin-page/... redirecting
- OpenAPI spec + regenerated client; docs use Views (zh: 插件可视化视图)
Sidebar (extensions group):
- Default mode shows the view name for plugin views
- New group-by-plugin toggle on the extensions header (grid icon, off by
default, persisted in localStorage): plugin views are grouped per plugin
under the extensions group, each group listing all of the plugin's views
- Hover card on plugin view items shows page name, plugin display name
(with id), author, and type; white card style reused for the toggle
tooltip
- Pinning works for nested views too, lifting them to the group top
* feat(star): add umo and template_name params to Star.text_to_image
Plugins can now pass umo (unified_message_origin) so the t2i template is
resolved from the configuration file bound to that session, falling back
to the default configuration when unbound or omitted. An explicit
template_name always takes precedence over configuration.
* feat(config): move text-to-image settings into the config profile extension group
Consolidate all t2i settings under Config Profile -> Extension Features
(ext_group) so they are isolated per configuration file:
- Move the t2i object (output toggle, word threshold) from
platform_group to ext_group
- Move t2i_strategy, t2i_endpoint, t2i_template and t2i_active_template
from the system-wide settings group (Settings -> Appearance) into the
same ext_group t2i object
- Remove the Text-to-Image card from the Settings appearance section
- Relocate the matching i18n entries in all four locales and update the
zh/en docs with the new location
* fix(dashboard): hide trailing divider after invisible config items
getVisibleItemEntries did not filter invisible items, so an invisible
last item (e.g. t2i_active_template) left a dangling divider below the
last visible row.
* feat(dashboard): rename the sidebar Settings entry to System Settings
The Settings page now only holds global/system-wide options after
behavior configs (such as text-to-image) moved into config profiles.
Rename the sidebar entry in all four locales and update the zh/en docs
to reference the new name.
* feat(config): hide dependent t2i options when text-to-image output is off
Add a t2i=true condition to the word threshold, strategy, endpoint and
custom template items so they only appear when the output toggle is on.
The endpoint/template items keep their t2i_strategy=remote condition.
* fix(star): fall back to the default template when get_config returns None
text_to_image called .get() directly on the result of
context.get_config(umo), which can be None for context implementations
without a default configuration. Guard the lookup so the renderer
receives None instead, restoring the behavior covered by
test_text_to_image_without_config.
* feat(star): hold html_renderer on Context and resolve t2i endpoint per config
- Context now exposes the shared html_renderer, injected by the core
lifecycle, so plugins can call self.context.html_renderer directly
- Star.text_to_image/html_render resolve the t2i template and render
endpoint from the configuration file bound to the given umo and pass
them through per call (no renderer cache, no restart needed)
- The render chain (HtmlRenderer/NetworkRenderStrategy) accepts an
optional per-call endpoint
- The pipeline's automatic t2i conversion passes the endpoint from its
own per-profile configuration
* refactor(init): remove unnecessary comments and add deprecation notice for HtmlRenderer usage
- Move the bot version from the brand header (where long pre-release
versions wrapped mid-string) to below the Settings button, left-aligned
with the footer icon
- Align the Settings gear with the nav icons above: the v-btn grid gap
pushed it 12px right; zero the grid gap and space the label inside
v-btn__content (10px, matching nav items)
- Replace nav icons: providers Sparkles -> Box, config Settings ->
SlidersHorizontal, knowledge base BookOpen -> BookSearch
The DeepWiki badge served from deepwiki.com/badge.svg is an application route hosted on Vercel. GitHub's camo image proxy is rejected by that host with HTTP 429 (text/html) for the github-camo user agent, so the badge renders as a broken image in the README.
Reproduce the original badge with a shields.io static badge: black icon segment (labelColor=000000), #007ec6 'Ask DeepWiki' label, and the same embedded PNG logo. shields.io serves static badges from Cloudflare with a long Cache-Control, so camo can fetch and cache it reliably.
Affected files: README.md, README_zh.md, README_zh-TW.md, README_ja.md, README_fr.md, README_ru.md, README_es.md.