Skip to content

Feat/provider: Refactor provider router and enhance UI components#109

Merged
whhjdi merged 3 commits intoawsl-project:mainfrom
whhjdi:feat/provider
Jan 17, 2026
Merged

Feat/provider: Refactor provider router and enhance UI components#109
whhjdi merged 3 commits intoawsl-project:mainfrom
whhjdi:feat/provider

Conversation

@whhjdi
Copy link
Contributor

@whhjdi whhjdi commented Jan 17, 2026

Summary by CodeRabbit

发布说明

  • 新功能

    • 为提供商创建和编辑流程添加了独立的路由导航。
  • 重构

    • 优化了提供商创建工作流程,采用基于路由的导航。
    • 将动画背景元素提取为可复用组件。
    • 重组提供商表单状态管理系统。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

概述

此PR引入了新的提供商创建和编辑工作流程,采用基于hooks和context的架构替代原有的prop驱动模式。同时添加了MarqueeBackground可复用UI组件,用于多个位置的流媒体背景动画。路由层面支持了多步骤创建流程,包括类型选择、token导入和自定义配置。

变更清单

Cohort / File(s) 变更摘要
MarqueeBackground 组件
web/src/components/ui/marquee-background.tsx, web/src/components/ui/index.ts
新增MarqueeBackground组件,支持show、color、opacity参数控制动画背景的显示和样式,通过绝对定位和CSS过渡实现非交互式背景效果
Marquee背景引用更新
web/src/components/layout/app-sidebar/client-routes-items.tsx, web/src/components/layout/app-sidebar/requests-nav-item.tsx, web/src/pages/client-routes/components/provider-row.tsx, web/src/pages/providers/components/provider-row.tsx
将内联div动画背景替换为MarqueeBackground组件调用,简化条件渲染逻辑
提供商表单上下文和导航
web/src/pages/providers/context/provider-form-context.tsx, web/src/pages/providers/hooks/use-provider-navigation.ts
新增ProviderFormProvider和useProviderForm以管理表单状态(数据、验证、保存状态),新增useProviderNavigation hook提供六个导航助手方法
提供商创建流程重构
web/src/pages/providers/components/select-type-step.tsx, web/src/pages/providers/components/custom-config-step.tsx, web/src/pages/providers/components/antigravity-token-import.tsx, web/src/pages/providers/components/kiro-token-import.tsx
将所有组件从prop驱动转换为self-contained模式,使用内部hooks而非外部回调,整合内部导航和mutation逻辑,自定义配置步骤移除了多步流程UI分支
路由和页面布局
web/src/App.tsx, web/src/pages/providers/create-layout.tsx, web/src/pages/providers/edit.tsx
App.tsx新增两条路由(/providers/create/\* 和 /providers/:id/edit),create-layout.tsx包装ProviderFormProvider并定义多步创建路由,edit.tsx新增编辑页面组件
提供商列表页面
web/src/pages/providers/index.tsx
移除本地组件导入和状态流,改用导航方式触发路由(创建、编辑操作现通过navigate调用)

时序图

sequenceDiagram
    actor User
    participant Router as Router/Pages
    participant FormContext as ProviderFormContext
    participant FormHook as useProviderForm
    participant NavHook as useProviderNavigation
    participant Steps as Create Steps<br/>(Select/Config/Import)
    participant API as Provider API

    User->>Router: 点击"添加提供商"
    Router->>Router: 导航至 /providers/create
    Router->>FormContext: 初始化 ProviderFormProvider
    FormContext->>FormHook: 提供表单状态和更新函数

    User->>Steps: 选择提供商类型
    Steps->>FormHook: updateFormData(type)
    FormHook->>FormContext: 更新表单数据
    Steps->>NavHook: goToAntigravity() 或 goToKiro()
    NavHook->>Router: 导航至对应步骤

    User->>Steps: 输入Token/配置
    Steps->>FormHook: updateFormData(config)
    FormHook->>FormContext: 合并配置数据
    
    User->>Steps: 点击创建
    Steps->>API: createProvider.mutateAsync(formData)
    API-->>Steps: 成功响应
    Steps->>NavHook: goToProviders()
    NavHook->>Router: 导航至 /providers
    Router-->>User: 显示提供商列表
Loading

预计审查工作量

🎯 3 (中等复杂) | ⏱️ ~25 分钟

可能相关的PR

  • awsl-project/maxx#107 — 同样修改了侧边栏UI和MarqueeBackground组件使用,接触相同的provider行组件
  • awsl-project/maxx#94 — 修改了提供商UI、路由和创建/编辑页面,涉及相同的文件结构
  • awsl-project/maxx#85 — 修改了provider-row.tsx等提供商相关UI文件和路由配置

