Skip to content

feat: add Casbin admin permissions#5755

Merged
Calcium-Ion merged 6 commits into
mainfrom
feat/better-admin
Jun 27, 2026
Merged

feat: add Casbin admin permissions#5755
Calcium-Ion merged 6 commits into
mainfrom
feat/better-admin

Conversation

@Calcium-Ion

@Calcium-Ion Calcium-Ion commented Jun 26, 2026

Copy link
Copy Markdown
Member

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

新增基于 Casbin 的后台 Admin 细分权限能力,首期接入渠道管理权限。

  • 新增 casbin_rule policy 存储和 authz_roles 角色元数据初始化,启动时写入内置 Root/Admin 权限基线。
  • 新增通用 authz.Permission catalog、权限判断、能力矩阵和用户级 allow/deny override。
  • 渠道路由接入 RequirePermission,并对渠道 key、base URL、Header/Param Override、OpenAI Organization 等敏感字段执行字段级权限校验。
  • 用户 API 返回并保存通用 Admin 权限矩阵,Root 可对单个 Admin 增加或取消权限。
  • 默认前端根据权限矩阵控制渠道新增、复制、敏感编辑、Fetch Models 等入口展示,并补齐多语言文案。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

已在本地运行并通过:

  • go test ./...
  • bun run i18n:sync
  • bun run typecheck
  • bun run build

Summary by CodeRabbit

  • New Features

    • Added granular admin permissions for channel management, including sensitive-write controls.
    • Added permission-aware channel status updates (single and batch), plus an admin permission catalog endpoint.
  • Bug Fixes

    • Sensitive channel edits and destructive multi-key actions now require proper authorization.
    • User admin permissions stay consistent when roles/permissions are updated.
  • UI Updates

    • Channel, multi-key, and bulk actions are now disabled when permission is missing, with clearer “no permission” messaging.
    • Eligible admins can view and edit per-user admin permissions.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds a Casbin-backed authorization system, a permission-catalog endpoint, per-user admin permission persistence, channel sensitive-write checks and status routes, and frontend editors/UI gating for channel and user permissions. Locale files add matching permission and channel management text.

Changes

Authorization and permission gating

