Refactor/UI: refactor sidebar & format codebase#107
Refactor/UI: refactor sidebar & format codebase#107whhjdi merged 5 commits intoawsl-project:mainfrom
Conversation
- Refactor AppSidebar to use object-based configuration - Create unified sidebar-config.tsx as single source of truth - Add TypeScript discriminated unions for type safety - Extract components: RequestsNavItem, ClientRoutesItems - Create SidebarRenderer for unified rendering logic - Remove deprecated nav-main, nav-management, nav-routes - Update Prettier configuration - Add semicolons (semi: true) - Use trailing commas everywhere (trailingComma: all) - Increase line width to 100 (printWidth: 100) - Always use arrow function parens (arrowParens: always) - Add explicit bracket spacing and JSX quote configs - Reformat all code with new Prettier config All functionality preserved: streaming badges, marquee animations, section grouping
📝 WalkthroughWalkthrough本次变更:用模块化的 AppSidebar + sidebarConfig + SidebarRenderer 替换旧 SidebarNav,并新增侧边栏类型定义;同时在大量文件中统一代码风格(分号、单引号、Prettier 配置)并增加若干 i18n 文案与 UI 小改动(provider 创建流程、Request 详情拆分等)。 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant AppLayout
participant AppSidebar
participant SidebarRenderer
participant SidebarConfig
User->>AppLayout: 打开应用页面
AppLayout->>AppSidebar: 请求渲染侧边栏
AppSidebar->>AppSidebar: 获取代理状态与版本
AppSidebar->>SidebarRenderer: 传入 sidebarConfig
SidebarRenderer->>SidebarConfig: 读取 sections 列表
SidebarConfig-->>SidebarRenderer: 返回 sections
loop 遍历 sections
SidebarRenderer->>SidebarRenderer: 遍历 items
alt type == "standard"
SidebarRenderer->>AppSidebar: 渲染标准导航项 (NavLink/Button)
else type == "custom"
SidebarRenderer->>AppSidebar: 渲染自定义组件
else type == "dynamic-section"
SidebarRenderer->>AppSidebar: 调用 generator 产出节点
end
end
SidebarRenderer-->>AppSidebar: 返回完整侧边栏节点
AppSidebar-->>AppLayout: 将侧边栏注入布局
AppLayout-->>User: 显示页面(含新侧边栏)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 分钟 Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
web/src/components/ui/sidebar.tsx (1)
154-161: 修复错误的 collapsible 属性命名'offExamples'为'off'。
collapsible属性的默认值'offExamples'是错误的命名。该值在类型定义(第161行)、CSS 选择器(第219、231、232、290、291、292行)中一致使用。字符串中包含 "Examples" 明确表示这是从示例代码中遗留下来的占位符。应将其更正为'off',与其他选项'icon'和'none'的命名风格保持一致。web/src/components/force-project-dialog.tsx (3)
225-231: 硬编码的中文文本破坏了 i18n 一致性组件已导入
useTranslation并在其他地方使用t()函数,但拒绝按钮的文本是硬编码的中文:
"拒绝中..."(Line 225)"拒绝"(Line 230)这会导致非中文用户看到中文界面文本。
🛠️ 建议修复
首先在
en.json和zh.json中添加相应的翻译 key,然后:{rejectSession.isPending ? ( <> <div className="h-4 w-4 animate-spin rounded-full border-2 border-red-400/30 border-t-red-400" /> - <span className="text-sm font-bold">拒绝中...</span> + <span className="text-sm font-bold">{t('sessions.rejecting')}</span> </> ) : ( <> <X size={16} /> - <span className="text-sm font-bold">拒绝</span> + <span className="text-sm font-bold">{t('sessions.reject')}</span> </> )}
248-262: 确认按钮同样存在硬编码中文文本确认按钮的文本也是硬编码的中文:
"绑定中..."(Line 250)"确认绑定"(Line 259)🛠️ 建议修复
{updateSessionProject.isPending ? ( <> <div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" /> - <span className="text-sm font-bold text-white">绑定中...</span> + <span className="text-sm font-bold text-white">{t('sessions.binding')}</span> </> ) : ( <> <FolderOpen size={16} className="text-amber-400 group-hover:text-white transition-colors" /> <span className="text-sm font-bold text-amber-400 group-hover:text-white transition-colors"> - 确认绑定 + {t('sessions.confirmBind')} </span> </> )}
267-270: 提示信息硬编码中文底部警告提示也是硬编码的中文文本。
🛠️ 建议修复
<div className="flex items-start gap-2 rounded-lg bg-muted/50 p-2.5 text-[11px] text-muted-foreground"> <AlertCircle size={12} className="mt-0.5 shrink-0" /> - <p>如果未在规定时间内选择项目,请求将被拒绝。</p> + <p>{t('sessions.timeoutWarning')}</p> </div>web/src/pages/providers/components/provider-card.tsx (1)
14-109: 可点击卡片缺少键盘可访问性(role/tabIndex/键盘触发)
AntigravityProviderCard和CustomProviderCard的外层容器是div,虽然绑定了onClick事件并应用了cursor-pointer样式表现为按钮,但缺少键盘交互支持。键盘用户和屏幕阅读器用户无法访问这些卡片。建议为两处卡片容器添加role="button"、tabIndex={0}和 Enter/Space 键处理逻辑。建议修复
export function AntigravityProviderCard({ provider, onClick, streamingCount }: ProviderCardProps) { const { t } = useTranslation(); + const handleCardKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }; return ( <div onClick={onClick} + onKeyDown={handleCardKeyDown} + role="button" + tabIndex={0} className={`bg-muted border border-border rounded-xl p-4 hover:border-accent/30 hover:bg-accent cursor-pointer transition-all relative group ${export function CustomProviderCard({ provider, onClick, streamingCount }: ProviderCardProps) { const { t } = useTranslation(); + const handleCardKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }; return ( <div onClick={onClick} + onKeyDown={handleCardKeyDown} + role="button" + tabIndex={0} className={`bg-muted border border-border rounded-xl p-4 hover:border-accent/30 hover:bg-accent cursor-pointer transition-all relative group ${
🤖 Fix all issues with AI agents
In `@web/src/components/cooldown-details-dialog.tsx`:
- Around line 123-137: The current formatUntilTime uses toLocaleString and then
the code splits that string (untilDateStr.split(' ')) which breaks for locales
that don't separate date/time with a space; update formatUntilTime into two
helpers (e.g., formatDatePart and formatTimePart) or use two Intl.DateTimeFormat
instances to produce the date and time independently (respectively using options
for month/day and for hour/minute/second with hour12 false) and replace the
untilDateStr + split logic with direct calls to these helpers when computing
datePart and timePart for the component; change references to formatUntilTime
and untilDateStr accordingly so no string split is performed.
In `@web/src/components/layout/app-sidebar/client-routes-items.tsx`:
- Around line 13-16: The component-level subscription useStreamingRequests() is
being called inside each ClientNavItem causing duplicate subscriptions; instead
call useStreamingRequests() once in the parent component ClientRoutesItems,
obtain countsByClient (or precompute a streamingCount map), and pass the
relevant value down to ClientNavItem via a prop (e.g., streamingCount or
countsByClient). Remove useStreamingRequests() from ClientNavItem (and any other
child that does the same around lines 51-56), update ClientNavItem's signature
to accept the prop, and ensure all places that rendered ClientNavItem now supply
the precomputed count to avoid repeated subscriptions and duplicate updates.
In `@web/src/components/layout/app-sidebar/requests-nav-item.tsx`:
- Line 15: The active-check using location.pathname.startsWith('/requests') is
too broad and matches paths like /requests-foo; update the logic that sets
isActive (the variable computed from location.pathname) to only match the exact
'/requests' route or routes under that segment by checking for location.pathname
=== '/requests' || location.pathname.startsWith('/requests/'); use the same
check wherever isActive or the pathname prefix check appears to ensure correct
boundary matching.
In `@web/src/components/layout/app-sidebar/sidebar-renderer.tsx`:
- Around line 27-30: The current isActive calculation uses
location.pathname.startsWith(item.to), which misidentifies routes like
"/requests-foo" as matching "/requests"; update the logic in the isActive
computation (referencing isActive, item.activeMatch, location.pathname, item.to)
to enforce a path boundary: when activeMatch !== 'exact' treat a match as true
only if location.pathname === item.to or location.pathname starts with item.to +
'/' (or use a regex that checks for a segment boundary after item.to), so that
only same path or nested paths match and prefixes like "/requests-foo" do not.
- Around line 51-53: 在 sidebar-renderer.tsx 中处理 'dynamic-section' 的分支不要用 <div>
包裹动态内容(会破坏 SidebarMenu 的 <ul> 语义),将该分支改为用 React.Fragment 包裹并保留 key(即在处理
'dynamic-section' 时返回一个带 key 的 Fragment,内部直接渲染 item.generator()),以保证生成的直接子元素不是
<div> 而是符合 <ul> 语义的片段。
In `@web/src/components/layout/index.ts`:
- Around line 2-7: Remove the unnecessary public exports SidebarRenderer,
RequestsNavItem, and ClientRoutesItems from the module's public index (the
export list that currently re-exports SidebarRenderer, RequestsNavItem, and
ClientRoutesItems), leaving only SidebarNav, PageHeader, and NavProxyStatus; if
any of those three were intended to be part of the public API, instead add
documentation or usage examples for SidebarRenderer, RequestsNavItem, and
ClientRoutesItems rather than exporting them from the top-level layout index.
In `@web/src/components/ui/input-group.tsx`:
- Around line 53-58: The click handler currently focuses only an input by using
e.currentTarget.parentElement?.querySelector('input'), so textarea or other
controls with data-slot won't get focus; update the selector to look for the
group control element (e.g.
e.currentTarget.parentElement?.querySelector('[data-slot="input-group-control"]'))
and call .focus() on that element if present (ensure the element is focusable),
keeping the early return when the click is on a button; locate this change in
the onClick handler inside the InputGroup component in input-group.tsx.
In `@web/src/pages/api-tokens/index.tsx`:
- Around line 289-294: The copy button in the tokens table is attempting to copy
token.token but existing tokens returned by getAPITokens() do not include the
plaintext token (per APITokenCreateResult), only tokenPrefix is present; update
the UI in web/src/pages/api-tokens/index.tsx to remove or render the table-row
Copy Button disabled (the onClick handler that calls
navigator.clipboard.writeText(token.token) should be removed/disabled) and
ensure the only functional copy action remains in the token creation dialog
where the full plaintext token is available and already handled.
In `@web/src/pages/providers/components/antigravity-token-import.tsx`:
- Around line 80-116: The interval callback in handleOAuth closes over the stale
oauthStatus value; change the closure to use functional state updates so it
reads the latest status when the popup closes. Specifically, inside the
setInterval callback (where oauthWindowRef.current?.closed is checked), replace
any direct checks of oauthStatus with a functional updater on setOAuthStatus
(and inside that updater, if prev === 'waiting' transition to 'idle' and call
setOAuthState(null)); retain clearInterval(checkWindowClosed) as before and keep
oauthWindowRef usage unchanged.
In `@web/src/pages/providers/components/model-mapping-editor.tsx`:
- Around line 48-55: Trim and validate keys in handleUpdate: compute const
trimmedNew = newKey.trim() and const trimmedOld = oldKey.trim(), and if
trimmedNew is empty or equals trimmedOld just update newValue[trimmedOld] =
newVal (do not delete/rename); if trimmedNew !== trimmedOld and value
hasOwnProperty(trimmedNew) (i.e. duplicate key exists) do not perform the rename
— instead update newValue[trimmedOld] = newVal to avoid overwriting; only when
trimmedNew is non-empty and not a duplicate perform the rename by deleting
newValue[trimmedOld] and setting newValue[trimmedNew] = newVal, then call
onChange(newValue).
In `@web/src/pages/providers/components/provider-create-flow.tsx`:
- Line 220: Remove the stray text node "asdadas" that was accidentally left
inside the JSX of the ProviderCreateFlow component in provider-create-flow.tsx;
locate the JSX return (or render) block in the ProviderCreateFlow
function/component and delete the literal text node so no unexpected text is
rendered in the UI, and run the component to confirm no other leftover debug
strings remain.
In `@web/src/pages/providers/components/select-type-step.tsx`:
- Around line 104-110: 当前 SelectTypeStep 组件中 h3 显示的 "Kiro (Q Developer)" 和 p 显示的
"AWS CodeWhisperer / Q Developer" 是硬编码字符串,需与文件中其余使用的 t(...) 国际化方式保持一致;在
SelectTypeStep 的对应 JSX(含 className="text-headline font-semibold text-foreground
mb-1" 的 h3 和下方 p)将这两处文本替换为调用现有 i18n t(...),使用语义化的翻译 key(例如 provider.kiro.title /
provider.kiro.description 或与项目翻译命名一致的 key),并在项目的语言资源文件中添加这些 key 的翻译条目。 Ensure to
import/use the same t function/hooks already used in the file so runtime
behavior remains unchanged.
In `@web/src/pages/requests/detail/RequestDetailPanel.tsx`:
- Around line 38-45: The formatJSON function incorrectly treats all falsy values
as missing; change the initial check to only treat null/undefined as missing
(e.g., use obj == null or obj === null || obj === undefined) so numeric 0,
false, and empty string are preserved and then keep the existing
try/catch/JSON.stringify behavior to format the value or fall back to
String(obj).
🧹 Nitpick comments (17)
web/src/pages/projects/tabs/overview.tsx (1)
1-154: 可选建议:考虑使用自动化格式工具。所有更改都是格式调整,没有功能性影响。为了在整个代码库中保持一致的代码风格,可以考虑配置 Prettier 或 ESLint 的格式化规则,以自动化处理此类格式调整。
web/src/pages/sessions.tsx (1)
38-38: 可选优化:考虑使用useMemo缓存projectMap当前实现每次渲染都会创建新的 Map 对象。虽然对于小数组影响不大,但可以使用
useMemo进行优化。♻️ 建议的优化
+import { useState, useEffect, useMemo } from 'react'; -import { useState, useEffect } from 'react';- const projectMap = new Map(projects?.map((p) => [p.id, p.name]) ?? []); + const projectMap = useMemo( + () => new Map(projects?.map((p) => [p.id, p.name]) ?? []), + [projects] + );web/src/pages/retry-configs/index.tsx (1)
54-70: 建议去掉重复的refetch(),避免双重请求
useUpdateRetryConfig/useCreateRetryConfig的onSuccess已经invalidateQueries,对当前页面活跃查询通常会触发 refetch;这里再手动refetch()可能导致重复请求。
请确认你们当前的 React Query 版本里invalidateQueries对活跃查询的行为后再精简。♻️ 建议改动
onSuccess: () => { setHasChanges(false); - refetch(); }, }, ); } else { // Create if doesn't exist createConfig.mutate(data, { onSuccess: () => { setHasChanges(false); - refetch(); }, }); }web/src/pages/providers/components/provider-edit-flow.tsx (1)
174-177: 建议统一文案走翻译资源。这些硬编码文案(如编辑标题、删除、错误提示)如果产品已启用多语言,建议统一使用翻译 key,避免后续 i18n 覆盖不完整。
Also applies to: 181-184, 277-277, 312-316, 321-325
web/src/pages/routing-strategies/index.tsx (1)
107-131: 建议为表单标签补充 htmlFor/id 绑定以提升可访问性
当前 label 与 select 未建立显式关联,屏幕阅读器与点击标签聚焦体验会受影响。♿️ 建议修改
- <label className="mb-1 block text-sm font-medium">Project</label> + <label htmlFor="routing-project" className="mb-1 block text-sm font-medium"> + Project + </label> <select + id="routing-project" value={projectID} onChange={(e) => setProjectID(e.target.value)} className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus:border-ring focus:ring-2 focus:ring-ring/50 outline-none" > ... - <label className="mb-1 block text-sm font-medium">Type</label> + <label htmlFor="routing-type" className="mb-1 block text-sm font-medium"> + Type + </label> <select + id="routing-type" value={type} onChange={(e) => setType(e.target.value as RoutingStrategyType)} className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus:border-ring focus:ring-2 focus:ring-ring/50 outline-none" >web/src/pages/model-mappings/index.tsx (1)
152-176: 拖拽排序更新可并行化以降低等待Line 152-176 当前逐条
await更新,规则较多时会明显拖慢完成时间。若后端允许并发或提供批量接口,可考虑并行化更新以提升响应。♻️ 可选优化示例
- for (let i = 0; i < reordered.length; i++) { - const rule = reordered[i]; - if (rule.priority !== i * 10) { - await updateMapping.mutateAsync({ - id: rule.id, - data: { - pattern: rule.pattern, - target: rule.target, - priority: i * 10, - isEnabled: rule.isEnabled, - }, - }); - } - } + const updates = reordered + .map((rule, i) => ({ rule, priority: i * 10 })) + .filter(({ rule, priority }) => rule.priority !== priority); + + await Promise.all( + updates.map(({ rule, priority }) => + updateMapping.mutateAsync({ + id: rule.id, + data: { + pattern: rule.pattern, + target: rule.target, + priority, + isEnabled: rule.isEnabled, + }, + }), + ), + );web/src/components/theme-provider.tsx (2)
72-78: useTheme Hook 的错误检查可以更精确当前检查
context === undefined,但由于initialState提供了默认值,context不会是undefined。如果组件在 Provider 外部使用,将返回initialState而不是抛出错误。如果需要严格检查,建议使用不同的哨兵值或
null作为默认 context。
31-33: 此组件为客户端应用,SSR 问题不适用该文件位于 React SPA(单页应用)中,使用
createRoot()在客户端挂载,不存在服务端渲染环境。虽然没有'use client'指令,但当前架构中 localStorage 访问不会导致错误。然而,添加防护检查仍是良好实践,若此组件未来在 Next.js SSR 应用中使用,将需要该保护:
🔧 建议的修复方案(可选)
const [theme, setTheme] = useState<Theme>( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + () => { + if (typeof window === 'undefined') return defaultTheme; + return (localStorage.getItem(storageKey) as Theme) || defaultTheme; + }, );web/src/components/theme-toggle.tsx (2)
26-28: 建议使用国际化字符串这里的主题选项使用了硬编码的英文字符串("Light"、"Dark"、"System"),而项目中其他组件(如
api-tokens/index.tsx)使用了useTranslation进行国际化。建议保持一致性:
♻️ 建议的重构方案
+import { useTranslation } from 'react-i18next'; export function ThemeToggle() { + const { t } = useTranslation(); const { setTheme } = useTheme(); return ( <DropdownMenu> {/* ... */} <DropdownMenuContent align="end"> - <DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem> - <DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem> - <DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem> + <DropdownMenuItem onClick={() => setTheme('light')}>{t('theme.light')}</DropdownMenuItem> + <DropdownMenuItem onClick={() => setTheme('dark')}>{t('theme.dark')}</DropdownMenuItem> + <DropdownMenuItem onClick={() => setTheme('system')}>{t('theme.system')}</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ); }
21-21: 屏幕阅读器文本也应国际化
<span className="sr-only">Toggle theme</span>中的文本同样应使用翻译函数,以确保可访问性功能在所有语言环境下正常工作。web/src/pages/requests/detail/RequestHeader.tsx (3)
24-33: 日期格式化使用了硬编码的 locale
formatTime函数硬编码使用'en-US'locale,而api-tokens/index.tsx中使用了i18n.resolvedLanguage来动态获取用户语言设置。建议保持一致性,接受 locale 参数或使用 i18n hook:
♻️ 建议的重构方案
-function formatTime(timestamp: string): string { +function formatTime(timestamp: string, locale?: string): string { const date = new Date(timestamp); - return date.toLocaleString('en-US', { + return date.toLocaleString(locale ?? 'en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', }); }
92-144: 统计标签未国际化"Duration"、"Input"、"Output"、"Cache Read"、"Cache Write"、"Cost" 等标签都是硬编码的英文字符串。如果项目需要支持多语言,建议使用翻译函数。
14-22: 成本格式化使用了硬编码的美元符号
formatCost函数使用硬编码的$符号,如果需要支持其他货币或地区格式,建议使用Intl.NumberFormat进行货币格式化。web/src/types/sidebar.ts (1)
1-31: 建议显式引入 React 类型,避免依赖全局命名空间。当前文件使用
React.ComponentType/React.ReactNode但未显式引入 React 类型;若项目未启用全局 React 命名空间或有 ESLintno-undef规则,会报错。建议改为 type-only import。♻️ 建议修改
-import type { LucideIcon } from 'lucide-react'; +import type { ComponentType, ReactNode } from 'react'; +import type { LucideIcon } from 'lucide-react'; @@ export interface CustomMenuItem { type: 'custom'; key: string; - component: React.ComponentType; + component: ComponentType; } @@ export interface DynamicSectionMenuItem { type: 'dynamic-section'; key: string; - generator: () => React.ReactNode; + generator: () => ReactNode; }web/src/pages/settings/index.tsx (1)
219-227: 可选优化:考虑对超时输入添加防抖。
handleTimeoutChange在每次输入变化时直接调用 API,可能会导致频繁的网络请求。可以考虑像DataRetentionSection一样使用草稿状态模式,或添加防抖处理。♻️ 建议的防抖实现
// 使用 useDebouncedCallback 或类似方案 const handleTimeoutChange = useDebouncedCallback(async (value: string) => { const numValue = parseInt(value, 10); if (numValue >= 5 && numValue <= 300) { await updateSetting.mutateAsync({ key: 'force_project_timeout', value: value, }); } }, 500);web/src/pages/routes/index.tsx (1)
48-52: 考虑使用自定义确认对话框替代confirm()原生
confirm()可以正常工作,但与应用的整体 UI 风格不一致。可以考虑在后续迭代中使用自定义的确认对话框组件以保持界面一致性。web/src/pages/requests/detail/RequestDetailView.tsx (1)
135-158: 提取 JSON 解析逻辑以减少重复与双重解析
Line 135-158 和 Line 217-240:同一段 body 被解析两次(CopyButton + 展示),建议提取 helper,避免重复 try/catch 和多次JSON.parse。♻️ 建议重构
export function RequestDetailView({ request, activeTab, setActiveTab, formatJSON, formatCost, projectName, sessionInfo, projectMap, tokenName, }: RequestDetailViewProps) { const { t } = useTranslation(); + const formatBody = (raw?: string) => { + if (!raw) return ''; + try { + return formatJSON(JSON.parse(raw)); + } catch { + return raw; + } + }; return ( ... - content={(() => { - try { - return formatJSON(JSON.parse(request.requestInfo.body)); - } catch { - return request.requestInfo.body; - } - })()} + content={formatBody(request.requestInfo.body)} ... - {(() => { - try { - return formatJSON(JSON.parse(request.requestInfo.body)); - } catch { - return request.requestInfo.body; - } - })()} + {formatBody(request.requestInfo.body)} ... - content={(() => { - try { - return formatJSON(JSON.parse(request.responseInfo.body)); - } catch { - return request.responseInfo.body; - } - })()} + content={formatBody(request.responseInfo.body)} ... - {(() => { - try { - return formatJSON(JSON.parse(request.responseInfo.body)); - } catch { - return request.responseInfo.body; - } - })()} + {formatBody(request.responseInfo.body)}Also applies to: 217-240
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (115)
web/.prettierrc.jsonweb/src/components/cooldown-details-dialog.tsxweb/src/components/force-project-dialog.tsxweb/src/components/layout/app-layout.tsxweb/src/components/layout/app-sidebar/client-routes-items.tsxweb/src/components/layout/app-sidebar/index.tsxweb/src/components/layout/app-sidebar/requests-nav-item.tsxweb/src/components/layout/app-sidebar/sidebar-config.tsxweb/src/components/layout/app-sidebar/sidebar-renderer.tsxweb/src/components/layout/header.tsxweb/src/components/layout/index.tsweb/src/components/layout/nav-main.tsxweb/src/components/layout/nav-management.tsxweb/src/components/layout/nav-proxy-status.tsxweb/src/components/layout/page-header.tsxweb/src/components/layout/sidebar-nav.tsxweb/src/components/provider-details-dialog.tsxweb/src/components/routes/ClientTypeRoutesContent.tsxweb/src/components/theme-provider.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/alert-dialog.tsxweb/src/components/ui/badge.tsxweb/src/components/ui/button.tsxweb/src/components/ui/card.tsxweb/src/components/ui/combobox.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/dropdown-menu.tsxweb/src/components/ui/field.tsxweb/src/components/ui/index.tsweb/src/components/ui/input-group.tsxweb/src/components/ui/input.tsxweb/src/components/ui/label.tsxweb/src/components/ui/model-input.tsxweb/src/components/ui/resizable.tsxweb/src/components/ui/select.tsxweb/src/components/ui/separator.tsxweb/src/components/ui/sheet.tsxweb/src/components/ui/sidebar.tsxweb/src/components/ui/skeleton.tsxweb/src/components/ui/streaming-badge.tsxweb/src/components/ui/switch.tsxweb/src/components/ui/table.tsxweb/src/components/ui/tabs.tsxweb/src/components/ui/textarea.tsxweb/src/components/ui/tooltip.tsxweb/src/hooks/queries/index.tsweb/src/hooks/queries/use-proxy.tsweb/src/hooks/queries/use-requests.tsweb/src/hooks/queries/use-routes.tsweb/src/hooks/queries/use-routing-strategies.tsweb/src/hooks/queries/use-settings.tsweb/src/hooks/use-cooldowns.tsweb/src/hooks/use-mobile.tsxweb/src/hooks/use-pending-session.tsweb/src/hooks/use-streaming.tsweb/src/index.cssweb/src/lib/i18n.tsweb/src/lib/theme.tsweb/src/lib/transport/context.tsxweb/src/lib/transport/factory.tsweb/src/lib/transport/http-transport.tsweb/src/lib/transport/index.tsweb/src/lib/transport/interface.tsweb/src/lib/transport/types.tsweb/src/lib/utils.tsweb/src/locales/en.jsonweb/src/locales/zh.jsonweb/src/main.tsxweb/src/pages/api-tokens/index.tsxweb/src/pages/client-routes/components/provider-row.tsxweb/src/pages/client-routes/index.tsxweb/src/pages/console/index.tsxweb/src/pages/login.tsxweb/src/pages/model-mappings/index.tsxweb/src/pages/overview.tsxweb/src/pages/projects/detail.tsxweb/src/pages/projects/index.tsxweb/src/pages/projects/tabs/overview.tsxweb/src/pages/projects/tabs/requests.tsxweb/src/pages/projects/tabs/routes.tsxweb/src/pages/projects/tabs/sessions.tsxweb/src/pages/providers/components/antigravity-coming-soon.tsxweb/src/pages/providers/components/antigravity-provider-view.tsxweb/src/pages/providers/components/antigravity-token-import.tsxweb/src/pages/providers/components/clients-config-section.tsxweb/src/pages/providers/components/kiro-provider-view.tsxweb/src/pages/providers/components/kiro-token-import.tsxweb/src/pages/providers/components/model-mapping-editor.tsxweb/src/pages/providers/components/provider-card.tsxweb/src/pages/providers/components/provider-create-flow.tsxweb/src/pages/providers/components/provider-edit-flow.tsxweb/src/pages/providers/components/provider-row.tsxweb/src/pages/providers/components/select-type-step.tsxweb/src/pages/providers/index.tsxweb/src/pages/providers/types.tsweb/src/pages/requests/detail.tsxweb/src/pages/requests/detail/RequestDetailPanel.tsxweb/src/pages/requests/detail/RequestDetailView.tsxweb/src/pages/requests/detail/RequestHeader.tsxweb/src/pages/requests/detail/RequestSidebar.tsxweb/src/pages/requests/detail/components/CopyAsCurlButton.tsxweb/src/pages/requests/detail/components/CopyButton.tsxweb/src/pages/requests/detail/components/DiffButton.tsxweb/src/pages/requests/detail/components/DiffModal.tsxweb/src/pages/requests/detail/components/EmptyState.tsxweb/src/pages/requests/detail/components/index.tsweb/src/pages/requests/index.tsxweb/src/pages/retry-configs/index.tsxweb/src/pages/routes/form.tsxweb/src/pages/routes/index.tsxweb/src/pages/routing-strategies/index.tsxweb/src/pages/sessions.tsxweb/src/pages/settings/index.tsxweb/src/pages/stats/index.tsxweb/src/types/sidebar.ts
💤 Files with no reviewable changes (3)
- web/src/components/layout/nav-management.tsx
- web/src/components/layout/sidebar-nav.tsx
- web/src/components/layout/nav-main.tsx
🧰 Additional context used
🧬 Code graph analysis (67)
web/src/lib/utils.ts (1)
launcher/script.js (1)
seconds(121-121)
web/src/components/layout/app-sidebar/sidebar-config.tsx (1)
web/src/types/sidebar.ts (1)
SidebarConfig(50-52)
web/src/pages/requests/detail/components/DiffButton.tsx (1)
web/src/pages/requests/detail/components/index.ts (1)
DiffButton(4-4)
web/src/lib/transport/interface.ts (2)
web/src/lib/transport/types.ts (2)
Session(78-85)AntigravityQuotaData(335-340)internal/domain/model.go (1)
Session(113-126)
web/src/pages/projects/tabs/sessions.tsx (1)
web/src/components/ui/index.ts (1)
TableCell(15-15)
web/src/hooks/queries/use-routing-strategies.ts (2)
web/src/lib/query-client.ts (1)
queryClient(3-14)web/src/hooks/queries/index.ts (1)
routingStrategyKeys(62-62)
web/src/pages/requests/detail/components/CopyButton.tsx (1)
web/src/pages/requests/detail/components/index.ts (1)
CopyButton(1-1)
web/src/pages/providers/components/select-type-step.tsx (1)
web/src/pages/providers/types.ts (3)
ProviderFormData(165-172)PROVIDER_TYPE_CONFIGS(27-60)quickTemplates(90-147)
web/src/components/layout/app-sidebar/requests-nav-item.tsx (4)
web/src/hooks/use-streaming.ts (1)
useStreamingRequests(29-114)web/src/components/ui/sidebar.tsx (3)
SidebarMenuItem(688-688)SidebarMenuButton(687-687)SidebarMenuBadge(686-686)web/src/components/ui/streaming-badge.tsx (1)
StreamingBadge(27-81)web/src/components/layout/sidebar-nav.tsx (1)
RequestsNavItem(57-89)
web/src/pages/routes/form.tsx (6)
web/src/lib/transport/types.ts (2)
Route(89-101)ClientType(8-8)web/src/lib/transport/http-transport.ts (2)
createRoute(163-166)updateRoute(168-171)web/src/hooks/queries/use-routes.ts (2)
useCreateRoute(35-44)useUpdateRoute(47-58)web/src/hooks/queries/use-providers.ts (1)
useProviders(20-25)web/src/hooks/queries/use-projects.ts (1)
useProjects(20-25)web/src/lib/theme.ts (1)
ClientType(25-25)
web/src/pages/projects/index.tsx (5)
web/src/hooks/queries/index.ts (2)
useProjects(22-22)useCreateProject(25-25)web/src/hooks/queries/use-projects.ts (2)
useProjects(20-25)useCreateProject(46-55)web/src/lib/transport/http-transport.ts (1)
createProject(137-140)web/src/components/ui/button.tsx (1)
Button(58-58)web/src/components/ui/index.ts (1)
Button(2-2)
web/src/components/ui/input.tsx (2)
web/src/components/ui/index.ts (1)
Input(23-23)web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/force-project-dialog.tsx (5)
web/src/lib/transport/types.ts (1)
NewSessionPendingEvent(284-288)web/src/lib/transport/http-transport.ts (2)
updateSessionProject(188-197)rejectSession(199-204)web/src/hooks/queries/use-sessions.ts (2)
useUpdateSessionProject(24-37)useRejectSession(40-49)web/src/components/icons/client-icons.tsx (1)
getClientColor(44-46)web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/console/index.tsx (4)
web/src/lib/transport/factory.ts (1)
getTransport(79-102)web/src/lib/transport/index.ts (1)
getTransport(78-78)web/src/pages/requests/detail/components/EmptyState.tsx (1)
EmptyState(8-15)web/src/pages/requests/detail/components/index.ts (1)
EmptyState(5-5)
web/src/components/ui/resizable.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/ui/alert-dialog.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/model-mappings/index.tsx (4)
web/src/lib/transport/types.ts (2)
ModelMapping(369-384)ModelMappingInput(387-398)internal/domain/model.go (1)
ModelMapping(492-517)web/src/components/ui/model-input.tsx (1)
ModelInput(161-411)web/src/hooks/queries/index.ts (6)
useModelMappings(90-90)useCreateModelMapping(91-91)useUpdateModelMapping(92-92)useDeleteModelMapping(93-93)useClearAllModelMappings(94-94)useResetModelMappingsToDefaults(95-95)
web/src/components/layout/app-sidebar/index.tsx (2)
web/src/components/layout/app-sidebar/sidebar-config.tsx (1)
sidebarConfig(21-128)web/src/components/theme-toggle.tsx (1)
ThemeToggle(11-32)
web/src/components/layout/app-sidebar/sidebar-renderer.tsx (2)
web/src/types/sidebar.ts (2)
SidebarConfig(50-52)MenuItem(36-36)web/src/components/ui/sidebar.tsx (6)
SidebarMenuItem(688-688)SidebarMenuButton(687-687)SidebarGroup(677-677)SidebarGroupLabel(680-680)SidebarGroupContent(679-679)SidebarMenu(684-684)
web/src/components/ui/streaming-badge.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/projects/tabs/requests.tsx (2)
web/src/lib/transport/types.ts (1)
Project(63-70)internal/domain/model.go (1)
Project(98-111)
web/src/pages/requests/detail.tsx (7)
web/src/hooks/queries/index.ts (8)
useProxyRequest(75-75)useProxyUpstreamAttempts(76-76)useProviders(8-8)useProjects(22-22)useSessions(45-45)useRoutes(33-33)useAPITokens(101-101)useProxyRequestUpdates(77-77)web/src/hooks/queries/use-requests.ts (3)
useProxyRequest(42-48)useProxyUpstreamAttempts(51-57)useProxyRequestUpdates(60-130)web/src/hooks/queries/use-providers.ts (1)
useProviders(20-25)web/src/hooks/queries/use-projects.ts (1)
useProjects(20-25)web/src/hooks/queries/use-sessions.ts (1)
useSessions(16-21)web/src/hooks/queries/use-routes.ts (1)
useRoutes(18-23)web/src/hooks/queries/use-api-tokens.ts (1)
useAPITokens(18-23)
web/src/components/ui/skeleton.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/hooks/queries/use-settings.ts (2)
web/src/lib/transport/types.ts (1)
ModelMappingInput(387-398)web/src/lib/transport/factory.ts (1)
getTransport(79-102)
web/src/components/layout/app-layout.tsx (4)
web/src/components/layout/index.ts (1)
AppLayout(1-1)web/src/hooks/use-pending-session.ts (1)
usePendingSession(9-49)web/src/hooks/queries/use-settings.ts (1)
useSettings(15-20)web/src/components/ui/sidebar.tsx (1)
SidebarProvider(693-693)
web/src/pages/stats/index.tsx (1)
web/src/components/ui/tooltip.tsx (1)
Tooltip(56-56)
web/src/pages/requests/detail/components/CopyAsCurlButton.tsx (7)
web/src/lib/transport/index.ts (1)
RequestInfo(30-30)web/src/lib/transport/types.ts (1)
RequestInfo(147-152)internal/domain/model.go (1)
RequestInfo(161-166)web/src/pages/requests/detail/components/index.ts (1)
CopyAsCurlButton(2-2)web/src/hooks/queries/index.ts (1)
useSetting(87-87)web/src/hooks/queries/use-settings.ts (1)
useSetting(22-28)web/src/components/ui/index.ts (1)
Button(2-2)
web/src/components/ui/card.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/settings/index.tsx (3)
web/src/components/theme-provider.tsx (1)
useTheme(72-78)web/src/hooks/queries/use-settings.ts (2)
useSettings(15-20)useUpdateSetting(30-40)web/src/lib/transport/http-transport.ts (1)
updateSetting(323-328)
web/src/components/routes/ClientTypeRoutesContent.tsx (6)
web/src/lib/theme.ts (4)
ClientType(25-25)getClientColor(198-200)getProviderColor(153-155)ProviderType(9-20)web/src/lib/transport/index.ts (2)
ClientType(8-8)Provider(9-9)web/src/lib/transport/types.ts (2)
ClientType(8-8)Provider(43-51)web/src/hooks/queries/use-routes.ts (6)
useRoutes(18-23)useCreateRoute(35-44)useToggleRoute(73-88)useDeleteRoute(61-70)useUpdateRoutePositions(91-108)routeKeys(9-15)web/src/hooks/use-streaming.ts (1)
useStreamingRequests(29-114)web/src/pages/client-routes/types.ts (1)
ProviderConfigItem(4-10)
web/src/pages/projects/detail.tsx (2)
web/src/lib/query-client.ts (1)
queryClient(3-14)web/src/lib/transport/http-transport.ts (1)
deleteProject(147-149)
web/src/components/ui/textarea.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/lib/transport/http-transport.ts (4)
web/src/lib/transport/types.ts (11)
Session(78-85)RoutingStrategy(134-141)CursorPaginationParams(248-254)CursorPaginationResult(257-264)ProxyRequest(168-202)ProxyUpstreamAttempt(213-238)ProviderStats(307-319)AntigravityBatchValidationResult(350-352)AntigravityQuotaData(335-340)KiroQuotaData(418-429)AuthVerifyResult(474-478)internal/domain/model.go (5)
Session(113-126)RoutingStrategy(329-342)ProxyRequest(174-236)ProxyUpstreamAttempt(238-285)ProviderStats(399-419)internal/service/admin.go (1)
CursorPaginationResult(353-358)internal/adapter/provider/kiro/service.go (1)
KiroQuotaData(32-43)
web/src/components/theme-toggle.tsx (2)
web/src/components/theme-provider.tsx (1)
useTheme(72-78)web/src/components/ui/dropdown-menu.tsx (3)
DropdownMenuItem(235-235)DropdownMenuContent(232-232)DropdownMenu(229-229)
web/src/components/ui/label.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/ui/combobox.tsx (2)
web/src/components/ui/input-group.tsx (2)
InputGroup(138-138)InputGroupInput(142-142)web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/layout/header.tsx (3)
web/src/components/theme-provider.tsx (1)
useTheme(72-78)web/src/components/ui/button.tsx (1)
Button(58-58)web/src/components/ui/index.ts (1)
Button(2-2)
web/src/components/ui/badge.tsx (1)
web/src/components/ui/index.ts (1)
badgeVariants(20-20)
web/src/components/layout/app-sidebar/client-routes-items.tsx (2)
web/src/hooks/use-streaming.ts (1)
useStreamingRequests(29-114)web/src/components/icons/client-icons.tsx (2)
getClientName(51-53)allClientTypes(105-105)
web/src/pages/requests/detail/components/DiffModal.tsx (1)
web/src/pages/requests/detail/components/index.ts (1)
DiffModal(3-3)
web/src/hooks/queries/use-requests.ts (1)
web/src/lib/query-client.ts (1)
queryClient(3-14)
web/src/pages/retry-configs/index.tsx (2)
web/src/hooks/queries/use-retry-configs.ts (3)
useRetryConfigs(18-23)useUpdateRetryConfig(47-58)useCreateRetryConfig(35-44)web/src/lib/transport/types.ts (1)
RetryConfig(112-122)
web/src/pages/providers/components/antigravity-provider-view.tsx (3)
web/src/lib/transport/index.ts (4)
Provider(9-9)AntigravityModelQuota(45-45)AntigravityQuotaData(46-46)getTransport(78-78)web/src/lib/transport/types.ts (3)
Provider(43-51)AntigravityModelQuota(329-333)AntigravityQuotaData(335-340)web/src/lib/transport/factory.ts (1)
getTransport(79-102)
web/src/components/ui/tabs.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/projects/tabs/overview.tsx (4)
web/src/lib/transport/http-transport.ts (1)
updateProject(142-145)web/src/lib/query-client.ts (1)
queryClient(3-14)web/src/hooks/queries/index.ts (1)
projectKeys(21-21)web/src/hooks/queries/use-projects.ts (1)
projectKeys(9-17)
web/src/pages/providers/components/provider-create-flow.tsx (6)
web/src/pages/providers/types.ts (1)
CreateStep(175-175)web/src/hooks/queries/use-providers.ts (1)
useCreateProvider(37-47)web/src/lib/theme.ts (1)
ClientType(25-25)web/src/lib/transport/index.ts (2)
ClientType(8-8)CreateProviderData(13-13)web/src/lib/transport/types.ts (2)
ClientType(8-8)CreateProviderData(54-59)web/src/pages/providers/components/clients-config-section.tsx (1)
ClientsConfigSection(13-67)
web/src/pages/providers/components/clients-config-section.tsx (4)
web/src/components/icons/client-icons.tsx (1)
ClientIcon(65-100)web/src/components/ui/index.ts (2)
Switch(43-43)Input(23-23)web/src/components/ui/switch.tsx (1)
Switch(30-30)web/src/components/ui/input.tsx (1)
Input(20-20)
web/src/components/ui/switch.tsx (2)
web/src/components/ui/index.ts (1)
Switch(43-43)web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/providers/components/provider-row.tsx (4)
web/src/lib/transport/index.ts (4)
Provider(9-9)ProviderStats(32-32)AntigravityQuotaData(46-46)KiroQuotaData(55-55)web/src/lib/transport/types.ts (4)
Provider(43-51)ProviderStats(307-319)AntigravityQuotaData(335-340)KiroQuotaData(418-429)web/src/pages/providers/types.ts (1)
getProviderTypeConfig(63-65)web/src/hooks/queries/use-providers.ts (2)
useAntigravityQuota(99-108)useKiroQuota(111-120)
web/src/components/ui/dropdown-menu.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/hooks/queries/use-proxy.ts (1)
web/src/hooks/queries/index.ts (1)
proxyKeys(81-81)
web/src/pages/projects/tabs/routes.tsx (8)
web/src/lib/transport/types.ts (3)
ClientType(8-8)Project(63-70)Route(89-101)web/src/components/icons/client-icons.tsx (3)
ClientIcon(65-100)getClientName(51-53)getClientColor(44-46)web/src/hooks/queries/index.ts (3)
useRoutes(33-33)useUpdateProject(26-26)projectKeys(21-21)web/src/hooks/queries/use-routes.ts (1)
useRoutes(18-23)web/src/hooks/use-streaming.ts (1)
useStreamingRequests(29-114)web/src/lib/transport/http-transport.ts (1)
updateProject(142-145)web/src/hooks/queries/use-projects.ts (2)
useUpdateProject(58-69)projectKeys(9-17)web/src/components/ui/streaming-badge.tsx (1)
StreamingBadge(27-81)
web/src/pages/routes/index.tsx (5)
web/src/hooks/queries/use-routes.ts (2)
useRoutes(18-23)useDeleteRoute(61-70)web/src/hooks/queries/use-providers.ts (1)
useProviders(20-25)web/src/lib/transport/http-transport.ts (1)
deleteRoute(173-175)web/src/lib/transport/types.ts (1)
Route(89-101)web/src/pages/routes/form.tsx (1)
RouteForm(15-168)
web/src/pages/providers/components/kiro-provider-view.tsx (5)
web/src/lib/transport/index.ts (3)
Provider(9-9)KiroQuotaData(55-55)getTransport(78-78)web/src/lib/transport/types.ts (2)
Provider(43-51)KiroQuotaData(418-429)internal/domain/model.go (1)
Provider(76-96)internal/adapter/provider/kiro/service.go (1)
KiroQuotaData(32-43)web/src/components/icons/client-icons.tsx (1)
ClientIcon(65-100)
web/src/components/ui/dialog.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/ui/input-group.tsx (4)
web/src/lib/utils.ts (1)
cn(8-10)web/src/components/ui/button.tsx (1)
Button(58-58)web/src/components/ui/input.tsx (1)
Input(20-20)web/src/components/ui/textarea.tsx (1)
Textarea(18-18)
web/src/components/ui/model-input.tsx (4)
web/src/lib/transport/index.ts (1)
Provider(9-9)web/src/lib/transport/types.ts (1)
Provider(43-51)internal/domain/model.go (1)
Provider(76-96)web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/providers/components/kiro-token-import.tsx (3)
web/src/lib/transport/index.ts (3)
CreateProviderData(13-13)KiroTokenValidationResult(54-54)getTransport(78-78)web/src/lib/transport/types.ts (2)
CreateProviderData(54-59)KiroTokenValidationResult(402-416)internal/adapter/provider/kiro/service.go (1)
KiroTokenValidationResult(15-29)
web/src/components/cooldown-details-dialog.tsx (3)
web/src/lib/transport/index.ts (1)
Cooldown(59-59)web/src/lib/transport/types.ts (1)
Cooldown(458-466)web/src/hooks/use-cooldowns.ts (1)
useCooldowns(6-122)
web/src/pages/providers/components/model-mapping-editor.tsx (1)
web/src/components/ui/model-input.tsx (1)
ModelInput(161-411)
web/src/components/ui/separator.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/providers/components/provider-card.tsx (4)
web/src/lib/transport/index.ts (1)
Provider(9-9)web/src/lib/transport/types.ts (1)
Provider(43-51)web/src/hooks/use-cooldowns.ts (1)
useCooldowns(6-122)web/src/lib/transport/http-transport.ts (1)
clearCooldown(439-441)
web/src/components/ui/field.tsx (2)
web/src/lib/utils.ts (1)
cn(8-10)web/src/components/ui/label.tsx (1)
Label(18-18)
web/src/pages/sessions.tsx (4)
web/src/hooks/queries/use-sessions.ts (2)
useSessions(16-21)useUpdateSessionProject(24-37)web/src/lib/transport/types.ts (1)
Session(78-85)web/src/lib/transport/http-transport.ts (1)
updateSessionProject(188-197)web/src/lib/utils.ts (1)
cn(8-10)
web/src/pages/client-routes/components/provider-row.tsx (7)
web/src/pages/client-routes/types.ts (1)
ProviderConfigItem(4-10)web/src/lib/theme.ts (3)
ClientType(25-25)getProviderColorVar(139-141)ProviderType(9-20)web/src/lib/transport/index.ts (1)
ClientType(8-8)web/src/lib/transport/types.ts (1)
ClientType(8-8)web/src/hooks/use-cooldowns.ts (1)
useCooldowns(6-122)web/src/hooks/queries/use-providers.ts (1)
useAntigravityQuota(99-108)web/src/lib/utils.ts (1)
cn(8-10)
web/src/components/ui/sheet.tsx (1)
web/src/lib/utils.ts (1)
cn(8-10)
web/src/lib/transport/types.ts (1)
internal/domain/model.go (1)
Provider(76-96)
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const formatUntilTime = (until: string) => { | ||
| const date = new Date(until) | ||
| const date = new Date(until); | ||
| return date.toLocaleString(i18n.resolvedLanguage ?? i18n.language, { | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| hour: '2-digit', | ||
| minute: '2-digit', | ||
| second: '2-digit', | ||
| hour12: false, | ||
| }) | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| const untilDateStr = formatUntilTime(cooldown.untilTime) | ||
| const [datePart, timePart] = untilDateStr.split(' ') | ||
| const untilDateStr = formatUntilTime(cooldown.untilTime); | ||
| const [datePart, timePart] = untilDateStr.split(' '); | ||
|
|
There was a problem hiding this comment.
避免通过字符串 split 解析本地化时间
Line 123-137 依赖 toLocaleString() 再 split(' ') 拆分日期/时间,在部分语言环境(含非空格分隔符或 NBSP)下会拿到错误片段。建议直接分别格式化日期与时间,避免解析字符串。
🔧 建议修改
- const formatUntilTime = (until: string) => {
- const date = new Date(until);
- return date.toLocaleString(i18n.resolvedLanguage ?? i18n.language, {
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: false,
- });
- };
-
- const untilDateStr = formatUntilTime(cooldown.untilTime);
- const [datePart, timePart] = untilDateStr.split(' ');
+ const formatUntilTimeParts = (until: string) => {
+ const date = new Date(until);
+ const locale = i18n.resolvedLanguage ?? i18n.language;
+ return {
+ datePart: date.toLocaleDateString(locale, { month: '2-digit', day: '2-digit' }),
+ timePart: date.toLocaleTimeString(locale, {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false,
+ }),
+ };
+ };
+
+ const { datePart, timePart } = formatUntilTimeParts(cooldown.untilTime);🤖 Prompt for AI Agents
In `@web/src/components/cooldown-details-dialog.tsx` around lines 123 - 137, The
current formatUntilTime uses toLocaleString and then the code splits that string
(untilDateStr.split(' ')) which breaks for locales that don't separate date/time
with a space; update formatUntilTime into two helpers (e.g., formatDatePart and
formatTimePart) or use two Intl.DateTimeFormat instances to produce the date and
time independently (respectively using options for month/day and for
hour/minute/second with hour12 false) and replace the untilDateStr + split logic
with direct calls to these helpers when computing datePart and timePart for the
component; change references to formatUntilTime and untilDateStr accordingly so
no string split is performed.
| const isActive = | ||
| item.activeMatch === 'exact' | ||
| ? location.pathname === item.to | ||
| : location.pathname.startsWith(item.to); |
There was a problem hiding this comment.
避免前缀匹配误判激活状态
Line 27-30:startsWith 会让 /requests-foo 误匹配 /requests。建议加路径边界以降低误判。
🛠️ 建议修改
- const isActive =
- item.activeMatch === 'exact'
- ? location.pathname === item.to
- : location.pathname.startsWith(item.to);
+ const isActive =
+ item.activeMatch === 'exact'
+ ? location.pathname === item.to
+ : location.pathname === item.to || location.pathname.startsWith(`${item.to}/`);📝 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.
| const isActive = | |
| item.activeMatch === 'exact' | |
| ? location.pathname === item.to | |
| : location.pathname.startsWith(item.to); | |
| const isActive = | |
| item.activeMatch === 'exact' | |
| ? location.pathname === item.to | |
| : location.pathname === item.to || location.pathname.startsWith(`${item.to}/`); |
🤖 Prompt for AI Agents
In `@web/src/components/layout/app-sidebar/sidebar-renderer.tsx` around lines 27 -
30, The current isActive calculation uses location.pathname.startsWith(item.to),
which misidentifies routes like "/requests-foo" as matching "/requests"; update
the logic in the isActive computation (referencing isActive, item.activeMatch,
location.pathname, item.to) to enforce a path boundary: when activeMatch !==
'exact' treat a match as true only if location.pathname === item.to or
location.pathname starts with item.to + '/' (or use a regex that checks for a
segment boundary after item.to), so that only same path or nested paths match
and prefixes like "/requests-foo" do not.
| function formatJSON(obj: unknown): string { | ||
| if (!obj) return '-' | ||
| if (!obj) return '-'; | ||
| try { | ||
| return JSON.stringify(obj, null, 2) | ||
| return JSON.stringify(obj, null, 2); | ||
| } catch { | ||
| return String(obj) | ||
| return String(obj); | ||
| } | ||
| } |
There was a problem hiding this comment.
修正 formatJSON 对 falsy 值的处理。
if (!obj) 会把 0/false/'' 误显示为 -,当请求/响应体为 JSON 字面量时会丢信息。建议仅对 null/undefined 返回占位符。
🛠️ 建议修复
function formatJSON(obj: unknown): string {
- if (!obj) return '-';
+ if (obj === null || obj === undefined) return '-';
try {
return JSON.stringify(obj, null, 2);
} catch {
return String(obj);
}
}📝 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.
| function formatJSON(obj: unknown): string { | |
| if (!obj) return '-' | |
| if (!obj) return '-'; | |
| try { | |
| return JSON.stringify(obj, null, 2) | |
| return JSON.stringify(obj, null, 2); | |
| } catch { | |
| return String(obj) | |
| return String(obj); | |
| } | |
| } | |
| function formatJSON(obj: unknown): string { | |
| if (obj === null || obj === undefined) return '-'; | |
| try { | |
| return JSON.stringify(obj, null, 2); | |
| } catch { | |
| return String(obj); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@web/src/pages/requests/detail/RequestDetailPanel.tsx` around lines 38 - 45,
The formatJSON function incorrectly treats all falsy values as missing; change
the initial check to only treat null/undefined as missing (e.g., use obj == null
or obj === null || obj === undefined) so numeric 0, false, and empty string are
preserved and then keep the existing try/catch/JSON.stringify behavior to format
the value or fall back to String(obj).
There was a problem hiding this comment.
Actionable comments posted: 2
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/model-mapping-editor.tsx (1)
104-112: i18n 不一致:新增映射区域的 placeholder 未使用翻译。第 71、79 行使用了
t('modelMapping.requestModel')和t('modelMapping.mappedModel'),但这里的 placeholder 仍是硬编码的英文字符串。为保持一致性,应使用相同的翻译 key。🔧 建议修复
<ModelInput value={newFrom} onChange={setNewFrom} - placeholder="Request Model" + placeholder={t('modelMapping.requestModel')} className="flex-1" disabled={disabled} /> <ArrowRight size={16} className="text-muted-foreground shrink-0" /> <ModelInput value={newTo} onChange={setNewTo} - placeholder="Mapped Model" + placeholder={t('modelMapping.mappedModel')} className="flex-1" disabled={disabled} providers={targetProviders} />
🤖 Fix all issues with AI agents
In `@web/src/components/layout/app-sidebar/requests-nav-item.tsx`:
- Around line 37-40: The current conditional mounts
SidebarMenuBadge/StreamingBadge only when total > 0 which unmounts
StreamingBadge immediately when total hits 0 and breaks its hideDelay; instead
always render SidebarMenuBadge and StreamingBadge (remove the total > 0
short-circuit) and pass total as a prop (e.g., count or visible flag) so
StreamingBadge can animate/hide with its hideDelay, or alternatively move the
delay logic into the parent component that owns total; also ensure
SidebarMenuBadge handles empty-child styling so it doesn't leave unwanted layout
when count is 0.
In `@web/src/pages/providers/components/model-mapping-editor.tsx`:
- Around line 129-134: The empty-state paragraph is hardcoded in English;
replace the literal text in the JSX that renders when entries.length === 0 with
a call to the i18n helper (t) using a new key like
"modelMapping.noMappingsConfigured" (update the JSX that contains the
conditional rendering of entries to call t(...)), and add the corresponding
translation entry "modelMapping.noMappingsConfigured" to the locale translation
file(s) so the placeholder is translated across locales.
♻️ Duplicate comments (1)
web/src/pages/providers/components/model-mapping-editor.tsx (1)
48-60: 修复已确认:handleUpdate 现在正确处理边界情况。之前的审查意见已解决 — 现在 key 和 value 会被 trim,空值会被拒绝,重命名时若目标 key 已存在也会阻止覆盖。实现正确。
🧹 Nitpick comments (1)
web/src/pages/providers/components/select-type-step.tsx (1)
89-118: Kiro 卡片样式与其他 provider 卡片不一致Kiro 卡片使用
rounded-lg(第 93 行),而 Antigravity 和 Custom 卡片均使用rounded-xl。此外还缺少以下样式:
overflow-hiddenduration-200过渡效果focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2hover:border-accent/30 hover:shadow-sm建议统一样式以保持视觉一致性。
♻️ 建议修改
<Button onClick={() => onSelectType('kiro')} variant="ghost" - className={`group p-0 rounded-lg border text-left transition-all h-auto w-full ${ + className={`group p-0 rounded-xl border text-left h-auto w-full overflow-hidden transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 ${ formData.type === 'kiro' - ? 'border-provider-kiro bg-provider-kiro/10' - : 'border-border bg-card hover:bg-muted' + ? 'border-provider-kiro bg-provider-kiro/10 shadow-sm' + : 'border-border bg-card hover:bg-muted hover:border-accent/30 hover:shadow-sm' }`} >
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
web/src/components/layout/app-sidebar/client-routes-items.tsxweb/src/components/layout/app-sidebar/requests-nav-item.tsxweb/src/components/layout/app-sidebar/sidebar-renderer.tsxweb/src/pages/providers/components/antigravity-token-import.tsxweb/src/pages/providers/components/model-mapping-editor.tsxweb/src/pages/providers/components/provider-create-flow.tsxweb/src/pages/providers/components/select-type-step.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- web/src/components/layout/app-sidebar/client-routes-items.tsx
- web/src/pages/providers/components/provider-create-flow.tsx
- web/src/components/layout/app-sidebar/sidebar-renderer.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
web/src/pages/providers/components/select-type-step.tsx (1)
web/src/pages/providers/types.ts (3)
ProviderFormData(165-172)PROVIDER_TYPE_CONFIGS(27-60)quickTemplates(90-147)
web/src/pages/providers/components/model-mapping-editor.tsx (1)
web/src/components/ui/model-input.tsx (1)
ModelInput(161-411)
web/src/components/layout/app-sidebar/requests-nav-item.tsx (3)
web/src/hooks/use-streaming.ts (1)
useStreamingRequests(29-114)web/src/components/ui/sidebar.tsx (3)
SidebarMenuItem(688-688)SidebarMenuButton(687-687)SidebarMenuBadge(686-686)web/src/components/ui/streaming-badge.tsx (1)
StreamingBadge(27-81)
web/src/pages/providers/components/antigravity-token-import.tsx (3)
web/src/lib/transport/index.ts (4)
CreateProviderData(13-13)AntigravityTokenValidationResult(47-47)AntigravityOAuthResult(49-49)getTransport(78-78)web/src/lib/transport/types.ts (3)
CreateProviderData(54-59)AntigravityTokenValidationResult(342-348)AntigravityOAuthResult(354-364)web/src/lib/transport/factory.ts (1)
getTransport(79-102)
🔇 Additional comments (13)
web/src/pages/providers/components/antigravity-token-import.tsx (4)
1-33: 类型与签名格式化到位,未引入行为变更。
整体改动为一致化分号与排版,逻辑不受影响。
50-119: OAuth 订阅与弹窗流程保持稳定。
改动集中在格式与细节排版,流程一致。
121-211: Token 校验与创建流程清晰,改动安全。
保持原有校验与异常处理路径,未引入新风险。
213-624: UI 文案与样式调整一致。
展示层更改合理,未影响交互逻辑。web/src/pages/providers/components/select-type-step.tsx (7)
1-13: LGTM!导入语句整洁有序,正确引入了
useTranslation用于国际化支持。
30-34: LGTM!可见 provider 数量计算和动态 grid 列数逻辑正确,
useTranslationhook 使用规范。
36-50: LGTM!Header 区域正确使用了 i18n 翻译函数,布局响应式处理得当。
60-87: LGTM!Antigravity 卡片的 i18n 集成、样式和交互状态处理完善,包含了完整的 focus-visible 和 hover 状态。
120-148: LGTM!Custom 卡片的实现与 Antigravity 保持一致,i18n 和样式处理完善。
163-186: LGTM!模板区域的 grid 布局响应式配置合理,空模板卡片正确使用了 i18n 翻译。
188-250: LGTM!模板渲染逻辑处理得当:
- 正确使用条件表达式实现 i18n fallback(
template.nameKey ? t(template.nameKey) : template.name)- 预计算
templateName和templateDescription提高可读性- 选中状态的视觉反馈完善
web/src/pages/providers/components/model-mapping-editor.tsx (1)
30-46: LGTM!
handleAdd和handleRemove的实现简洁正确:
handleAdd正确检查空值和重复 keyhandleRemove通过创建新对象避免直接修改原 valueweb/src/components/layout/app-sidebar/requests-nav-item.tsx (1)
15-16: 路由激活判断更稳健
Line 15-16 的边界判断可以避免误匹配/requests-foo,符合预期。
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| {total > 0 && ( | ||
| <SidebarMenuBadge> | ||
| <StreamingBadge count={total} color={color} /> | ||
| </SidebarMenuBadge> |
There was a problem hiding this comment.
StreamingBadge 的延迟隐藏被父级条件短路
Line 37-40 只在 total > 0 时挂载会导致 StreamingBadge 的 hideDelay 失效(total 归零后立即卸载)。若希望避免闪烁,建议保持组件挂载,或把延迟逻辑上移到父级。
💡 建议修改(保持组件挂载以启用 hideDelay)
- {total > 0 && (
- <SidebarMenuBadge>
- <StreamingBadge count={total} color={color} />
- </SidebarMenuBadge>
- )}
+ <SidebarMenuBadge>
+ <StreamingBadge count={total} color={color} />
+ </SidebarMenuBadge>若 SidebarMenuBadge 在无子节点时仍占位,请确认是否需要在其内部处理空内容样式或把延迟逻辑移到父级。
📝 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.
| {total > 0 && ( | |
| <SidebarMenuBadge> | |
| <StreamingBadge count={total} color={color} /> | |
| </SidebarMenuBadge> | |
| <SidebarMenuBadge> | |
| <StreamingBadge count={total} color={color} /> | |
| </SidebarMenuBadge> |
🤖 Prompt for AI Agents
In `@web/src/components/layout/app-sidebar/requests-nav-item.tsx` around lines 37
- 40, The current conditional mounts SidebarMenuBadge/StreamingBadge only when
total > 0 which unmounts StreamingBadge immediately when total hits 0 and breaks
its hideDelay; instead always render SidebarMenuBadge and StreamingBadge (remove
the total > 0 short-circuit) and pass total as a prop (e.g., count or visible
flag) so StreamingBadge can animate/hide with its hideDelay, or alternatively
move the delay logic into the parent component that owns total; also ensure
SidebarMenuBadge handles empty-child styling so it doesn't leave unwanted layout
when count is 0.
| {entries.length === 0 && ( | ||
| <p className="text-xs text-muted-foreground"> | ||
| No model mappings configured. Add mappings to transform request models | ||
| before sending to upstream. | ||
| No model mappings configured. Add mappings to transform request models before sending to | ||
| upstream. | ||
| </p> | ||
| )} |
There was a problem hiding this comment.
i18n 缺失:空状态提示文本未国际化。
此处的提示信息是硬编码的英文,与其他已翻译的 placeholder 不一致。建议使用 t() 函数进行国际化处理。
🔧 建议修复
{entries.length === 0 && (
<p className="text-xs text-muted-foreground">
- No model mappings configured. Add mappings to transform request models before sending to
- upstream.
+ {t('modelMapping.noMappingsConfigured')}
</p>
)}需要在对应的翻译文件中添加 modelMapping.noMappingsConfigured 的翻译条目。
📝 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.
| {entries.length === 0 && ( | |
| <p className="text-xs text-muted-foreground"> | |
| No model mappings configured. Add mappings to transform request models | |
| before sending to upstream. | |
| No model mappings configured. Add mappings to transform request models before sending to | |
| upstream. | |
| </p> | |
| )} | |
| {entries.length === 0 && ( | |
| <p className="text-xs text-muted-foreground"> | |
| {t('modelMapping.noMappingsConfigured')} | |
| </p> | |
| )} |
🤖 Prompt for AI Agents
In `@web/src/pages/providers/components/model-mapping-editor.tsx` around lines 129
- 134, The empty-state paragraph is hardcoded in English; replace the literal
text in the JSX that renders when entries.length === 0 with a call to the i18n
helper (t) using a new key like "modelMapping.noMappingsConfigured" (update the
JSX that contains the conditional rendering of entries to call t(...)), and add
the corresponding translation entry "modelMapping.noMappingsConfigured" to the
locale translation file(s) so the placeholder is translated across locales.
Summary by CodeRabbit
发布说明
新功能
UI/UX改进
国际化
✏️ Tip: You can customize this high-level summary in your review settings.