建议审查者

  • awsl233777

兔兔之歌

🐰 表单装进Context,Hook驱动新舞步,
多步创建更灵动,导航闪闪放光彩!
马蹄背景优雅舞,代码重构美如诗,
长耳朵为你点赞 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了主要改动:重构提供者路由和增强UI组件,涵盖了新增路由、新建context、新增MarqueeBackground组件等核心变更。

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
web/src/pages/providers/components/antigravity-token-import.tsx (1)

181-209: OAuth 创建失败不会向用户展示错误

handleOAuthCreate 捕获异常只设置了 error,但错误提示仅在 oauthStatus === 'error' 时渲染,导致创建失败时用户无反馈。建议在成功态也展示创建错误或设置独立状态。

🛠️ 建议修复(示例)
- {error && oauthStatus === 'error' && (
+ {error && (oauthStatus === 'error' || oauthStatus === 'success') && (
   <div className="bg-error/5 border border-error/20 rounded-xl p-4 flex items-start gap-3 animate-in fade-in zoom-in-95">
     <AlertCircle size={20} className="text-error shrink-0 mt-0.5" />
     <div>
-      <p className="text-sm font-medium text-error">OAuth Failed</p>
+      <p className="text-sm font-medium text-error">
+        {oauthStatus === 'error' ? 'OAuth Failed' : 'Provider Creation Failed'}
+      </p>
       <p className="text-xs text-error/80 mt-0.5">{error}</p>
     </div>
   </div>
 )}
🤖 Fix all issues with AI agents
In `@web/src/pages/providers/components/select-type-step.tsx`:
- Line 183: The "Empty template" button currently only navigates via
goToCustomConfig without clearing previously selected template/config; before
calling goToCustomConfig, reset the template-related form state (e.g.,
selectedTemplate, templateId, and any templateConfig values) by invoking the
form context's reset method (e.g., formContext.resetForm() or
form.resetFields()) or manually setting defaults from the form context defaults,
then call goToCustomConfig so the "从零开始" state is truly empty.
🧹 Nitpick comments (6)
web/src/pages/providers/edit.tsx (1)

12-12: ID 比较方式可以优化

使用 p.id + '' === id + '' 进行字符串比较虽然能正常工作,但可读性较差。建议使用更清晰的方式:

♻️ 建议的改进
-  const provider = providers?.find((p) => p.id + '' === id + '');
+  const provider = providers?.find((p) => String(p.id) === id);
web/src/pages/providers/context/provider-form-context.tsx (1)

22-28: 建议为 validationResultoauthState 定义具体类型

使用 any 类型会失去 TypeScript 的类型安全优势。建议定义具体的接口类型以提高代码可维护性和类型安全性。

♻️ 建议定义具体类型
+// 定义具体类型替代 any
+interface ValidationResult {
+  isValid: boolean;
+  message?: string;
+  // 根据实际需求添加其他字段
+}
+
+interface OAuthState {
+  token?: string;
+  expiresAt?: number;
+  // 根据实际需求添加其他字段
+}
+
 interface ProviderFormContextType {
   // ...
-  validationResult: any | null;
-  setValidationResult: (result: any | null) => void;
+  validationResult: ValidationResult | null;
+  setValidationResult: (result: ValidationResult | null) => void;
 
-  oauthState: any | null;
-  setOAuthState: (state: any | null) => void;
+  oauthState: OAuthState | null;
+  setOAuthState: (state: OAuthState | null) => void;
 }
web/src/pages/client-routes/components/provider-row.tsx (1)

186-188: 未使用的 isOverlay 参数

isOverlay 参数被解构但未使用,虽然已添加 eslint-disable 注释。如果此参数是为将来功能预留的,建议添加 TODO 注释说明用途;如果不再需要,可以考虑移除。

web/src/pages/providers/create-layout.tsx (1)

11-16: 考虑添加未知路径的回退处理

当前路由配置没有处理 /providers/create/invalid-path 这类无效路径的情况。可以考虑添加一个通配符路由来重定向到选择类型页面。

♻️ 可选的回退路由
 <Routes>
   <Route index element={<SelectTypeStep />} />
   <Route path="custom" element={<CustomConfigStep />} />
   <Route path="antigravity" element={<AntigravityTokenImport />} />
   <Route path="kiro" element={<KiroTokenImport />} />
+  <Route path="*" element={<Navigate to="/providers/create" replace />} />
 </Routes>

需要从 react-router-dom 导入 Navigate

web/src/pages/providers/index.tsx (1)

1-1: 建议合并导入语句

useState 可以与其他 React hooks 合并到同一行导入,保持导入语句的整洁。

♻️ 合并导入
-import { useMemo, useRef } from 'react';
+import { useMemo, useRef, useState } from 'react';
 import { Plus, Layers, Download, Upload } from 'lucide-react';
 import { useTranslation } from 'react-i18next';
 import { useNavigate } from 'react-router-dom';
 import { useProviders, useAllProviderStats } from '@/hooks/queries';
 import { useStreamingRequests } from '@/hooks/use-streaming';
 import type { Provider, ImportResult } from '@/lib/transport';
 import { getTransport } from '@/lib/transport';
 import { ProviderRow } from './components/provider-row';
 import { useQueryClient } from '@tanstack/react-query';
 import { Button } from '@/components/ui/button';
 import { PageHeader } from '@/components/layout/page-header';
 import { PROVIDER_TYPE_CONFIGS, type ProviderTypeKey } from './types';
-import { useState } from 'react';

Also applies to: 14-14

web/src/pages/providers/components/custom-config-step.tsx (1)

45-48: setTimeout 导航建议加清理

成功后延迟跳转若组件提前卸载,可能触发意外导航。可选地记录定时器并在卸载时清理。

♻️ 可选改进(示例)
+import { useEffect, useRef } from 'react';
 import { Globe, ChevronLeft, Key, Check } from 'lucide-react';
 ...
 export function CustomConfigStep() {
+  const redirectTimeoutRef = useRef<number | null>(null);
   ...
-      setTimeout(() => goToProviders(), 500);
+      redirectTimeoutRef.current = window.setTimeout(() => goToProviders(), 500);
     } catch (error) {
       console.error('Failed to create provider:', error);
       setSaveStatus('error');
     } finally {
       setSaving(false);
     }
   };
