Fix/frontend optimizations (#2088)

* fix(web): auto-redirect to wizard on first visit and change sidebar icons to blue

* refactor(wizard): use backend metadata table instead of localStorage for wizard completion state

- Add wizard_completed field to system info API (read from metadata table)
- Add POST /api/v1/system/wizard/completed endpoint to mark wizard done
- Frontend home layout checks systemInfo.wizard_completed for auto-redirect
- Wizard calls markWizardCompleted API on skip/finish
- Ensures consistent behavior across all browsers on the same instance

* fix(wizard): update systemInfo in memory before navigation to prevent redirect loop

* fix(monitoring): prevent horizontal overflow and unify empty state styles

* fix(wizard): use Object.assign for systemInfo and await wizard completion API

- Replace systemInfo reassignment with Object.assign in all 3 locations
  to preserve object identity across module imports
- Await markWizardCompleted() POST in wizard skip/finish handlers
  instead of fire-and-forget to ensure backend persistence
- Always re-fetch systemInfo in home layout to get latest
  wizard_completed state from backend

* fix(wizard): prevent redirect loop by blocking navigation on failed status save

- Refactor wizard_completed (boolean) to wizard_status (string: none/skipped/completed)
- Remove ALL localStorage usage from wizard page (form state persistence)
- Replace AlertDialogAction with Button so skip dialog stays open during POST
- Add loading spinners for skip and complete actions
- If POST fails, show error toast and keep dialog/button active for retry
- If POST succeeds, update in-memory state and navigate

* fix(wizard): fix row[0].value bug causing GET /info to always return wizard_status=none

conn.execute(select(Entity)) returns Row with raw column values, not ORM
entities. row[0] is the key column (a string), so row[0].value raises
AttributeError which was silently swallowed by except-pass, making the
GET endpoint always return wizard_status=none regardless of DB state.

* fix(wizard): replace AlertDialog with Dialog for skip confirmation to remove slide animation

* chore: optimize toast in wizard

* fix(wizard): set default token value for Telegram adapter and initialize adapter config in wizard

* feat(web): move webhook URL to dynamic form system, add market category filter, fix layout overflow

- Add 'webhook-url' dynamic form field type rendered as read-only input
  with copy button, defined in adapter YAML specs instead of hardcoded
  in BotForm. Supports show_if conditions for optional-webhook adapters.
- Remove hardcoded webhook display logic from BotForm.tsx, pass webhook
  URLs via systemContext to DynamicFormComponent.
- Fetch webhook URLs after bot creation in wizard and pass to Step 1.
- Support ?category= query param on /home/market page for filtering by
  component type (mirrors langbot-space behavior).
- Link 'install knowledge engine' hint to /home/market?category=KnowledgeEngine.
- Fix SidebarInset missing min-w-0 causing content overflow when sidebar
  is expanded.
- Add vertical divider between plugin detail config and readme panels.
- Fix infinite re-render loop in DynamicFormComponent by memoizing
  editableItems array.

* fix: lint

* fix(web): change systemInfo to const to satisfy prefer-const lint rule

* fix: update adapter descriptions for clarity and usage requirements
This commit is contained in:
Junyan Chin
2026-03-28 15:50:32 +08:00
committed by GitHub
parent 498d030da9
commit 4c904c2375
36 changed files with 543 additions and 344 deletions

View File

@@ -1,7 +1,9 @@
import quart
import sqlalchemy
from .. import group
from .....utils import constants
from .....entity.persistence.metadata import Metadata
@group.group_class('system', '/api/v1/system')
@@ -9,6 +11,19 @@ class SystemRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE)
async def _() -> str:
# Read wizard_status from metadata table
# Possible values: 'skipped', 'completed'; absent key means 'none'
wizard_status = 'none'
try:
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(Metadata).where(Metadata.key == 'wizard_status')
)
row = result.first()
if row:
wizard_status = row.value
except Exception:
pass
return self.success(
data={
'version': constants.semantic_version,
@@ -27,9 +42,38 @@ class SystemRouterGroup(group.RouterGroup):
'disable_models_service', False
),
'limitation': self.ap.instance_config.data.get('system', {}).get('limitation', {}),
'wizard_status': wizard_status,
}
)
@self.route('/wizard/completed', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
"""Mark wizard status in metadata table.
Accepts JSON body: { "status": "skipped" | "completed" }
"""
data = await quart.request.get_json(silent=True) or {}
status = data.get('status', 'completed')
if status not in ('skipped', 'completed'):
return self.http_status(400, 400, f'Invalid wizard status: {status}')
try:
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(Metadata).where(Metadata.key == 'wizard_status')
)
if result.first():
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(Metadata).where(Metadata.key == 'wizard_status').values(value=status)
)
else:
await self.ap.persistence_mgr.execute_async(
sqlalchemy.insert(Metadata).values(key='wizard_status', value=status)
)
except Exception as e:
return self.http_status(500, 500, f'Failed to update wizard status: {e}')
return self.success(data={})
@self.route('/tasks', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
task_type = quart.request.args.get('type')

View File

@@ -6,8 +6,8 @@ metadata:
en_US: OneBot v11
zh_Hans: OneBot v11
description:
en_US: OneBot v11 Adapter
zh_Hans: OneBot v11 适配器,请查看文档了解使用方式
en_US: OneBot v11 Adapter, used for QQ bots
zh_Hans: OneBot v11 适配器,用于接入 QQ 机器人协议端,请查看文档了解使用方式
icon: onebot.png
spec:
config:

View File

@@ -7,7 +7,8 @@ metadata:
zh_Hans: Discord
description:
en_US: Discord Adapter
zh_Hans: Discord 适配器,请查看文档了解使用方式
zh_Hans: Discord 适配器,需要可连接 Discord 服务器的网络环境
ja_JP: Discord アダプター
icon: discord.svg
spec:
config:

View File

@@ -6,8 +6,9 @@ metadata:
en_US: Lark
zh_Hans: 飞书
description:
en_US: Lark Adapter
zh_Hans: 飞书适配器,请查看文档了解使用方式
en_US: Lark Adapter, supports both long connection and Webhook modes. Please refer to the documentation for usage details.
zh_Hans: 飞书适配器,支持长连接和 Webhook 两种接入方式,请查看文档了解使用方式
ja_JP: Lark アダプター、長期接続およびWebhookモードの両方をサポートしています。使用方法の詳細については、ドキュメントを参照してください。
icon: lark.svg
spec:
config:
@@ -45,6 +46,20 @@ spec:
type: boolean
required: true
default: false
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your Lark app's webhook configuration
zh_Hans: 复制此地址并粘贴到飞书应用的 Webhook 配置中
type: webhook-url
required: false
default: ""
show_if:
field: enable-webhook
operator: eq
value: true
- name: encrypt-key
label:
en_US: Encrypt Key
@@ -55,6 +70,10 @@ spec:
type: string
required: true
default: ""
show_if:
field: enable-webhook
operator: eq
value: true
- name: enable-stream-reply
label:
en_US: Enable Stream Reply Mode

View File

@@ -6,13 +6,27 @@ metadata:
en_US: LINE
zh_Hans: LINE
description:
en_US: LINE Adapter
zh_Hans: LINE适配器请查看文档了解使用方式
ja_JP: LINEアダプター、ドキュメントを参照してください
zh_Hant: LINE適配器查看文了解使用方式
en_US: LINE Adapter, requires a public URL to receive LINE message pushes, please refer to the documentation for usage details
zh_Hans: LINE适配器需要公网地址以接收 LINE 消息推送,请查看文档了解使用方式
ja_JP: LINEアダプター、LINEのメッセージプッシュを受信するためにパブリックURLが必要です。使用方法の詳細については、ドキュメントを参照してください
zh_Hant: LINE適配器需要公网地址以接收 LINE 消息推送,请查看文了解使用方式
icon: line.png
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
ja_JP: Webhook コールバック URL
zh_Hant: Webhook 回調地址
description:
en_US: Copy this URL and paste it into your LINE channel's webhook configuration
zh_Hans: 复制此地址并粘贴到 LINE 频道的 Webhook 配置中
ja_JP: この URL をコピーして LINE チャンネルの Webhook 設定に貼り付けてください
zh_Hant: 複製此地址並粘貼到 LINE 頻道的 Webhook 配置中
type: webhook-url
required: false
default: ""
- name: channel_access_token
label:
en_US: Channel access token

View File

@@ -7,10 +7,20 @@ metadata:
zh_Hans: 微信公众号
description:
en_US: Official Account Adapter
zh_Hans: 微信公众号适配器,请查看文档了解使用方式
zh_Hans: 微信公众号适配器,需要公网地址以接收消息推送,请查看文档了解使用方式
icon: officialaccount.png
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your Official Account webhook configuration
zh_Hans: 复制此地址并粘贴到微信公众号的 Webhook 配置中
type: webhook-url
required: false
default: ""
- name: token
label:
en_US: Token

View File

@@ -4,10 +4,10 @@ metadata:
name: openclaw-weixin
label:
en_US: OpenClaw WeChat
zh_Hans: OpenClaw 微信
zh_Hans: 个人微信机器人
description:
en_US: OpenClaw WeChat adapter, supports personal WeChat via QR code login
zh_Hans: OpenClaw 微信适配器,通过扫码登录支持个人微信
zh_Hans: 微信官方个人助手,扫码即可登录使用
icon: wechat.png
spec:
config:
@@ -27,7 +27,7 @@ spec:
zh_Hans: 令牌
description:
en_US: Bearer token obtained after QR code login authorization. Leave empty to trigger QR code login on startup.
zh_Hans: 扫码登录授权后获取的 Bearer 令牌。留空则启动时自动触发扫码登录。
zh_Hans: 扫码登录授权后获取的 Bearer 令牌。留空并保存,将在启动时输出二维码到日志,扫码后即可自动登录。
type: string
required: false
default: ""

View File

@@ -7,10 +7,20 @@ metadata:
zh_Hans: QQ 官方 API
description:
en_US: QQ Official API (Webhook)
zh_Hans: QQ 官方 API (Webhook),请查看文档了解使用方式
zh_Hans: QQ 官方 API (Webhook)需要公网地址以接收消息推送,请查看文档了解使用方式
icon: qqofficial.svg
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your QQ Official API webhook configuration
zh_Hans: 复制此地址并粘贴到 QQ 官方 API 的 Webhook 配置中
type: webhook-url
required: false
default: ""
- name: appid
label:
en_US: App ID

View File

@@ -7,7 +7,7 @@ metadata:
zh_Hans: Satori
description:
en_US: SatoriAdapter
zh_Hans: 古明地觉协议适配器
zh_Hans: Satori 协议适配器,支持多种平台的接入,请查看文档了解使用方式
icon: satori.png
spec:
config:

View File

@@ -7,10 +7,22 @@ metadata:
zh_Hans: Slack
description:
en_US: Slack Adapter
zh_Hans: Slack 适配器,请查看文档了解使用方式
zh_Hans: Slack 适配器,需要公网地址以接收 Slack 消息推送,请查看文档了解使用方式
ja_JP: Slack アダプター、Slackのメッセージプッシュを受信するためにパブリックURLが必要です。使用方法の詳細については、ドキュメントを参照してください。
zh_Hant: Slack 适配器,需要公网地址以接收 Slack 消息推送,请查看文档了解使用方式
icon: slack.png
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your Slack app's event subscription configuration
zh_Hans: 复制此地址并粘贴到 Slack 应用的事件订阅配置中
type: webhook-url
required: false
default: ""
- name: bot_token
label:
en_US: Bot Token

View File

@@ -7,7 +7,7 @@ metadata:
zh_Hans: 电报
description:
en_US: Telegram Adapter
zh_Hans: 电报适配器,请查看文档了解使用方式
zh_Hans: Telegram 适配器,请查看文档了解使用方式
icon: telegram.svg
spec:
config:
@@ -17,7 +17,7 @@ spec:
zh_Hans: 令牌
type: string
required: true
default: ""
default: "token_from_botfather"
- name: markdown_card
label:
en_US: Markdown Card

View File

@@ -4,10 +4,10 @@ metadata:
name: wechatpad
label:
en_US: WeChatPad
zh_CN: WeChatPad个人微信ipad
zh_Hans: WeChatPad个人微信ipad
description:
en_US: WeChatPad Adapter
zh_CN: WeChatPad 适配器
zh_Hans: WeChatPad 适配器基于WeChatPad的个人微信解决方案请查看文档了解使用方式
icon: wechatpad.png
spec:
config:

View File

@@ -7,10 +7,20 @@ metadata:
zh_Hans: 企业微信
description:
en_US: WeCom Adapter
zh_Hans: 企业微信适配器,请查看文档了解使用方式
zh_Hans: 企业微信内部机器人,请查看文档了解使用方式
icon: wecom.png
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your WeCom app's webhook configuration
zh_Hans: 复制此地址并粘贴到企业微信应用的 Webhook 配置中
type: webhook-url
required: false
default: ""
- name: corpid
label:
en_US: Corpid

View File

@@ -7,7 +7,7 @@ metadata:
zh_Hans: 企业微信智能机器人
description:
en_US: WeComBot Adapter
zh_Hans: 企业微信智能机器人适配器,请查看文档了解使用方式
zh_Hans: 企业微信智能机器人,支持长连接和 Webhook 两种接入方式,请查看文档了解使用方式
icon: wecombot.png
spec:
config:
@@ -35,6 +35,20 @@ spec:
type: boolean
required: true
default: false
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your WeComBot webhook configuration
zh_Hans: 复制此地址并粘贴到企业微信智能机器人的 Webhook 配置中
type: webhook-url
required: false
default: ""
show_if:
field: enable-webhook
operator: eq
value: true
- name: Secret
label:
en_US: Secret

View File

@@ -7,10 +7,20 @@ metadata:
zh_Hans: 企业微信客服
description:
en_US: WeComCSAdapter
zh_Hans: 企业微信客服适配器
zh_Hans: 企业微信对外客服机器人,需要公网地址以接收消息推送,请查看文档了解使用方式
icon: wecom.png
spec:
config:
- name: webhook_url
label:
en_US: Webhook Callback URL
zh_Hans: Webhook 回调地址
description:
en_US: Copy this URL and paste it into your WeCom Customer Service webhook configuration
zh_Hans: 复制此地址并粘贴到企业微信客服的 Webhook 配置中
type: webhook-url
required: false
default: ""
- name: corpid
label:
en_US: Corpid