Skip to content

fix(ui): 修复删除弹窗崩溃并清理 lint 阻塞#1

Open
LeekJay wants to merge 3 commits into
CuzTeam:mainfrom
LeekJay:fix/ai-providers-delete-dialog
Open

fix(ui): 修复删除弹窗崩溃并清理 lint 阻塞#1
LeekJay wants to merge 3 commits into
CuzTeam:mainfrom
LeekJay:fix/ai-providers-delete-dialog

Conversation

@LeekJay

@LeekJay LeekJay commented Jun 9, 2026

Copy link
Copy Markdown

Summary

  • Fix Button asChild rendering so Radix Slot always receives a single child.
  • Move UI variant helpers and sidebar context out of component export files to satisfy Fast Refresh lint rules.
  • Remove synchronous setState-in-effect patterns from AuthFilesPage and stabilize ConfigPage handleSave with useCallback.

Verification

  • bun run type-check
  • bun run lint
  • bun run build

Note: Windows local build still prints the existing git describe /dev/null path noise before Vite build, but the build exits successfully.

Summary by CodeRabbit

  • New Features
    • Auth Files view now remembers filter and page state and improves batch-selection actions (select page/filtered/invert) for more reliable bulk operations.
  • Chores
    • Improved button loading/rendering when used with child elements.
  • Refactor
    • Consolidated UI variant/config and sidebar state into shared modules for more consistent styling, typing, and behavior; trimmed several internal helper exports.

删除确认弹窗中的 AlertDialogAction 通过 Button asChild 组合 Radix Slot,按钮内部额外渲染 loading 图标会让 Slot 收到多个子节点并导致 /ai-providers 删除操作崩溃,本次修复 asChild 场景的子节点结构。

变更:

- 将 loading 图标抽出为可复用节点

- 在 asChild 且 loading 时把图标注入唯一的 React 子元素

- 保持普通 button 场景继续渲染 loading 图标与原 children

影响:

- 修复 /ai-providers 点击删除时的 Radix Slot 崩溃

- 保留 Button 组件现有 loading 表现,降低对其他按钮的回归风险
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Extracts CVA variant definitions into new modules, updates component prop typings and module exports, centralizes sidebar context into a new hook, refactors Button loading/asChild rendering, and refactors AuthFilesPage selection/persistence and ConfigPage's handleSave to use callbacks.

Changes

UI variant extraction, sidebar context, button render, and pages refactor

Layer / File(s) Summary
Extracted variant modules & export updates
src/components/ui/button-variants.ts, src/components/ui/toggle-variants.ts, src/components/ui/button.tsx, src/components/config/VisualConfigEditor.tsx, src/components/ui/badge.tsx, src/components/ui/button-group.tsx, src/components/ui/tabs.tsx, src/components/ui/toggle.tsx, src/components/ui/toggle-group.tsx
Creates button-variants and toggle-variants CVA modules and *VariantProps types; updates imports/prop typings and removes multiple internal variant re-exports from UI modules.
Button loading & asChild render
src/components/ui/button.tsx
Precomputes loader when loading is true, derives slottedChildren by cloning children in asChild mode to inject the loader, and updates render branches; Button prop typing now uses ButtonVariantProps.
Sidebar context extraction & wiring
src/components/ui/sidebar-context.ts, src/components/ui/sidebar.tsx, src/components/app-sidebar.tsx
Adds a new SidebarContext module and useSidebar hook; removes local context/hook from sidebar.tsx, updates imports, and removes useSidebar export from the sidebar module.
LobeProviderIcon API cleanup
src/components/common/LobeProviderIcon.tsx
Makes LobeProviderIconProps and normalizeLobeProviderKey internal and removes the previously exported hasLobeProviderIcon helper; the component remains exported.
AuthFilesPage selection centralization & persistence
src/pages/AuthFilesPage.tsx
Reads initial filter/page from persisted UI state and persists changes; centralizes memoized selection handlers for toggle-all, toggle-file, select page/filtered, and invert selection; rewires header/row checkboxes and batch-action buttons to use new handlers.
ConfigPage handleSave memoization
src/pages/ConfigPage.tsx
Converts handleSave into a useCallback-memoized handler and adds an explicit dependency array.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

"I nibble code like crunchy treats,
variants harvested in tidy heaps,
sidebar roots dug out and shared,
buttons clone their tiny heaps.
Selection lines aligned and neat — a rabbit's tap-tap beat." 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is in Chinese and describes fixing a delete dialog crash and cleaning up lint blocking, but the changes primarily involve refactoring variant helpers and context exports, not the delete dialog crash itself. The title uses non-descriptive or potentially misleading phrasing in a non-English language; provide a clear English summary of the main changes or clarify which delete dialog issue is being addressed.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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 and usage tips.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the Button component to support a loading state when asChild is true by cloning the child element and injecting a loader icon. The review feedback points out a potential React key warning when cloning elements with multiple children and suggests using React.Children.toArray to safely flatten and key the children.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +69 to +77
const slottedChildren =
asChild && loader && React.isValidElement(children)
? React.cloneElement(
children,
undefined,
loader,
(children.props as { children?: React.ReactNode }).children
)
: children

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.

medium

When asChild is true and the slotted element contains multiple children (e.g., <Button asChild><a><Icon /> <span>Text</span></a></Button>), children.props.children will be an array. Passing this array directly as an argument to React.cloneElement alongside loader will cause React to treat it as a nested array and trigger a console warning:

Warning: Each child in a list should have a unique "key" prop.

To prevent this warning, we can flatten and safely key the children using React.Children.toArray and spread them into React.cloneElement.

Suggested change
const slottedChildren =
asChild && loader && React.isValidElement(children)
? React.cloneElement(
children,
undefined,
loader,
(children.props as { children?: React.ReactNode }).children
)
: children
const slottedChildren =
asChild && loader && React.isValidElement(children)
? React.cloneElement(
children,
undefined,
loader,
...React.Children.toArray((children.props as { children?: React.ReactNode }).children)
)
: children

前端 lint 当前被 AuthFilesPage 同步 setState、Fast Refresh 非组件导出以及 ConfigPage 不稳定 hook 依赖阻塞,本次集中修复这些已知问题以恢复 lint 通过。

变更:

- 将 AuthFilesPage 持久化状态改为 lazy initializer,并把批量操作栏打开动作移到用户事件

- 拆出 Button、Toggle variants 与 Sidebar context,收窄 UI 组件文件导出

- 将 ConfigPage 的 handleSave 包装为 useCallback,稳定 pageActions 依赖

影响:

- bun run lint 可通过,减少 Fast Refresh 与 hooks 规则噪声

- 保留批量操作栏动画与现有按钮样式行为
@LeekJay LeekJay changed the title fix(ui): 修复 AI Providers 删除确认弹窗崩溃 fix(ui): 修复删除弹窗崩溃并清理 lint 阻塞 Jun 9, 2026
在 Claude 和 OpenAI Compatibility 的新建/编辑表单里,测试模型的“自动”选项会生成空字符串项,触发 Radix Select 的约束并中断表单。改用非空哨兵值后,界面仍保留自动选择语义,保存和连通性测试都继续按空值处理。

变更:
- 为自动测试模型选项引入 `__auto__` 哨兵值
- 将 Select 当前值映射为哨兵,避免向 `Select.Item` 传入空字符串
- 在选择变化时把哨兵还原为空字符串,保持现有持久化语义

影响:
- 新建和编辑 Claude / OpenAI Compatibility 提供商时不再直接报错
- 自动测试模型的交互和保存结果保持不变

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/features/providers/sheets/forms/BaseProviderForm.tsx`:
- Line 69: The constant AUTO_TEST_MODEL_VALUE ('__auto__') can collide with a
real model name; change the approach in BaseProviderForm by making the sentinel
collision-proof: either generate a unique sentinel per options set (e.g., append
a UUID or index) or escape/unescape real model values before mapping to the
sentinel, and update all places that map model values back in onChange (see
usages around AUTO_TEST_MODEL_VALUE and the mapping logic at the regions
corresponding to lines 313-315 and 616-618) so that selection and save correctly
round-trip without losing a real model named '__auto__'.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 41319ba0-f633-4277-b3c8-d0aad1b5bfd7

📥 Commits

Reviewing files that changed from the base of the PR and between 6e08701 and 09ac2fe.

📒 Files selected for processing (1)
  • src/features/providers/sheets/forms/BaseProviderForm.tsx

apiKey: '',
proxyUrl: '',
});
const AUTO_TEST_MODEL_VALUE = '__auto__';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid sentinel collision with real model values

Using a fixed sentinel ('__auto__') can corrupt intent when a real model name matches it: selecting that model is converted to '' on save. Please make the auto sentinel collision-proof per options set (or escape/unescape model values) before mapping back in onChange.

Suggested direction
-const AUTO_TEST_MODEL_VALUE = '__auto__';
+const AUTO_TEST_MODEL_VALUE_BASE = '__auto__';

-const testModelOptions = useMemo(() => {
+const { testModelOptions, autoTestModelValue } = useMemo(() => {
   const seen = new Set<string>();
   const names: string[] = [];
   ...
-  const opts: Array<{ value: string; label: string }> = [
-    { value: AUTO_TEST_MODEL_VALUE, label: autoLabel },
-  ];
+  let autoTestModelValue = AUTO_TEST_MODEL_VALUE_BASE;
+  while (seen.has(autoTestModelValue)) autoTestModelValue += '_';
+  const opts: Array<{ value: string; label: string }> = [
+    { value: autoTestModelValue, label: autoLabel },
+  ];
   ...
-  return opts;
+  return { testModelOptions: opts, autoTestModelValue };
 }, [form.models, form.testModel, t]);

-const testModelValue = form.testModel?.trim() ? form.testModel : AUTO_TEST_MODEL_VALUE;
+const testModelValue = form.testModel?.trim() ? form.testModel : autoTestModelValue;

-onChange={(value) => updateField('testModel', value === AUTO_TEST_MODEL_VALUE ? '' : value)}
+onChange={(value) => updateField('testModel', value === autoTestModelValue ? '' : value)}

Also applies to: 313-315, 616-618

🤖 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 `@src/features/providers/sheets/forms/BaseProviderForm.tsx` at line 69, The
constant AUTO_TEST_MODEL_VALUE ('__auto__') can collide with a real model name;
change the approach in BaseProviderForm by making the sentinel collision-proof:
either generate a unique sentinel per options set (e.g., append a UUID or index)
or escape/unescape real model values before mapping to the sentinel, and update
all places that map model values back in onChange (see usages around
AUTO_TEST_MODEL_VALUE and the mapping logic at the regions corresponding to
lines 313-315 and 616-618) so that selection and save correctly round-trip
without losing a real model named '__auto__'.

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