+
+  useEffect(() => {
+    return () => {
+      if (redirectTimeoutRef.current !== null) {
+        clearTimeout(redirectTimeoutRef.current);
+      }
+    };
+  }, []);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62e6eb7 and ffe9967.

📒 Files selected for processing (16)
  • web/src/App.tsx
  • web/src/components/layout/app-sidebar/client-routes-items.tsx
  • web/src/components/layout/app-sidebar/requests-nav-item.tsx
  • web/src/components/ui/index.ts
  • web/src/components/ui/marquee-background.tsx
  • web/src/pages/client-routes/components/provider-row.tsx
  • web/src/pages/providers/components/antigravity-token-import.tsx
  • web/src/pages/providers/components/custom-config-step.tsx
  • web/src/pages/providers/components/kiro-token-import.tsx
  • web/src/pages/providers/components/provider-row.tsx
  • web/src/pages/providers/components/select-type-step.tsx
  • web/src/pages/providers/context/provider-form-context.tsx
  • web/src/pages/providers/create-layout.tsx
  • web/src/pages/providers/edit.tsx
  • web/src/pages/providers/hooks/use-provider-navigation.ts
  • web/src/pages/providers/index.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
web/src/pages/providers/components/custom-config-step.tsx (2)
web/src/pages/providers/context/provider-form-context.tsx (1)
  • useProviderForm (95-101)
web/src/pages/providers/hooks/use-provider-navigation.ts (1)
  • useProviderNavigation (3-14)
web/src/pages/providers/components/provider-row.tsx (2)
web/src/components/ui/index.ts (1)
  • MarqueeBackground (49-49)
web/src/components/ui/marquee-background.tsx (1)
  • MarqueeBackground (24-39)
web/src/pages/providers/edit.tsx (1)
web/src/pages/providers/components/provider-edit-flow.tsx (1)
  • ProviderEditFlow (33-292)
web/src/components/ui/marquee-background.tsx (1)
web/src/components/ui/index.ts (1)
  • MarqueeBackground (49-49)
web/src/pages/providers/components/antigravity-token-import.tsx (3)
web/src/pages/providers/hooks/use-provider-navigation.ts (1)
  • useProviderNavigation (3-14)
web/src/lib/transport/http-transport.ts (1)
  • createProvider (96-99)
web/src/hooks/queries/use-providers.ts (1)
  • useCreateProvider (37-47)
web/src/components/layout/app-sidebar/client-routes-items.tsx (4)
web/src/components/ui/index.ts (1)
  • MarqueeBackground (49-49)