Layer / File(s) Summary
Permission vocabulary
service/authz/permission.go, service/authz/registry.go, service/authz/role.go, service/authz/resources_channel.go, service/authz/assignment.go
Defines shared permission primitives, the registry, built-in roles, and channel resource metadata.
Authz engine and bootstrap
go.mod, model/authz_role.go, model/casbin_rule.go, model/main.go, service/authz/adapter.go, service/authz/enforcer.go, service/authz/seed.go, service/authz/override.go, service/authz/resolver.go, service/authz/authz_test.go, controller/authz.go, router/authz-router.go, main.go
Adds Casbin-backed persistence, policy seeding, authz initialization, the permission catalog endpoint, and its router wiring.
User authz state
model/user.go, controller/user.go, controller/audit.go, model/log.go
Adds admin permission data to user responses and updates transactional user mutation and audit flows.
Channel authz and routing
middleware/auth.go, controller/channel.go, controller/channel_authz.go, controller/channel_authz_test.go, router/api-router.go, router/channel-router.go, router/channel_router_test.go
Adds sensitive-write checks for channel edits and multi-key actions, channel status handlers, and permission-aware channel routing tests.
Channel UI gating
web/default/src/features/channels/..., web/default/src/i18n/locales/*.json
Updates channel APIs, status/delete flows, mutation form filtering, and channel permission copy across the locale files.
Admin permission editing
web/default/src/lib/admin-permissions.ts, web/default/src/stores/auth-store.ts, web/default/src/features/users/..., web/default/src/i18n/locales/*.json
Adds the permission catalog model, user permission matrix editing, and admin-permission copy across the locale files.

Estimated review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels
enhancement

Suggested reviewers

  • seefs001

A bunny hopped through perms all day,
With Casbin gates to mark the way.
🐇 Keys, channels, and roles now sing,
While admin carrots do their thing.
Hop hop — the audit logs stay neat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR’s main change: adding Casbin-based admin permissions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/better-admin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/channel.go`:
- Around line 1081-1083: The key-change sensitivity check in the channel update
logic is too permissive because it only flags changes when channel.Key is
non-empty, allowing explicit clearing via key being empty or null to slip
through. Update the comparison in the channel update/sensitivity path so that
whenever requestData contains key, it is treated as sensitive if the submitted
value differs from origin.Key, including transitions to empty or nil. Use the
existing channel.Key, origin.Key, and requestData["key"] check to locate and
adjust the condition.

In `@controller/user.go`:
- Around line 646-649: The user update and authorization update are not atomic,
so a failure in updateAdminPermissionsForUser or authz.ClearUserAuthorization
can leave the User record changed while casbin_rule is stale. Refactor the
affected handlers around updatedUser, updateAdminPermissionsForUser, and
authz.ClearUserAuthorization to perform the user write and authz write in one
transactional flow with rollback/compensation on failure, and make sure the
demote path still invalidates cache only after both steps succeed. Apply the
same atomicity fix in the other update paths noted by the review as well.

In `@model/casbin_rule.go`:
- Around line 5-11: The Casbin rule schema in the model struct needs a
uniqueness constraint on the policy columns so concurrent AddPolicy calls cannot
create duplicate rows. Update the gorm tags on CasbinRule fields (Ptype, V0
through V5) to define a unique composite index over the full policy key, and
keep the existing adapter logic in service/authz/adapter.go aligned with that
database-level enforcement.

In `@model/main.go`:
- Around line 354-355: The concurrent migration path in migrateDBFast is now
including CasbinRule and AuthzRole, which can trigger SQLite locking during
startup. Keep these authz tables out of the parallel AutoMigrate set and ensure
SQLite uses the serial migration path instead, while still running the authz
setup afterward. Update the migration selection logic around migrateDBFast and
the authz initialization flow so all supported dialects remain compatible.

In `@router/channel-router.go`:
- Around line 23-29: The channel key endpoint is still wired to RootAuth()
instead of the new channel secret-view authorization. Update the route in
channelRoute.POST("/:id/key") to use the ChannelSecretView permission check from
the channel permission matrix, keeping the existing
rate-limit/cache/verification middleware intact, and ensure
controller.GetChannelKey is only reached when secret-view access is granted.

In `@service/authz/authz_test.go`:
- Around line 16-18: The in-memory SQLite test fixture created in authz_test.go
can become flaky because gorm.Open with sqlite.Open(":memory:") may use multiple
connections. Update the test setup around gorm.Open and AutoMigrate so the
underlying DB is restricted to a single connection for the lifetime of the
fixture, using the existing db handle returned in the test. This should be
applied where the test initializes the database before running Authz-related
assertions.

In `@service/authz/authz.go`:
- Around line 315-331: The built-in role seeding logic in the role upsert path
is doing a read-then-create/update flow with a raw Where on the key column,
which is not dialect-safe and can race during concurrent startup. Replace this
block with a single upsert in the seeding routine that targets the role key
uniquely and updates the existing fields atomically, using the shared
reserved-word-safe column constant instead of a raw key literal. Keep the change
within the authz seeding code around the AuthzRole handling so it works across
SQLite, MySQL, and PostgreSQL without aborting when another instance seeds the
same role first.

In `@web/default/src/lib/admin-permissions.ts`:
- Around line 11-17: The `secret_view` admin permission is being dropped during
normalization because `normalizeAdminPermissions` rebuilds permissions from
`ADMIN_PERMISSION_CATALOG` but `ADMIN_PERMISSION_ACTIONS.SECRET_VIEW` is not
included there. Update the `ADMIN_PERMISSION_CATALOG` entry in
`admin-permissions.ts` so the `resource.actions` array includes `SECRET_VIEW`,
ensuring `users-mutate-drawer.tsx` and `user-form.ts` preserve it when calling
`normalizeAdminPermissions` and `authz.SetUserPermissions`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 584c6dd7-8a3a-428c-a7df-312a2628fad2

📥 Commits

Reviewing files that changed from the base of the PR and between 7939674 and a23080c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (28)
  • controller/channel.go
  • controller/channel_authz_test.go
  • controller/user.go
  • go.mod
  • main.go
  • middleware/auth.go
  • model/authz_role.go
  • model/casbin_rule.go
  • model/main.go
  • model/user.go
  • router/api-router.go
  • router/channel-router.go
  • service/authz/adapter.go
  • service/authz/authz.go
  • service/authz/authz_test.go
  • web/default/src/features/channels/components/channels-primary-buttons.tsx
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/users/components/users-mutate-drawer.tsx
  • web/default/src/features/users/lib/user-form.ts
  • web/default/src/features/users/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/lib/admin-permissions.ts
  • web/default/src/stores/auth-store.ts

Comment thread controller/channel.go Outdated
Comment thread controller/user.go Outdated
Comment thread model/casbin_rule.go Outdated
Comment thread model/main.go Outdated
Comment thread router/channel-router.go
Comment thread service/authz/authz_test.go
Comment thread service/authz/authz.go Outdated
Comment thread web/default/src/lib/admin-permissions.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
web/default/src/i18n/locales/ja.json (1)

3640-3640: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor terminology inconsistency: "チャンネル" vs "チャネル".

These two new strings use "チャンネル", while the rest of the channel-related strings in this file (e.g. Line 713 "チャネル管理", Line 3894 "機密チャネル設定") use "チャネル". Align for consistency.

Also applies to: 4732-4732

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/ja.json` at line 3640, The Japanese locale has
inconsistent terminology for channel-related strings by using “チャンネル” instead of
the established “チャネル” used elsewhere in this file. Update the affected
translation entries, including the string in the reserved-view message and the
other noted entry, so they consistently use “チャネル” to match the existing locale
wording.

Source: Learnings

web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx (1)

324-330: 🔒 Security & Privacy | 🔵 Trivial

Reveal-key gating uses role, not the SECRET_VIEW permission.

The canRevealChannelKey check relies on a hard-coded SUPER_ADMIN role requirement, bypassing the SECRET_VIEW permission explicitly defined for "Viewing complete channel secrets." This prevents administrators with the specific SECRET_VIEW grant from revealing keys, creating an inconsistency with the permission model.

Replace the role check with hasPermission to respect the defined permission grant:

Current vs. Proposed Change
  const canEditSensitive = hasPermission(
    currentUser,
    ADMIN_PERMISSION_RESOURCES.CHANNEL,
    ADMIN_PERMISSION_ACTIONS.SENSITIVE_WRITE
  )
- const canRevealChannelKey = currentUser?.role === ROLE.SUPER_ADMIN
+ const canRevealChannelKey = hasPermission(
+   currentUser,
+   ADMIN_PERMISSION_RESOURCES.CHANNEL,
+   ADMIN_PERMISSION_ACTIONS.SECRET_VIEW
+ )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx`
around lines 324 - 330, The reveal-key access check is using a hard-coded role
instead of the existing permission model. Update the canRevealChannelKey logic
in channel-mutate-drawer.tsx to use hasPermission with
ADMIN_PERMISSION_RESOURCES.CHANNEL and ADMIN_PERMISSION_ACTIONS.SECRET_VIEW,
matching the pattern already used by canEditSensitive, and remove the direct
SUPER_ADMIN role comparison.
web/default/src/features/channels/hooks/use-channel-mutate-form.ts (1)

44-54: 🗄️ Data Integrity & Integration | 🔵 Trivial

SENSITIVE_UPDATE_FIELDS diverges from the UI's SENSITIVE_FORM_FIELDS, though backend enforcement is narrow.

Verification confirms the backend (controller/channel.go) only enforces ChannelSensitiveWrite permission for changes to type, base_url, key, openai_organization, setting, param_override, and header_override. Fields like proxy, system_prompt, and force_format (marked sensitive in the drawer) are not checked by the backend, so their presence in the payload does not currently cause save rejections for locked users.

However, the frontend maintains two separate lists: the hook's strip list matches the backend's strict definition, while the drawer treats ~33 fields as sensitive. This drift creates ambiguity: the UI may prevent edits or flag changes for fields the backend considers safe, and future backend updates syncing with the UI's broader list could cause unexpected rejections if the strip list isn't updated.

Recommendation:

  • Centralize the definition of sensitive fields. Ensure SENSITIVE_FORM_FIELDS in the drawer and SENSITIVE_UPDATE_FIELDS in the hook derive from a single source of truth to prevent drift.
  • If the UI intentionally treats fields like proxy as sensitive (for behavioral locking), explicitly include them in SENSITIVE_UPDATE_FIELDS to strip them for locked users, ensuring the UI protection logic matches the data sent to the API.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/channels/hooks/use-channel-mutate-form.ts` around
lines 44 - 54, The sensitive-field rules are split between the channel mutate
hook and the drawer, causing drift between what the UI blocks and what gets sent
to the API. Update the `SENSITIVE_UPDATE_FIELDS` in `use-channel-mutate-form` to
derive from the same source as `SENSITIVE_FORM_FIELDS` (or otherwise share a
single constants list) so both the form UI and payload-stripping logic stay
aligned. If fields like `proxy`, `system_prompt`, or `force_format` are meant to
be locked, ensure they are included in the shared definition and therefore
handled consistently by `use-channel-mutate-form` and the drawer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/authz/authz.go`:
- Around line 273-280: The authz enforcer access is racy because ReloadPolicy
can call LoadPolicy while Can/GetFilteredPolicy run outside the lock, causing
concurrent map access. Fix this by making the enforcer thread-safe at creation
time with casbin.NewSyncedEnforcer, or by holding enforcerMu.RLock() for the
full duration of enforcement in Can and any policy reads. Keep the change
centered around currentEnforcer, Can, and ReloadPolicy so all enforcer
operations are protected consistently.

---

Nitpick comments:
In
`@web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Around line 324-330: The reveal-key access check is using a hard-coded role
instead of the existing permission model. Update the canRevealChannelKey logic
in channel-mutate-drawer.tsx to use hasPermission with
ADMIN_PERMISSION_RESOURCES.CHANNEL and ADMIN_PERMISSION_ACTIONS.SECRET_VIEW,
matching the pattern already used by canEditSensitive, and remove the direct
SUPER_ADMIN role comparison.

In `@web/default/src/features/channels/hooks/use-channel-mutate-form.ts`:
- Around line 44-54: The sensitive-field rules are split between the channel
mutate hook and the drawer, causing drift between what the UI blocks and what
gets sent to the API. Update the `SENSITIVE_UPDATE_FIELDS` in
`use-channel-mutate-form` to derive from the same source as
`SENSITIVE_FORM_FIELDS` (or otherwise share a single constants list) so both the
form UI and payload-stripping logic stay aligned. If fields like `proxy`,
`system_prompt`, or `force_format` are meant to be locked, ensure they are
included in the shared definition and therefore handled consistently by
`use-channel-mutate-form` and the drawer.

In `@web/default/src/i18n/locales/ja.json`:
- Line 3640: The Japanese locale has inconsistent terminology for
channel-related strings by using “チャンネル” instead of the established “チャネル” used
elsewhere in this file. Update the affected translation entries, including the
string in the reserved-view message and the other noted entry, so they
consistently use “チャネル” to match the existing locale wording.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c76bc3c-f887-4ef0-9ba5-af41bbc9daf4

📥 Commits

Reviewing files that changed from the base of the PR and between 2f5565e and a6de6c7.

📒 Files selected for processing (21)
  • controller/user.go
  • model/casbin_rule.go
  • model/main.go
  • model/user.go
  • router/channel-router.go
  • service/authz/adapter.go
  • service/authz/authz.go
  • service/authz/authz_test.go
  • web/default/src/features/channels/components/channels-primary-buttons.tsx
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/channels/components/dialogs/multi-key-manage-dialog.tsx
  • web/default/src/features/channels/components/dialogs/multi-key-table-row-actions.tsx
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/hooks/use-channel-mutate-form.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/lib/admin-permissions.ts
💤 Files with no reviewable changes (1)
  • model/main.go
✅ Files skipped from review due to trivial changes (3)
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • model/casbin_rule.go
  • router/channel-router.go
  • web/default/src/features/channels/components/channels-primary-buttons.tsx
  • web/default/src/lib/admin-permissions.ts
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • service/authz/adapter.go

Comment thread service/authz/authz.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/default/src/features/channels/components/data-table-bulk-actions.tsx (1)

180-199: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disabled delete trigger hides the permission explanation.

Line 184 disables the button, so the tooltip text added at Lines 195-199 is unlikely to be reachable by mouse or keyboard users. That makes the new “No permission to perform this action” feedback effectively invisible. Keep the trigger focusable (for example, wrap the disabled control) or surface the reason inline. As per coding guidelines, use keyboard-accessible controls and appropriate ARIA/interaction behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/channels/components/data-table-bulk-actions.tsx`
around lines 180 - 199, The delete action in data-table-bulk-actions is fully
disabled when canEditSensitive is false, which prevents the TooltipTrigger
content from being reached and hides the permission message. Update the delete
control so it remains keyboard/mouse accessible while conveying the disabled
state, such as by using a focusable wrapper around the disabled button or by
surfacing the “No permission to perform this action” text inline. Make sure the
interaction in the delete trigger and TooltipContent path still uses accessible
behavior and ARIA semantics for users without edit permission.

Source: Coding guidelines

🧹 Nitpick comments (1)
service/authz/registry.go (1)

27-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deep-copy DefaultRoles in the catalog copy.

Catalog() copies the actions slice, but each ActionDefinition.DefaultRoles slice still shares backing storage with the registry. A caller mutating the returned catalog can accidentally change role defaults used by the authz package.

♻️ Proposed fix
 func Catalog() []ResourceDefinition {
 	result := make([]ResourceDefinition, 0, len(registry))
 	for _, resource := range registry {
+		actions := make([]ActionDefinition, 0, len(resource.Actions))
+		for _, action := range resource.Actions {
+			action.DefaultRoles = append([]string(nil), action.DefaultRoles...)
+			actions = append(actions, action)
+		}
 		result = append(result, ResourceDefinition{
 			Resource: resource.Resource,
 			LabelKey: resource.LabelKey,
-			Actions:  append([]ActionDefinition(nil), resource.Actions...),
+			Actions:  actions,
 		})
 	}
 	return result
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/authz/registry.go` around lines 27 - 34, Catalog() only deep-copies
Actions, but ActionDefinition.DefaultRoles is still shared with the registry and
can be mutated by callers. Update the Catalog function to also copy each
action’s DefaultRoles slice when building the returned ResourceDefinition list,
using the ActionDefinition and ResourceDefinition symbols to locate the copy
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/channel_authz.go`:
- Around line 90-97: The non-sensitive edit classification in UpdateChannel is
too permissive because server-managed/accounting fields like balance,
used_quota, and the timestamp/health fields can still slip through when they
appear in the request body. Update the sensitive-write handling in
channel_authz.go so that these fields are rejected outright in UpdateChannel, or
ensure they are treated as sensitive before the classified-field skip list is
applied. Use the existing UpdateChannel flow and the field allow/deny logic
around the classified-field checks to locate the fix.

In `@controller/channel.go`:
- Around line 1093-1095: Update the request parsing in UpdateChannelStatus and
BatchUpdateChannelStatus to use the shared JSON wrapper instead of
c.ShouldBindJSON. Read the body with c.GetRawData() and deserialize via
common.Unmarshal or common.DecodeJson, then validate the resulting
ChannelStatusRequest as before. Keep the change localized to the request
handling around ChannelStatusRequest so both endpoints follow the common/json.go
guideline consistently.

In `@service/authz/enforcer.go`:
- Around line 14-16: The shared authz enforcer is currently a raw
casbin.Enforcer, so concurrent policy reads and writes in currentEnforcer,
GetFilteredPolicy, AddPolicy, RemoveFilteredPolicy, and LoadPolicy can race.
Update the shared instance in enforcer.go to use casbin.SyncedEnforcer, or
consistently guard every access with enforcerMu, and propagate the resulting
type/signature changes through the helper methods that return or accept the
enforcer.

In `@service/authz/resolver.go`:
- Around line 13-20: The permission check in the resolver currently allows
superuser roles to short-circuit before validation, so unknown permissions can
incorrectly pass. Update the logic in the permission guard that uses roles,
isSuperuserRole, and isKnownPermission so permission validity is checked first
and any unknown catalog entry returns false before considering superuser access.

In `@web/default/src/features/users/lib/user-form.ts`:
- Around line 83-91: The admin permission payload in transformFormDataToPayload
is still being set when the only available catalog is EMPTY_PERMISSION_CATALOG,
which causes an empty admin_permissions override to be serialized. Update the
guard around the admin_permissions assignment so it only runs when there is a
usable catalog (not the empty sentinel) and the target role is admin, using
ADMIN_ROLE_KEY from `@/lib/admin-permissions` to validate the baseline/resources
before calling normalizeAdminPermissions. This keeps UsersMutateDrawer from
persisting admin_permissions: {} during loading or failure states and preserves
existing permissions when the catalog is not ready.

---

Outside diff comments:
In `@web/default/src/features/channels/components/data-table-bulk-actions.tsx`:
- Around line 180-199: The delete action in data-table-bulk-actions is fully
disabled when canEditSensitive is false, which prevents the TooltipTrigger
content from being reached and hides the permission message. Update the delete
control so it remains keyboard/mouse accessible while conveying the disabled
state, such as by using a focusable wrapper around the disabled button or by
surfacing the “No permission to perform this action” text inline. Make sure the
interaction in the delete trigger and TooltipContent path still uses accessible
behavior and ARIA semantics for users without edit permission.

---

Nitpick comments:
In `@service/authz/registry.go`:
- Around line 27-34: Catalog() only deep-copies Actions, but
ActionDefinition.DefaultRoles is still shared with the registry and can be
mutated by callers. Update the Catalog function to also copy each action’s
DefaultRoles slice when building the returned ResourceDefinition list, using the
ActionDefinition and ResourceDefinition symbols to locate the copy logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d90e377-c8a0-4a91-b523-823d3d92db0d

📥 Commits

Reviewing files that changed from the base of the PR and between a6de6c7 and a23b7e4.

📒 Files selected for processing (36)
  • controller/authz.go
  • controller/channel.go
  • controller/channel_authz.go
  • controller/channel_authz_test.go
  • main.go
  • router/api-router.go
  • router/authz-router.go
  • router/channel-router.go
  • router/channel_router_test.go
  • service/authz/assignment.go
  • service/authz/authz_test.go
  • service/authz/enforcer.go
  • service/authz/override.go
  • service/authz/permission.go
  • service/authz/registry.go
  • service/authz/resolver.go
  • service/authz/resources_channel.go
  • service/authz/role.go
  • service/authz/seed.go
  • web/default/src/features/channels/api.ts
  • web/default/src/features/channels/components/channels-primary-buttons.tsx
  • web/default/src/features/channels/components/data-table-bulk-actions.tsx
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/default/src/features/channels/lib/channel-form.ts
  • web/default/src/features/users/api.ts
  • web/default/src/features/users/components/users-mutate-drawer.tsx
  • web/default/src/features/users/lib/user-form.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/lib/admin-permissions.ts
💤 Files with no reviewable changes (1)
  • web/default/src/features/channels/lib/channel-form.ts
✅ Files skipped from review due to trivial changes (2)
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/ja.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • router/api-router.go
  • router/channel-router.go
  • web/default/src/features/channels/components/channels-primary-buttons.tsx
  • web/default/src/features/users/components/users-mutate-drawer.tsx
  • service/authz/authz_test.go
  • web/default/src/i18n/locales/fr.json
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/ru.json

Comment thread controller/channel_authz.go Outdated
Comment thread controller/channel.go
Comment thread service/authz/enforcer.go Outdated
Comment thread service/authz/resolver.go
Comment on lines +13 to +20
for _, role := range roles {
if isSuperuserRole(role) {
return true
}
}
if !isKnownPermission(permission) {
return false
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed for unknown permissions before the superuser allow path.

A typo or unregistered permission currently returns true for root users because the superuser check runs before isKnownPermission. Validate the permission first so all callers fail closed on unknown catalog entries.

🛡️ Proposed fix
 	if len(roles) == 0 {
 		return false
 	}
+	if !isKnownPermission(permission) {
+		return false
+	}
 	for _, role := range roles {
 		if isSuperuserRole(role) {
 			return true
 		}
 	}
-	if !isKnownPermission(permission) {
-		return false
-	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, role := range roles {
if isSuperuserRole(role) {
return true
}
}
if !isKnownPermission(permission) {
return false
}
for _, role := range roles {
if isSuperuserRole(role) {
return true
}
}
if !isKnownPermission(permission) {
return false
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/authz/resolver.go` around lines 13 - 20, The permission check in the
resolver currently allows superuser roles to short-circuit before validation, so
unknown permissions can incorrectly pass. Update the logic in the permission
guard that uses roles, isSuperuserRole, and isKnownPermission so permission
validity is checked first and any unknown catalog entry returns false before
considering superuser access.

Comment thread web/default/src/features/users/lib/user-form.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
controller/channel_authz.go (1)

9-10: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Treat explicit key clearing as a sensitive change.

With channel.Key != "", a request containing "key": "" against a channel with an existing key falls through as non-sensitive because key is then skipped by channelSensitiveFields. Since channel keys are part of the sensitive-write surface, explicit clearing should still require ChannelSensitiveWrite.

Suggested fix
-	if _, ok := requestData["key"]; ok && channel.Key != "" && channel.Key != origin.Key {
+	if _, ok := requestData["key"]; ok && channel.Key != origin.Key {
 		return true
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/channel_authz.go` around lines 9 - 10, The sensitivity check in
`channelHasSensitiveWrite` currently skips explicit key clearing because it only
treats `requestData["key"]` as sensitive when `channel.Key` is non-empty and
different from `origin.Key`. Update this logic so any request that includes the
key field, including `"key": ""`, is treated as a sensitive write when the
channel currently has a key. Keep the fix localized to
`channelHasSensitiveWrite` and `channelSensitiveFields` so
`ChannelSensitiveWrite` is required for explicit key removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@controller/channel_authz.go`:
- Around line 9-10: The sensitivity check in `channelHasSensitiveWrite`
currently skips explicit key clearing because it only treats
`requestData["key"]` as sensitive when `channel.Key` is non-empty and different
from `origin.Key`. Update this logic so any request that includes the key field,
including `"key": ""`, is treated as a sensitive write when the channel
currently has a key. Keep the fix localized to `channelHasSensitiveWrite` and
`channelSensitiveFields` so `ChannelSensitiveWrite` is required for explicit key
removal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c38a8d5-0d8e-4498-b6c8-4aefd2988215

📥 Commits

Reviewing files that changed from the base of the PR and between a23b7e4 and 4689230.

📒 Files selected for processing (7)
  • controller/channel.go
  • controller/channel_authz.go
  • controller/channel_authz_test.go
  • service/authz/enforcer.go
  • service/authz/override.go
  • service/authz/resolver.go
  • web/default/src/features/channels/components/data-table-bulk-actions.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/default/src/features/channels/components/data-table-bulk-actions.tsx
  • service/authz/resolver.go
  • service/authz/enforcer.go
  • service/authz/override.go
  • controller/channel.go

@Calcium-Ion Calcium-Ion merged commit 4aee5f7 into main Jun 27, 2026
1 check passed
shudonglin added a commit to rayward-external/new-api that referenced this pull request Jun 27, 2026
* chore: avoid duplicate shadcn skill exposure

* fix: support SMTP STARTTLS mode and NTLM auth (QuantumNous#5426)

* fix: support SMTP STARTTLS mode and NTLM auth

Add explicit SMTP STARTTLS configuration for 587-style connections and keep SSL/TLS as the implicit TLS mode.

Prefer PLAIN when advertised, keep LOGIN compatibility, and add NTLM as a fallback for Exchange SMTP servers that require it after STARTTLS.

* fix: respect explicit SMTP encryption mode

* fix: preserve SMTP TLS compatibility

* fix: preserve SMTP PLAIN auth TLS guard

* chore(deps): bump github.com/ClickHouse/ch-go from 0.58.2 to 0.65.0 (QuantumNous#5664)

Bumps [github.com/ClickHouse/ch-go](https://github.com/ClickHouse/ch-go) from 0.58.2 to 0.65.0.
- [Release notes](https://github.com/ClickHouse/ch-go/releases)
- [Commits](ClickHouse/ch-go@v0.58.2...v0.65.0)

---
updated-dependencies:
- dependency-name: github.com/ClickHouse/ch-go
  dependency-version: 0.65.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: update agent skills and project config

- add vercel-react-best-practices skill (SKILL.md + full-guide.md)
- slim CLAUDE.md to import shared AGENTS.md conventions
- promote go-ntlmssp to a direct dependency in go.mod

* fix: date-fns-tz classic theme build error (QuantumNous#5676)

* chore(deps): update clickhouse-go and orb dependencies

* feat: add system task runner (QuantumNous#5680)

* feat: add system instance info panel (QuantumNous#5716)

* feat: add system instance reporting

* feat: show system instance resources

* fix: update translations for heartbeat messages in Russian and Vietnamese

* fix(web): replace default markdown renderer and expand syntax support (QuantumNous#5689)

* fix(markdown): render default markdown with marked

- switch default frontend markdown rendering from react-markdown/remark-gfm to marked to avoid old WebKit parse failures from lookbehind regex literals
- sanitize marked HTML output with DOMPurify and preserve external link target and rel behavior
- remove default direct dependencies on react-markdown, remark-gfm, and rehype-raw while leaving classic unchanged

* fix(markdown): expand default markdown rendering support

- render default markdown with marked extensions for KaTeX formulas, page breaks, and common emoji shortcodes.
- sanitize KaTeX output with an explicit DOMPurify allowlist while preserving external link behavior.
- avoid overriding marked text rendering so lists and inline parsing keep their internal parser context.

* fix(markdown): render diagram code blocks in default UI

- add sanitized SVG rendering for flow and sequence diagram code blocks.
- size flow nodes from their labels and route edges from node anchors to prevent clipping.
- style diagram nodes, arrows, labels, and notes with theme-aware classes.

* fix(web): sync channel card selection state (QuantumNous#5700)

* fix(web): hide wallet entry in profile dropdown when wallet module disabled (QuantumNous#5708)

The profile dropdown rendered the wallet item unconditionally, so it
still showed after an admin disabled the personal/topup (wallet) sidebar
module. Reuse the sidebar module visibility check so the dropdown honours
the same toggle as the sidebar.

Fixes QuantumNous#5696

* feat(system-settings): add user token limit configuration section (QuantumNous#5678)

* feat: add channel async polling delay toggle

Fixes QuantumNous#5717
Fixes QuantumNous#4244

* fix: add token limit save label translations

* feat: enhance i18n-translate skill

* feat: add date-fns and date-fns-tz dependencies

* feat: add date-fns and date-fns-tz paths to build configuration

* chore(deps): bump dompurify from 3.4.5 to 3.4.11 in /web/default (QuantumNous#5718)

Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.5 to 3.4.11.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](cure53/DOMPurify@3.4.5...3.4.11)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.11
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(ci): install classic workspace dependencies for releases (QuantumNous#5719)

* fix: use neutral drawing task labels

* perf(web): streamline table actions and destructive dialogs (QuantumNous#5645)

* perf(data-table): autosize action columns

- exclude actions columns from shared table width calculations so action cells size to their content.
- remove fixed size and w-* width overrides from feature action columns to preserve content-based layout.

* perf(data-table): streamline row action controls

- expose common edit and status actions directly while moving secondary actions into overflow menus.
- add shared row action menu helpers so static and table rows use consistent action controls.
- let action columns size to their content instead of relying on fixed widths.

* fix(web): localize destructive dialog copy

- route delete, reset, and batch update confirmation text through i18n.
- add locale entries for affected channel, model, system settings, and user dialogs.

* perf(web): unify destructive dialog actions

- align delete and cleanup confirmation buttons with the shared destructive variant.
- replace custom destructive color overrides with semantic button variants.
- clean up lint errors in touched dialog files before committing.

* fix(web): add user action success translations

- add localized success messages for user delete, status, and role changes.
- keep user management toast copy available across all frontend locales.

* fix(data-table): prevent mobile badge clipping

- expose badge cell slots so mobile card styles can target nested badge wrappers.
- reset badge margins in card rows to keep provider icons fully visible on small screens.

* fix: add Waffo goods info and webhook SDK update (QuantumNous#5704)

* fix: add Waffo goods info and webhook SDK update

* chore: remove Waffo test code from PR

* fix(model-pricing): refresh tiered expression editor when switching models (QuantumNous#5752)

Switching models in the pricing editor kept the previous model's tiers and prices in the expression panel: TieredPricingEditor seeds its internal visual/raw state only on mount, and the initRef guard never re-ran on prop changes, so only the model name updated.

Bump a reload token in the same effect that seeds billingExpr and use it as the editor's key, so a freshly loaded model remounts the editor and re-parses its expression. The token changes in lockstep with billingExpr, and user edits (which only touch state) do not trigger it.

Closes QuantumNous#5750

* chore(deps): sync bun.lock for dompurify 3.4.11 (QuantumNous#5738)

* fix(theme): 切换前端主题后重置到首页,避免路由 404 (QuantumNous#5612)

* fix(theme): 切换前端主题后重置到首页,避免路由 404
经典前端与新版前端的路由路径不同,切换主题后停留在原路径会导致 404:
- 经典前端切换到新版前端时跳转首页,不再原地刷新当前路径
- 新版前端保存时若前端主题发生变化,保存成功后跳转首页

Fixes QuantumNous#4947

* fix: 更新前端切换提示信息,修正页面跳转逻辑

* fix(task): attribute async task usage log to the initiating node (QuantumNous#5684)

Async task usage logs (LogQuotaData node dimension) were recorded
under whichever node happened to poll the task to completion, not the
node that submitted it. For token/adaptor-billed video tasks the
pre-deduction is often 0, so the entire quota landed on the last
polling node.

Snapshot common.NodeName into TaskPrivateData at submit time and use
it when writing the settlement consume log; fall back to the current
node when empty so existing tasks stay compatible.

* chore: update i18n skill

* feat: better admin permissions (QuantumNous#5755)

* feat: add casbin admin permissions

* feat: improve audit logging to associate logs with actual operators and target users

* feat: enhance admin permissions and UI interactions for sensitive actions

* Refactor authz RBAC and tighten channel permissions

* Split channel authz field policy

* Address channel authz review findings

* fix: adapt ClickHouse log LIKE filters

* feat(playground): improve Playground chat experience and Markdown rendering (QuantumNous#5217)

* refactor(playground): streamline chat request state

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.

* fix(playground): validate persisted chat state

- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.

* refactor(playground): centralize message content access

- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.

* refactor(playground): split storage schemas

- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.

* refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.

* refactor(playground): extract prompt suggestions

- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.

* refactor(playground): extract input tools

- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.

* refactor(playground): extract input controls

- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.

* refactor(playground): extract message content display

- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.

* refactor(playground): extract message editor

- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.

* refactor(playground): extract stream error parsing

- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.

* refactor(playground): extract request error parsing

- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.

* refactor(playground): extract streaming chunk updates

- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.

* refactor(playground): extract message reasoning parser

- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.

* refactor(playground): extract message streaming utilities

- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.

* refactor(playground): extract message update utilities

- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.

* refactor(playground): extract completion choice handling

- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.

* refactor(playground): centralize assistant completion state

- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.

* refactor(playground): extract stream message parsing

- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.

* refactor(playground): extract stream ready state checks

- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.

* refactor(playground): extract conversation message helpers

- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.

* refactor(playground): extract state initialization helpers

- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.

* refactor(playground): extract option fallback helpers

- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.

* refactor(playground): extract message action helpers

- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.

* refactor(playground): extract input control state

- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.

* refactor(playground): extract message content state

- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.

* refactor(playground): extract message editor state

- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.

* refactor(playground): extract message error state

- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.

* refactor(playground): extract chat render state

- move editing content lookup and per-message render flags into conversation helpers.

- keep the chat component focused on mapping messages to editor and content views.

* refactor(playground): extract suggestion display state

- move suggestion class selection into a pure helper.

- keep the suggestions component focused on translation and rendering.

* refactor(playground): extract assistant message state checks

- move final and pending assistant status checks into streaming utilities.

- keep the chat handler focused on request lifecycle updates.

* refactor(playground): extract input tool state

- move attachment action metadata and development notices into input tool utilities.

- keep the input tools component focused on menu and button rendering.

* refactor(playground): extract stream protocol checks

- move SSE done-message and closed-ready-state checks into stream utilities.

- keep the stream request hook focused on event handling flow.

* refactor(playground): extract message removal helper

- move delete-message filtering into conversation message utilities.

- keep the conversation hook focused on action orchestration.

* refactor(playground): extract option error messages

- move option load error message selection into playground option utilities
- keep the options hook focused on query effects and fallback updates

* refactor(playground): extract input submit text helper

- move prompt submit text validation into input control utilities
- let the input component submit only when a concrete text value is available

* refactor(playground): centralize error message checks

- add a shared helper for identifying error messages
- remove direct status string checks from message content rendering

* refactor(playground): extract message content display checks

- move loader and content visibility decisions into local helper functions
- keep message content state assembly focused on composing render state

* refactor(playground): replace raw message role checks

- use shared message role constants in conversation edit handling
- avoid raw assistant role literals when validating API messages

* refactor(playground): extract non-stream response handling

- move chat completion response choice handling into message streaming utilities
- keep the chat handler focused on request lifecycle and error routing

* refactor(playground): centralize stream cleanup

- reuse one stream cleanup path for completion, errors, startup failures, and manual stops
- preserve the current-source guard when closing SSE streams

* refactor(playground): extract pending assistant check

- centralize pending assistant message detection in streaming utilities
- reuse the helper when sanitizing stored playground messages

* perf(playground): improve mobile input controls

- split mobile input controls into selector and action rows
- keep the desktop input footer compact while reducing mobile control crowding

* perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state

* perf(playground): improve mobile message actions

- collapse mobile message actions into a touch-friendly dropdown menu
- keep the desktop hover action strip unchanged for pointer workflows
- share one action list between desktop buttons and the mobile menu

* perf(playground): add error recovery actions

- show retry, edit, and delete actions inside error message alerts
- route edit recovery to the previous user prompt when available
- keep recovery controls touch-friendly on mobile layouts

* perf(playground): refine message editing experience

- present message edits in a focused bordered editor panel
- add unsaved-change state, reset, and cancel confirmation flows
- improve mobile touch targets and keyboard shortcuts for editing

* perf(playground): improve markdown code blocks

- render fenced markdown code with syntax highlighting, line numbers, and fallback plain text
- add copy, download, and collapse controls for playground AI responses
- tighten code block layout and theme token styles for responsive markdown rendering

* fix(playground): constrain markdown code block height

- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in

* feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.

* perf(playground): improve chat markdown rendering

- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.

* style: format code block component

* style: format playground frontend files

* feat(playground): render markdown with stream parser

- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.

* fix(playground): localize reasoning and chat feedback

- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.

* refactor(playground): group files by functional area

- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.

* fix(playground): prevent history replay from freezing page

- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.

* feat(playground): allow regenerating from user messages

- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.

* feat(playground): add raw response source view

- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.

* feat(playground): render code with unified editor

- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.

* perf(playground): streamline chat input controls

- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.

* fix(playground): filter models by selected group

- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.

* perf(playground): remove input suggestion chips

- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

* perf(playground): stabilize reasoning trigger layout

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.

* perf(playground): smooth reasoning expansion

- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.

* fix(auth): align password validation copy (QuantumNous#5759)

* fix(i18n): add missing frontend translations

- add missing locale entries for API key loading, channel model empty states, auth, playground, and model configuration copy.
- correct inaccurate Russian and Vietnamese model empty-state translations to avoid fallback or misleading copy.

* fix(auth): align password validation copy

- remove the login password length gate so existing shorter passwords are not blocked before reaching the server.
- reuse distinct minimum-length and 8-20 character messages based on the actual validation rule.
- drop unused duplicate password locale keys and align the user creation placeholder with the 8-20 character constraint.

* fix(i18n): add auth validation message translations

- cover schema-driven auth form errors that are translated through FormMessage.
- keep password, username, confirmation, and OTP validation messages available in every locale.

* fix(web): render custom HTML and Markdown content consistently (QuantumNous#5760)

* fix(markdown): render announcement markdown consistently

- support soft line breaks for announcement markdown without changing the default parser behavior.
- add explicit markdown element styles so lists, tables, code blocks, and quotes render correctly when typography styles are unavailable.
- apply the announcement markdown mode in both the popover and detail dialog for consistent display.

* refactor(markdown): simplify fallback markdown styles

- remove duplicate typography utility classes now covered by explicit markdown element fallbacks.
- keep the markdown renderer behavior unchanged while reducing class noise.
- modernize small helper expressions to satisfy targeted lint checks.

* fix(content): render custom HTML consistently

- add shared rich content rendering so custom HTML and Markdown use the same path across public pages and announcements.
- reuse common URL and HTML detection instead of duplicating content format checks per page.
- keep custom home content inside the standard public layout while preserving full-page iframe rendering for external URLs.

* fix(security): pin patched frontend transitive dependencies

* fix(web): secure rich content rendering

* fix(web): harden iframe sandboxing

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: CaIon <i@caion.me>
Co-authored-by: Benson Yan <fuxin04@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Seefs <40468931+seefs001@users.noreply.github.com>
Co-authored-by: QuentinHsu <xuquentinyang@gmail.com>
Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com>
Co-authored-by: feitianbubu <feitianbubu@qq.com>
Co-authored-by: RedwindA <128586631+RedwindA@users.noreply.github.com>
Co-authored-by: zhongyuanzhao-alt <zhongyuan.zhao@waffo.com>
Co-authored-by: peakchao <zhangzhichaolove@vip.qq.com>
@Calcium-Ion Calcium-Ion deleted the feat/better-admin branch June 28, 2026 05:42
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