web/src/components/ui/marquee-background.tsx (1)
  • MarqueeBackground (24-39)
web/src/components/icons/client-icons.tsx (1)
  • ClientIcon (65-100)
web/src/components/ui/streaming-badge.tsx (1)
  • StreamingBadge (27-81)
web/src/pages/client-routes/components/provider-row.tsx (2)
web/src/components/ui/index.ts (1)
  • MarqueeBackground (49-49)
web/src/components/ui/marquee-background.tsx (1)
  • MarqueeBackground (24-39)
web/src/pages/providers/context/provider-form-context.tsx (1)
web/src/pages/providers/types.ts (3)
  • ProviderFormData (165-172)
  • ClientConfig (150-155)
  • defaultClients (157-162)
web/src/App.tsx (2)
web/src/pages/providers/create-layout.tsx (1)
  • ProviderCreateLayout (8-19)
web/src/pages/providers/edit.tsx (1)
  • ProviderEditPage (6-31)
web/src/pages/providers/components/kiro-token-import.tsx (3)
web/src/pages/providers/hooks/use-provider-navigation.ts (1)
  • useProviderNavigation (3-14)
web/src/lib/transport/http-transport.ts (1)
  • createProvider (96-99)
web/src/hooks/queries/use-providers.ts (1)
  • useCreateProvider (37-47)
🔇 Additional comments (20)
web/src/pages/providers/edit.tsx (1)

6-30: 组件结构清晰,状态处理完善

组件正确处理了加载状态、未找到状态和成功状态。使用 useProviders 获取数据并传递给 ProviderEditFlow 的方式符合 React 最佳实践。

web/src/pages/providers/hooks/use-provider-navigation.ts (1)

3-13: 导航 Hook 实现简洁明了

这个 Hook 很好地封装了导航逻辑,提供了语义化的导航方法。这种模式使得组件代码更加清晰,也便于后续路由变更时统一修改。

web/src/components/ui/index.ts (1)

47-49: 导出新增正确

MarqueeBackground 组件的导出遵循了现有的 barrel export 模式,保持了代码风格一致性。

web/src/pages/providers/context/provider-form-context.tsx (2)

42-47: 状态管理实现良好

Context Provider 正确初始化了所有必要的状态,使用 useState 管理表单数据和各种状态标志。updateFormDataupdateClient 方法使用不可变更新模式,符合 React 最佳实践。


67-74: 此顾虑不适用 - 创建和编辑流程使用不同的组件

ProviderFormContext 仅在创建流程中使用(create-layout.tsx),编辑流程使用独立的 ProviderEditFlow 组件(provider-edit-flow.tsx),两者有各自的验证逻辑。编辑流程中的 isValid() 不要求 apiKey 非空,并在保存时保留现有密钥(apiKey: formData.apiKey || provider.config?.custom?.apiKey || ''),与 UI 标签"留空保持当前"的设计一致。两个流程的验证差异是合理的,无需修改。

Likely an incorrect or invalid review comment.

web/src/pages/client-routes/components/provider-row.tsx (1)

253-257: MarqueeBackground 组件使用正确

使用新的 MarqueeBackground 组件替代内联标记是很好的重构。show 条件正确检查了 streamingCount > 0 && enabled && !isInCooldown,与原有逻辑保持一致。

web/src/components/ui/marquee-background.tsx (1)

24-38: 组件设计良好,CSS 动画类已正确定义

MarqueeBackground 组件实现简洁优雅。使用 pointer-events-none 确保不会阻挡用户交互,absolute inset-0 实现全覆盖定位。animate-marquee CSS 类和 @keyframes marquee 动画已在全局样式 web/src/index.css 中正确定义,包括深色模式支持。组件通过 transition-opacity 提供平滑的淡入淡出效果,动画配置合理。

web/src/pages/providers/components/provider-row.tsx (1)

4-4: 重构为可复用组件,实现良好!

使用 MarqueeBackground 组件替代内联的条纹背景实现是一个很好的抽象。这提高了代码的一致性和可维护性,与 client-routes-items.tsxrequests-nav-item.tsx 中的使用模式保持一致。

Also applies to: 142-142

web/src/App.tsx (1)

50-51: 路由配置正确!

新增的路由结构合理:

  • /providers/create/* 使用通配符支持 ProviderCreateLayout 内部的嵌套路由(如 /providers/create/custom/providers/create/antigravity 等)
  • /providers/:id/edit 用于编辑特定 provider

路由放置在 /providers 索引路由之后,React Router v7 的路由排名算法会正确处理匹配优先级。

web/src/components/layout/app-sidebar/client-routes-items.tsx (1)

34-40: 重构一致且正确!

  1. MarqueeBackgroundshow 条件 streamingCount > 0 && !isActive 确保在激活状态下不显示动画背景,避免视觉冲突。
  2. SidebarMenuBadge 始终渲染是合理的,因为 StreamingBadge 组件内部会在 displayCount === 0 时返回 null,处理了自身的可见性逻辑。
web/src/pages/providers/create-layout.tsx (1)

8-18: 布局结构清晰!

ProviderCreateLayout 正确地将 ProviderFormProvider 上下文包裹在所有嵌套路由外层,确保表单状态在步骤切换时得以保持。嵌套路由结构符合 React Router v7 的模式。

web/src/components/layout/app-sidebar/requests-nav-item.tsx (1)

28-34: 与其他侧边栏项保持一致的重构!

实现与 client-routes-items.tsx 完全一致:

  • MarqueeBackground 使用相同的 show 条件模式
  • SidebarMenuBadge 始终渲染,StreamingBadge 内部处理可见性

这种一致性有助于代码维护。

web/src/pages/providers/index.tsx (2)

1-4: 导航驱动的流程重构良好!

将状态驱动的 UI 流程改为路由导航是一个很好的架构改进:

  • 更清晰的关注点分离
  • 创建/编辑流程有独立的 URL,支持浏览器历史导航和深度链接
  • 简化了 ProvidersPage 的职责

Also applies to: 14-14, 18-18


135-138: 导航路径正确!

三处导航调用都使用了正确的路由路径:

  • /providers/create 对应 ProviderCreateLayout
  • /providers/${provider.id}/edit 对应 ProviderEditPage

App.tsx 中定义的路由配置一致。

Also applies to: 153-158, 186-186

web/src/pages/providers/components/kiro-token-import.tsx (2)

19-25: 内部导航接线清晰

改为 useProviderNavigation 并在返回按钮上统一路由,和新的创建流程契合。

Also applies to: 106-106


88-89: 创建成功后跳转逻辑清楚

mutateAsync 完成后再导航,流程一致且易读。

web/src/pages/providers/components/antigravity-token-import.tsx (2)

24-33: 内部化导航与创建钩子替代 props 可读

路由和创建逻辑收敛到组件内部,后续维护更一致。

Also applies to: 223-223


171-172: Token 创建后导航流程 OK

创建成功即跳转列表,行为明确。

web/src/pages/providers/components/custom-config-step.tsx (1)

8-15: 表单上下文接线一致,状态驱动清楚

通过 useProviderForm/updateFormData 与导航钩子统一入口,UI 与状态同步明确。

Also applies to: 60-77, 105-106, 121-122, 140-141

web/src/pages/providers/components/select-type-step.tsx (1)

11-48: 类型选择与模板应用逻辑清晰

选择类型与应用模板的状态更新路径一致,阅读体验好。

Also applies to: 78-138, 216-216

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

{/* Empty Template Card */}
<Button
onClick={onSkipToConfig}
onClick={goToCustomConfig}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

“空模板”未清空已选模板/配置

点击“空模板”仅跳转,若用户之前已选择模板,配置仍会保留,和“从零开始”语义不符。建议跳转前重置模板相关字段(最好复用表单上下文的默认值/重置方法)。

🛠️ 建议修复(示例)
- <Button
-   onClick={goToCustomConfig}
+ <Button
+   onClick={() => {
+     updateFormData({
+       selectedTemplate: null,
+       name: '',
+       clients: formData.clients.map((client) => ({
+         ...client,
+         enabled: false,
+         urlOverride: '',
+       })),
+     });
+     goToCustomConfig();
+   }}
   variant="ghost"
   className="text-left group p-0 rounded-xl border border-dashed h-full w-full min-h-36 sm:min-h-40 transition-all duration-200 border-border bg-card hover:bg-muted hover:border-accent/30 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
 >
🤖 Prompt for AI Agents
In `@web/src/pages/providers/components/select-type-step.tsx` at line 183, The
"Empty template" button currently only navigates via goToCustomConfig without
clearing previously selected template/config; before calling goToCustomConfig,
reset the template-related form state (e.g., selectedTemplate, templateId, and
any templateConfig values) by invoking the form context's reset method (e.g.,
formContext.resetForm() or form.resetFields()) or manually setting defaults from
the form context defaults, then call goToCustomConfig so the "从零开始" state is
truly empty.

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.

2 participants