Conversation
- 更新了内容库页面的结构,简化了状态管理和数据获取逻辑。 - 引入了新的组件,如 `ContentList`、`ContentCard` 和 `ContentPreview`,以提高代码的可重用性和可维护性。 - 增强了搜索和过滤功能,允许用户更方便地查找内容。 - 实现了 AI 分析卡片组件,展示内容的智能分析结果,提升用户体验。 - 进行了代码格式化和类型定义的改进,确保代码的一致性和清晰度。 这些更改显著提升了内容库的功能和用户交互体验。
- 修改了多个组件和测试文件中的路由路径,将 `/reader/${item.id}` 更新为 `/content-library/reader/${item.id}`,以确保一致性。
- 删除了不再使用的 `page.tsx` 文件,简化了代码结构。
这些更改确保了内容库的路由逻辑与新的页面结构相匹配,提升了代码的可维护性。
- 修改了 `ContentPreview` 组件的内边距,使其顶部间距更为一致。 - 优化了 `EnhancedLLMAnalysisSidebar` 组件的结构,确保推荐分析和自定义分析对话框的布局更加清晰。 这些更改提升了组件的视觉一致性和用户体验。
- 将 `ContentCard` 组件中的背景色从 `bg-primary/10` 修改为 `bg-transparent`,提升视觉效果。 - 移除了 `ContentPreview` 组件中的图标,简化了布局。 这些更改增强了组件的视觉一致性和用户体验。
- 将 `ClientContent` 组件中的某些绝对定位的样式修改为更灵活的布局,提升了响应式设计。 - 移除了不必要的分享按钮和弹窗,简化了用户界面。 - 调整了 `VirtualScrollRenderer` 组件的样式,去掉了多余的边框,增强了视觉效果。 这些更改提升了组件的可用性和用户体验。
- 在 `globals.css` 中添加了 `.no-scrollbar` 类,以完全隐藏滚动条,提升用户界面美观性。 - 在 `ContentLibraryPage` 中重构了页面布局,采用左右两栏设计,增强了内容展示的清晰度。 - 使用新的选择组件替代原有的下拉菜单,优化了状态和类型筛选功能的用户体验。 - 在 `ContentList` 组件中添加了分隔符,提升了内容卡片之间的视觉分隔效果。 这些更改提升了内容库页面的可用性和用户体验。
- 在 `globals.css` 中调整了侧边栏的颜色和宽度,增加了自定义宽度的变量。 - 在 `ContentLibraryPage` 中优化了左栏的布局,确保在不同屏幕尺寸下的适应性。 - 修改了 `ContentCard` 组件的样式,提升了内容卡片的视觉效果和一致性。 - 更新了 `MainLayout` 的背景样式,确保整体视觉风格统一。 - 调整了 `ProcessingStatusBadge` 组件的样式,简化了状态显示。 这些更改提升了内容库页面的可用性和视觉一致性。
- 在 `globals.css` 中添加了新的自定义宽度变量 `--size-card-title`,用于设置卡片标题的宽度。 - 修改了 `ContentCard` 组件,使用新的宽度变量来优化标题和摘要的样式。 - 在 `ContentList` 组件中调整了分隔符的样式,使其宽度与卡片标题一致,增强了视觉一致性。 这些更改提升了内容卡片的可读性和整体布局的美观性。
- 在 `globals.css` 中新增了多个自定义颜色变量,包括 Tailwind 的石头色和锌色调色板,以及 Notion 的颜色调色板,分别适用于浅色和深色主题。 - 这些更改提升了应用的视觉一致性,确保在不同主题下的颜色表现更加协调。
- 将 `IconCirclePlusFilled` 替换为 `IconCirclePlus`,以统一图标样式。 - 调整了 `SidebarHeader` 的内边距,增加了 `px-1`,提升了视觉效果。 - 优化了 `SidebarGroup` 的布局,确保主要导航和上传内容的清晰展示。 这些更改增强了侧边栏的可用性和视觉一致性。
- 在 `globals.css` 中调整了背景和前景颜色,使用新的自定义颜色变量,增强了视觉一致性。 - 修改了 `ContentLibraryPage` 中的侧边栏样式,确保其在不同屏幕尺寸下的适应性。 - 优化了 `AIAnalysisCard` 和 `ContentCard` 组件的样式,简化了布局,提升了用户体验。 - 更新了 `ContentPreview` 组件,调整了内边距和结构,增强了内容展示的清晰度。 这些更改提升了应用的整体可用性和视觉美观性。
- 从 `Providers` 组件中移除了 `ReactQueryDevtools`,以减少不必要的依赖和复杂性。 - 该更改有助于提升应用的性能和可维护性。
- 修改了 `ContentLibraryPage` 中的测试用例,确保点击“查看全文”按钮后正确导航到阅读页面。 - 在 `ContentCard` 组件中调整了点击处理逻辑,首次点击仅设置焦点,第二次点击通过按钮触发导航。 - 更新了 `ContentPreview` 组件,添加了键盘事件处理,确保无障碍访问。 这些更改提升了用户在内容库页面的交互体验和可用性。
- 在 `ContentList` 组件中引入了 `React`,以确保使用 `React.Fragment` 的正确性。 - 将原有的空标签替换为 `React.Fragment`,并为其添加了 `key` 属性,提升了组件的性能和可维护性。 这些更改增强了代码的清晰度和可读性。
- 在 `PreviewTransition.test.tsx` 中添加了对 `PreviewTransition` 组件的测试,确保在切换面板时旧面板在 DOM 中保持可见,直到动画结束。 - 更新了 `ContentLibraryPage` 和 `ContentPreview` 组件的样式,调整了布局以提升用户体验和视觉效果。 这些更改增强了组件的可测试性和用户交互体验。
- 在 `openapi.json` 中添加了多个 API 路径和响应结构,确保文档的完整性和准确性。 - 在 `ContentLibraryPage` 和相关组件中调整了导入语句和样式,提升了代码的可读性和一致性。 - 更新了 `ContentCard` 和 `ContentList` 组件的样式,确保在不同屏幕尺寸下的适应性。 这些更改增强了 API 文档的可用性和组件的用户体验。
- 在 `page.test.tsx` 中添加了对 `scrollTo` 函数的模拟,确保测试环境的稳定性。 - 移除了搜索功能和过滤控件的相关测试,反映了功能的实际变更。 - 删除了 `PreviewTransition.test.tsx` 文件,移除了不再需要的测试用例。 这些更改提升了测试的准确性和代码的整洁性。
CI Feedback 🧐(Feedback updated until commit 29430f7)A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
WalkthroughThis update introduces a major refactor and redesign of the content library UI. Logic for content management is extracted into custom hooks and new components for cards, lists, and previews. The layout, color palette, and styling are overhauled, and several UI features (search, filter, sharing, conversations) are removed or simplified. Extensive new utility classes and palettes are added. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ContentList
participant useContentItems
participant ContentPreview
User->>ContentList: Selects content card
ContentList->>useContentItems: setSelectedItem(item)
useContentItems-->>ContentList: Updates selected item state
ContentList->>ContentPreview: Passes selected item as prop
ContentPreview->>User: Displays preview and AI analysis
User->>ContentPreview: Clicks "View Full Text"
ContentPreview->>Router: Navigate to reader page
Assessment against linked issues
Suggested reviewers
Poem
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||
- 从 `index.ts` 文件中删除了对 `types` 和 `utils` 的导入,反映了代码的实际需求。 - 该更改有助于提升代码的整洁性和可维护性。
There was a problem hiding this comment.
Actionable comments posted: 9
🔭 Outside diff range comments (1)
frontend/components/ui/enhanced-llm-analysis-sidebar.tsx (1)
75-165: Add cleanup for async operations to prevent memory leaksThe loadHistoricalAnalyses function updates state without checking if the component is mounted.
Add a mounted flag to prevent state updates after unmount:
const loadHistoricalAnalyses = useCallback(async () => { + let isMounted = true; try { setLoadingHistorical(true); // ... existing code ... if (contentData.ai_analysis) { + if (!isMounted) return; // ... rest of the processing ... setHistoricalAnalyses(historicalData); } } catch (error) { console.error("加载历史分析失败:", error); } finally { + if (isMounted) { setLoadingHistorical(false); + } } + return () => { isMounted = false; }; }, [contentId]);
🧹 Nitpick comments (11)
frontend/app/globals.css (2)
28-29: Ensure consistent color token usage across semantic variables.The semantic color variables reference Linear design tokens, but there's potential for inconsistency:
--background: var(--color-linear-bg-1); --sidebar: var(--color-linear-bg-1);Both use the same Linear token, which may not provide sufficient contrast between background and sidebar in all contexts.
Consider using distinct tokens for better visual hierarchy:
- --background: var(--color-linear-bg-1); + --background: var(--color-linear-bg-0); --sidebar: var(--color-linear-bg-1);Also applies to: 56-56
73-77: Consider centralizing layout token definitions.The custom width tokens are well-defined but could benefit from better organization and documentation:
+ /* === Layout & Sizing Tokens === */ + /* Content library specific widths */ --size-library: 35.25rem; /* 564px - left pane fixed width */ --size-library-card: 28.75rem; /* 460px - card width */ --size-card-title: 25rem; /* 400px - card text width */ + + /* Verify these widths work well on tablets and mobile */frontend/app/content-library/components/ContentCard.tsx (2)
49-49: Remove unnecessary key propThe
keyprop on line 49 is unnecessary since this component is not directly rendered in a list. The parent component should handle keys when rendering multiple ContentCard instances.- <Card - key={item.id} + <Card
19-30: Consider using a const assertion for better type safetyThe icon mapping could benefit from const assertion to ensure type safety and prevent accidental modifications.
-const getContentIcon = (type: string) => { +const getContentIcon = (type: string) => { + const iconMap = { + pdf: FileText, + url: Link, + text: BookOpen, + } as const; + + const IconComponent = iconMap[type as keyof typeof iconMap] || FileText; + return <IconComponent className="h-4 w-4" />; - switch (type) { - case "pdf": - return <FileText className="h-4 w-4" />; - case "url": - return <Link className="h-4 w-4" />; - case "text": - return <BookOpen className="h-4 w-4" />; - default: - return <FileText className="h-4 w-4" />; - } };frontend/components/ui/ProcessingStatusBadge.tsx (1)
77-81: Consider consolidating icon size configurationThe
iconSizeMapduplicates size information that could be consolidated withsizeClassesfor better maintainability.const sizeClasses = { sm: { container: "text-xs px-1.5 py-0.5", - icon: "h-3 w-3", + icon: "h-4 w-4", gap: "gap-1", }, md: { container: "text-sm px-2 py-1", - icon: "h-4 w-4", + icon: "h-5 w-5", gap: "gap-1.5", }, lg: { container: "text-base px-3 py-1.5", - icon: "h-5 w-5", + icon: "h-6 w-6", gap: "gap-2", }, }; export const ProcessingStatusBadge: FC<ProcessingStatusBadgeProps> = ({ status, progress, className, size = "md", showText = false, }) => { const config = statusConfigs[status]; const sizeConfig = sizeClasses[size]; const Icon = config.icon; - const iconSizeMap: Record<typeof size, string> = { - sm: "h-4 w-4", - md: "h-5 w-5", - lg: "h-6 w-6", - }; return ( <div className={cn( "inline-flex items-center rounded-full font-medium transition-colors border-transparent bg-transparent", config.textClass, sizeConfig.container, sizeConfig.gap, className, )} > <Icon className={cn( - iconSizeMap[size], + sizeConfig.icon, status === "processing" && "animate-spin", )} />frontend/app/content-library/reader/[id]/ClientContent.tsx (1)
100-114: Consider making AI processing indicators configurableThe AI processing version indicators are now hidden using
hiddenclass. Consider making this configurable through props or a feature flag instead of hard-coding the hiding behavior.+interface ProcessedContentRendererProps { + content: ContentDetail; + markdownContent?: string | null; + contentId: string; + showProcessingIndicators?: boolean; +} const ProcessedContentRenderer = memo( ({ content, markdownContent, contentId, + showProcessingIndicators = false, }: ProcessedContentRendererProps) => { // ... existing code ... - <div className="hidden"> + <div className={showProcessingIndicators ? "" : "hidden"}>Also applies to: 157-169
frontend/app/content-library/components/AIAnalysisCard.tsx (1)
50-54: Improve preview text handling for better UXUsing JSON.stringify for preview text might show technical details to users. Consider providing a more user-friendly fallback.
<p className="text-sm leading-relaxed"> {typeof preview === "string" ? preview.substring(0, 150) + "..." - : "(unsupported format)"} + : "内容格式暂不支持预览"} </p>frontend/components/ui/enhanced-llm-analysis-sidebar.tsx (2)
49-50: Remove unnecessary variable and update commentThe comment states the tab state is no longer necessary, but the variable is still used in the JSX.
Since the sidebar always shows analysis, remove the variable declaration and hard-code the value where needed:
- // The sidebar will always show the analysis view, so a dedicated tab state is no longer necessary. - const activeTab = "analysis" as const;Then update line 374:
- <Tabs value={activeTab} className="h-full"> + <Tabs value="analysis" className="h-full">
391-397: Remove commented-out codeCommented code should be removed to keep the codebase clean.
- {/* 历史分析标识 - 已移除 */} - {/* {analysis.id.startsWith("historical_") && ( - <div className="flex items-center gap-2 text-xs text-muted-foreground mb-2"> - <History className="h-3 w-3" /> - <span>历史分析</span> - <div className="flex-1 h-px bg-border" /> - </div> - )} */}frontend/app/content-library/hooks/useContentItems.ts (2)
83-101: Use requestAnimationFrame for more reliable scroll restorationUsing setTimeout with a fixed delay is fragile and may not work reliably across different devices.
if (savedState.scrollPosition > 0) { - setTimeout( - () => + requestAnimationFrame(() => { + requestAnimationFrame(() => { window.scrollTo({ top: savedState.scrollPosition, behavior: "auto", - }), - 100, - ); + }); + }); + }); }
305-305: Consider using requestIdleCallback for batch prefetchingStarting batch prefetch immediately after data load might impact initial render performance.
- setTimeout(() => batchPrefetchContent(data), 500); + if ('requestIdleCallback' in window) { + requestIdleCallback(() => batchPrefetchContent(data)); + } else { + setTimeout(() => batchPrefetchContent(data), 1000); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
frontend/__tests__/app/content-library/page.test.tsx(3 hunks)frontend/app/content-library/components/AIAnalysisCard.tsx(1 hunks)frontend/app/content-library/components/ContentCard.tsx(1 hunks)frontend/app/content-library/components/ContentList.tsx(1 hunks)frontend/app/content-library/components/ContentPreview.tsx(1 hunks)frontend/app/content-library/components/LibraryHeader.tsx(1 hunks)frontend/app/content-library/hooks/useContentItems.ts(1 hunks)frontend/app/content-library/page.tsx(2 hunks)frontend/app/content-library/reader/[id]/ClientContent.tsx(5 hunks)frontend/app/content-library/types.ts(1 hunks)frontend/app/content-library/utils/ripple.ts(1 hunks)frontend/app/globals.css(8 hunks)frontend/app/providers.tsx(0 hunks)frontend/components/layout/AppSidebar.tsx(3 hunks)frontend/components/layout/MainLayout.tsx(2 hunks)frontend/components/layout/ReaderLayout.tsx(1 hunks)frontend/components/ui/ProcessingStatusBadge.tsx(2 hunks)frontend/components/ui/VirtualScrollRenderer.tsx(2 hunks)frontend/components/ui/enhanced-llm-analysis-sidebar.tsx(4 hunks)frontend/components/ui/llm-analysis-card.tsx(1 hunks)frontend/components/ui/prompt-recommendations.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- frontend/app/providers.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: test-backend
- GitHub Check: Complete CI/CD Pipeline
🔇 Additional comments (28)
frontend/app/content-library/types.ts (1)
1-30: Well-structured TypeScript interface with good extensibility.The
ContentItemPublicinterface is well-designed with:
- Appropriate use of optional fields for nullable/undefined values
- Comprehensive AI analysis structure supporting multiple analyzer types
- Extensible design with the index signature for unknown analysis types
- Clear field naming and logical nesting
Consider adding JSDoc comments to document the purpose of complex nested structures:
export interface ContentItemPublic { id: string; type: string; source_uri?: string | null; title?: string | null; summary?: string | null; user_id: string; processing_status: string; created_at: string; updated_at: string; + /** AI-generated analysis results from various analyzers */ ai_analysis?: { + /** Text summarization analysis with main thesis and insights */ summarizer?: { summary?: { main_thesis?: string; key_insights?: string[]; conclusion?: string; }; raw_text?: string; }; + /** Key points extraction with categorized insights */ key_points_extractor?: { // ... rest of structurefrontend/app/globals.css (2)
997-1002: LGTM: Line clamp utility addition.The
.line-clamp-4utility class is a useful addition that complements the existing line clamp utilities and supports content truncation in the new UI components.
397-397: ```shell
#!/usr/bin/env bashRe-run search across CSS, TS, and TSX files (ripgrep doesn’t include TSX by default)
echo "Searching for header spacing references in .css, .ts, and .tsx files..."
rg -A 3 -B 3 'header.3.5rem|--spacing-header|header-height' -g '.css' -g '.ts' -g '.tsx'</details> <details> <summary>frontend/components/layout/ReaderLayout.tsx (1)</summary> `89-89`: **LGTM: Subtle background enhancement for visual separation.** The addition of `bg-muted/30` provides appropriate visual separation for the AI analysis panel while maintaining readability. The translucent background aligns well with the overall UI refactoring objectives. </details> <details> <summary>frontend/components/ui/llm-analysis-card.tsx (1)</summary> `203-207`: **Loading state UI simplification approved.** The removal of the redundant loading indicator aligns with the broader UI simplification goals while preserving the streaming content experience with the typing cursor animation. </details> <details> <summary>frontend/components/ui/prompt-recommendations.tsx (3)</summary> `40-40`: **Button styling enhancements look good.** The updated button classes improve visual feedback with shadow effects and better hover states while maintaining the horizontal layout constraints. --- `64-71`: **Consistent loading state UI removal.** The commented-out loading indicator is consistent with similar changes across other components in this refactor. --- `29-29`: Continuing accessibility inspection across TS/TSX files: ```shell #!/bin/bash # Description: Find horizontal scroll containers and check for accessibility attributes in TS/TSX rg -A3 -B3 'overflow-x-auto|flex.*space-x' --glob '*.tsx' --glob '*.ts'frontend/components/ui/VirtualScrollRenderer.tsx (2)
264-264: Clean styling simplification for main container.Removing the border and background classes creates a cleaner, more minimalist appearance consistent with the UI refactor goals.
339-339: ChunkItem styling simplified appropriately.The removal of border and background classes from individual chunk items maintains visual consistency with the overall simplification theme.
frontend/__tests__/app/content-library/page.test.tsx (4)
16-17: Good JSDOM compatibility improvement.Adding the scrollTo mock prevents potential test failures in the JSDOM environment and supports the scroll management features introduced in the UI refactor.
118-122: Correctly tests removal of search functionality.The assertion properly verifies that search functionality has been removed as part of the UI simplification, changing from expecting presence to expecting absence.
124-146: Navigation test updated for new interaction pattern.The test correctly reflects the new two-step interaction pattern where clicking a content card sets focus first, then clicking "查看全文" navigates to the reader page.
161-169: Header title and filter control assertions updated.The test properly verifies the header title change from Chinese "内容库" to English "Library" and confirms the removal of filter controls.
frontend/app/content-library/components/ContentList.tsx (2)
15-43: Well-structured component with good React patterns.The ContentList component follows excellent React practices with proper keying, conditional rendering, and clear separation of concerns. The use of React.Fragment for grouping cards with separators is appropriate.
34-37: ```shell
#!/bin/bashRetry searching for CSS variable definitions using grep
Search for exact --size-card-title in relevant files
grep -R -- "--size-card-title" .
--include=".css"
--include=".scss"
--include=".tsx"
--include=".ts" || trueSearch for any variables starting with --size-card- in CSS/SCSS
grep -R -- "--size-card-" .
--include=".css"
--include=".scss" || true</details> <details> <summary>frontend/components/layout/AppSidebar.tsx (3)</summary> `8-8`: **Icon naming consistency check** The icon was changed from `IconCirclePlusFilled` to `IconCirclePlus`. Ensure this aligns with the overall design system's icon usage patterns across the application. --- `137-150`: **Good consolidation of navigation items** Merging the upload button with main navigation items into a single `SidebarGroup` simplifies the component structure and improves maintainability. --- `124-124`: Let’s pull in the `SidebarHeader` implementation to see its default padding: ```shell #!/bin/bash # Display SidebarHeader definition with surrounding lines rg -n -A10 -B5 "function SidebarHeader" --glob 'frontend/components/ui/sidebar.tsx'frontend/app/content-library/components/ContentCard.tsx (2)
58-63: Good keyboard accessibility implementationThe keyboard event handling properly supports both Enter and Space keys, and includes preventDefault to avoid unintended scrolling.
51-51: Verify custom width class definitionThe component uses a custom width class
w-libraryCard. Ensure this class is defined in your CSS/Tailwind configuration.#!/bin/bash # Description: Search for the w-libraryCard class definition # Check CSS files rg "w-libraryCard" --type css # Check Tailwind config fd "tailwind.config" -e js -e ts | xargs rg "libraryCard" # Check for any width-related custom classes rg "libraryCard" --type css --type scssfrontend/components/ui/ProcessingStatusBadge.tsx (1)
71-71: To catch all JSX usages more reliably, let’s search TSX files with ripgrep for any<ProcessingStatusBadge>that omitshowText:#!/usr/bin/env bash # Find all TSX usages of ProcessingStatusBadge without an explicit showText prop rg "<ProcessingStatusBadge\b[^>]*>" -n --glob "*.tsx" | grep -v "showText"frontend/components/layout/MainLayout.tsx (2)
8-8: Clean removal of authentication UIThe removal of all authentication-related imports and UI elements aligns well with the PR's objective to simplify the interface. This creates a cleaner separation of concerns.
38-38: Verify page-top-highlight class definitionThe container class was changed from
bg-backgroundtopage-top-highlight. Ensure this custom class is properly defined in your styles.#!/bin/bash # Description: Search for page-top-highlight class definition # Check CSS files rg "page-top-highlight" --type css --type scss # Check global styles fd "globals.css" -e css | xargs rg "page-top-highlight" # Check if it's a Tailwind utility fd "tailwind.config" -e js -e ts | xargs rg "page-top-highlight"frontend/app/content-library/reader/[id]/ClientContent.tsx (2)
435-437: Good UI simplificationThe simplified header with just the title and removal of badges creates a cleaner, less cluttered interface that aligns well with the PR's objectives.
117-117: ```shell
#!/bin/bashDisplay the top of the component to inspect wrapper containers
sed -n '1,200p' frontend/app/content-library/reader/[id]/ClientContent.tsx
Search for any usage of "relative" in className within this file
rg -n "className=.*relative" frontend/app/content-library/reader/[id]/ClientContent.tsx
</details> <details> <summary>frontend/app/content-library/components/ContentPreview.tsx (1)</summary> `86-89`: **Add null check before scrollTo call** The optional chaining for focus is good, but scrollTo is called without a null check. ```diff useEffect(() => { - containerRef.current?.scrollTo({ top: 0 }); - containerRef.current?.focus?.(); + if (containerRef.current) { + containerRef.current.scrollTo({ top: 0 }); + containerRef.current.focus?.(); + } }, []);Likely an incorrect or invalid review comment.
frontend/app/content-library/hooks/useContentItems.ts (1)
73-80: Move debounced handler outside effect to prevent recreationThe debounced handler is recreated on every render, defeating the purpose of debouncing.
+ // Create debounced scroll handler outside of effect + const debouncedHandleScroll = useCallback( + debounce(() => { + navigationState.saveLibraryState({ scrollPosition: window.scrollY }); + }, 300), + [] + ); + // 滚动位置保存/恢复 useEffect(() => { - const handleScroll = () => { - navigationState.saveLibraryState({ scrollPosition: window.scrollY }); - }; - const debouncedHandleScroll = debounce(handleScroll, 300); window.addEventListener("scroll", debouncedHandleScroll); return () => window.removeEventListener("scroll", debouncedHandleScroll); - }, []); + }, [debouncedHandleScroll]);Likely an incorrect or invalid review comment.
| export const createRipple = (event: React.MouseEvent<HTMLElement>) => { | ||
| const button = event.currentTarget; | ||
| const rect = button.getBoundingClientRect(); | ||
| const size = Math.max(rect.width, rect.height); | ||
| const x = event.clientX - rect.left - size / 2; | ||
| const y = event.clientY - rect.top - size / 2; | ||
|
|
||
| const ripple = document.createElement("span"); | ||
| ripple.style.cssText = ` | ||
| position: absolute; | ||
| border-radius: 50%; | ||
| background: rgba(var(--primary), 0.3); | ||
| transform: scale(0); | ||
| animation: ripple 0.6s linear; | ||
| left: ${x}px; | ||
| top: ${y}px; | ||
| width: ${size}px; | ||
| height: ${size}px; | ||
| pointer-events: none; | ||
| `; | ||
|
|
||
| button.style.position = "relative"; | ||
| button.style.overflow = "hidden"; | ||
| button.appendChild(ripple); | ||
|
|
||
| setTimeout(() => { | ||
| ripple.remove(); | ||
| }, 600); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve robustness and prevent potential issues with the ripple implementation.
Several areas need attention:
- Style preservation: Direct modification of
button.style.positionandbutton.style.overflowcould override existing styles without restoration. - Animation duration consistency: The duration is hard-coded in both CSS (
0.6s) and setTimeout (600ms). - Memory leak prevention: If the button element is removed before the timeout completes, the cleanup won't execute properly.
- Multiple rapid clicks: Successive clicks could create overlapping ripples without cleanup.
export const createRipple = (event: React.MouseEvent<HTMLElement>) => {
const button = event.currentTarget;
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = event.clientX - rect.left - size / 2;
const y = event.clientY - rect.top - size / 2;
+ const ANIMATION_DURATION = 600; // Centralize duration constant
+
const ripple = document.createElement("span");
ripple.style.cssText = `
position: absolute;
border-radius: 50%;
background: rgba(var(--primary), 0.3);
transform: scale(0);
- animation: ripple 0.6s linear;
+ animation: ripple ${ANIMATION_DURATION}ms linear;
left: ${x}px;
top: ${y}px;
width: ${size}px;
height: ${size}px;
pointer-events: none;
`;
+ // Preserve original styles
+ const originalPosition = button.style.position;
+ const originalOverflow = button.style.overflow;
+
- button.style.position = "relative";
- button.style.overflow = "hidden";
+ if (!button.style.position || button.style.position === "static") {
+ button.style.position = "relative";
+ }
+ if (!button.style.overflow) {
+ button.style.overflow = "hidden";
+ }
+
button.appendChild(ripple);
+ // Improved cleanup with element existence check
- setTimeout(() => {
- ripple.remove();
- }, 600);
+ const cleanup = () => {
+ if (ripple.parentNode) {
+ ripple.remove();
+ }
+ };
+
+ setTimeout(cleanup, ANIMATION_DURATION);
};📝 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.
| export const createRipple = (event: React.MouseEvent<HTMLElement>) => { | |
| const button = event.currentTarget; | |
| const rect = button.getBoundingClientRect(); | |
| const size = Math.max(rect.width, rect.height); | |
| const x = event.clientX - rect.left - size / 2; | |
| const y = event.clientY - rect.top - size / 2; | |
| const ripple = document.createElement("span"); | |
| ripple.style.cssText = ` | |
| position: absolute; | |
| border-radius: 50%; | |
| background: rgba(var(--primary), 0.3); | |
| transform: scale(0); | |
| animation: ripple 0.6s linear; | |
| left: ${x}px; | |
| top: ${y}px; | |
| width: ${size}px; | |
| height: ${size}px; | |
| pointer-events: none; | |
| `; | |
| button.style.position = "relative"; | |
| button.style.overflow = "hidden"; | |
| button.appendChild(ripple); | |
| setTimeout(() => { | |
| ripple.remove(); | |
| }, 600); | |
| }; | |
| export const createRipple = (event: React.MouseEvent<HTMLElement>) => { | |
| const button = event.currentTarget; | |
| const rect = button.getBoundingClientRect(); | |
| const size = Math.max(rect.width, rect.height); | |
| const x = event.clientX - rect.left - size / 2; | |
| const y = event.clientY - rect.top - size / 2; | |
| const ANIMATION_DURATION = 600; // Centralize duration constant | |
| const ripple = document.createElement("span"); | |
| ripple.style.cssText = ` | |
| position: absolute; | |
| border-radius: 50%; | |
| background: rgba(var(--primary), 0.3); | |
| transform: scale(0); | |
| animation: ripple ${ANIMATION_DURATION}ms linear; | |
| left: ${x}px; | |
| top: ${y}px; | |
| width: ${size}px; | |
| height: ${size}px; | |
| pointer-events: none; | |
| `; | |
| // Preserve original styles | |
| const originalPosition = button.style.position; | |
| const originalOverflow = button.style.overflow; | |
| if (!button.style.position || button.style.position === "static") { | |
| button.style.position = "relative"; | |
| } | |
| if (!button.style.overflow) { | |
| button.style.overflow = "hidden"; | |
| } | |
| button.appendChild(ripple); | |
| // Improved cleanup with element existence check | |
| const cleanup = () => { | |
| if (ripple.parentNode) { | |
| ripple.remove(); | |
| } | |
| }; | |
| setTimeout(cleanup, ANIMATION_DURATION); | |
| }; |
🤖 Prompt for AI Agents
In frontend/app/content-library/utils/ripple.ts lines 1 to 29, improve the
ripple effect by preserving the button's original position and overflow styles
before modifying them and restore these styles after the ripple animation
completes. Synchronize the animation duration by defining it once and using it
consistently in both the CSS animation and the setTimeout delay. Add a check to
ensure the ripple element still exists before attempting to remove it to prevent
errors if the button is removed early. Also, handle multiple rapid clicks by
removing any existing ripple elements before adding a new one to avoid
overlapping ripples.
| /* === Custom Color Palettes (Tailwind v4 tokens) === */ | ||
| /* ------------------------------------------------- | ||
| * Common root —— Dark Scheme | ||
| * -------------------------------------------------*/ | ||
| /* ------ Tailwind Stone ------ */ | ||
| --stone-50: oklch(98.48% 0.001 106deg); | ||
| --stone-100: oklch(96.99% 0.001 106deg); | ||
| --stone-200: oklch(92.32% 0.003 49deg); | ||
| --stone-300: oklch(86.87% 0.004 56deg); | ||
| --stone-400: oklch(71.61% 0.009 56deg); | ||
| --stone-500: oklch(55.34% 0.012 58deg); | ||
| --stone-600: oklch(44.44% 0.01 74deg); | ||
| --stone-700: oklch(37.41% 0.009 68deg); | ||
| --stone-800: oklch(26.85% 0.006 34deg); | ||
| --stone-900: oklch(21.61% 0.006 56deg); | ||
| --stone-950: oklch(14.69% 0.004 49deg); | ||
|
|
||
| /* ------ Tailwind Zinc ------ */ | ||
| --zinc-50: oklch(98.51% 0.0 90deg); | ||
| --zinc-100: oklch(96.74% 0.001 286deg); | ||
| --zinc-200: oklch(91.97% 0.004 286deg); | ||
| --zinc-300: oklch(87.11% 0.005 286deg); | ||
| --zinc-400: oklch(71.18% 0.013 286deg); | ||
| --zinc-500: oklch(55.17% 0.014 286deg); | ||
| --zinc-600: oklch(44.19% 0.015 286deg); | ||
| --zinc-700: oklch(37.03% 0.012 286deg); | ||
| --zinc-800: oklch(27.39% 0.005 286deg); | ||
| --zinc-900: oklch(21.03% 0.006 286deg); | ||
| --zinc-950: oklch(14.08% 0.004 286deg); | ||
|
|
||
| /* ------ Notion Palette (text / bg) ------ */ | ||
| --notion-default-bg: oklch(100% 0 90deg); | ||
| --notion-default-tx: oklch(32.80% 0.01 87deg); | ||
|
|
||
| --notion-gray-bg: oklch(94.29% 0.002 93deg); | ||
| --notion-gray-tx: oklch(55.52% 0.009 83deg); | ||
|
|
||
| --notion-brown-bg: oklch(95.28% 0.001 43deg); | ||
| --notion-brown-tx: oklch(53.23% 0.03 55deg); | ||
|
|
||
| --notion-orange-bg: oklch(95.92% 0.005 77deg); | ||
| --notion-orange-tx: oklch(60.07% 0.083 69deg); | ||
|
|
||
| --notion-yellow-bg: oklch(97.37% 0.01 94deg); | ||
| --notion-yellow-tx: oklch(70.26% 0.085 90deg); | ||
|
|
||
| --notion-green-bg: oklch(95.22% 0.005 154deg); | ||
| --notion-green-tx: oklch(60.04% 0.054 147deg); | ||
|
|
||
| --notion-blue-bg: oklch(94.97% 0.008 245deg); | ||
| --notion-blue-tx: oklch(55.08% 0.057 250deg); | ||
|
|
||
| --notion-purple-bg: oklch(96.71% 0.005 306deg); | ||
| --notion-purple-tx: oklch(57.40% 0.068 301deg); | ||
|
|
||
| --notion-pink-bg: oklch(97.08% 0.006 19deg); | ||
| --notion-pink-tx: oklch(60.45% 0.094 23deg); | ||
|
|
||
| --notion-red-bg: oklch(96.45% 0.007 33deg); | ||
| --notion-red-tx: oklch(57.30% 0.098 37deg); | ||
|
|
||
| /* ------ macOS System Gray (Dark) ------ */ | ||
| --mac-gray-1: oklch(55.17% 0.01 88deg); | ||
| --mac-gray-2: oklch(38.78% 0.009 93deg); | ||
| --mac-gray-3: oklch(28.83% 0.006 93deg); | ||
| --mac-gray-4: oklch(23.27% 0.006 94deg); | ||
| --mac-gray-5: oklch(17.61% 0.005 94deg); | ||
| --mac-gray-6: oklch(11.13% 0.004 93deg); | ||
|
|
||
| /* === Linear Palette === */ | ||
| /* Brand / Accent */ | ||
| --color-linear-accent: #5e6ad2; | ||
|
|
||
| /* Neutrals */ | ||
| --color-linear-bg-0: #f4f5f8; /* 页面最底层背景 */ | ||
| --color-linear-bg-1: #f5f5f5; /* 侧边栏 / 主背景 */ | ||
| --color-linear-bg-2: #ffffff; /* 卡片 / 列 */ | ||
| --color-linear-border: #e5e6ea; /* Divider */ | ||
|
|
||
| /* Typography */ | ||
| --color-linear-text: #222326; /* 主文本 */ | ||
| --color-linear-text-secondary: #8a8f98; /* 次文本 */ | ||
|
|
||
| /* Reeder specific */ | ||
| --color-reeder-border-card: #f3f2ee; | ||
|
|
||
| /* Dia icon palette */ | ||
| --color-dia-icon-bg: #f1f2f2; /* icon container background */ | ||
| --color-dia-icon: #fcfcfc; /* icon itself / fg */ | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Optimize CSS by removing duplicate color palette definitions.
The color palettes are defined identically in both light and dark mode sections. This creates unnecessary code duplication and maintenance overhead.
/* Move shared palettes outside the .dark selector */
+ /* === Shared Color Palettes === */
+ /* These palettes are theme-agnostic */
+ /* ------ Tailwind Stone ------ */
+ --stone-50: oklch(98.48% 0.001 106deg);
+ /* ... rest of stone palette ... */
+
+ /* ------ Notion Palette ------ */
+ --notion-default-bg: oklch(100% 0 90deg);
+ /* ... rest of notion palette ... */
.dark {
/* Dark‑mode semantic colors */
--background: oklch(0.145 0 0);
/* ... other dark-specific overrides ... */
- /* Remove duplicate palette definitions from here */This reduces CSS size and ensures palette consistency across themes.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/app/globals.css between lines 208 and 297, the color palettes for
light and dark modes are duplicated identically, causing unnecessary code bloat
and maintenance overhead. To fix this, consolidate the shared color palette
definitions into a single common section outside the theme-specific blocks,
removing the duplicate definitions from both light and dark mode sections. This
will reduce CSS size and maintain consistency across themes.
| <header className="flex shrink-0 items-center gap-2 bg-background px-4 md:px-6"> | ||
| {/* Title */} | ||
| <h1 className="text-lg font-semibold">内容库</h1> | ||
| </header> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enhance accessibility and internationalization.
The component has several areas for improvement:
- Accessibility: Missing proper heading hierarchy and semantic structure
- Internationalization: Hardcoded Chinese text "内容库"
- Flexibility: Could accept props for customization
+ interface LibraryHeaderProps {
+ title?: string;
+ className?: string;
+ }
+
- export const LibraryHeader: FC = () => {
+ export const LibraryHeader: FC<LibraryHeaderProps> = ({
+ title = "内容库",
+ className = ""
+ }) => {
return (
- <header className="flex shrink-0 items-center gap-2 bg-background px-4 md:px-6">
+ <header
+ className={`flex shrink-0 items-center gap-2 bg-background px-4 md:px-6 ${className}`}
+ role="banner"
+ >
{/* Title */}
- <h1 className="text-lg font-semibold">内容库</h1>
+ <h1 className="text-lg font-semibold" id="library-title">
+ {title}
+ </h1>
</header>
);
};Consider extracting the title to a translation system for better i18n support.
🤖 Prompt for AI Agents
In frontend/app/content-library/components/LibraryHeader.tsx around lines 7 to
10, the header uses a hardcoded Chinese title "内容库" which lacks
internationalization support and accessibility improvements. Replace the
hardcoded text with a translatable string using the app's i18n system, ensure
the heading element fits the proper semantic hierarchy, and modify the component
to accept props for title customization to improve flexibility.
| > | ||
| <ArrowLeft className="h-4 w-4 mr-2" /> | ||
| Back to Library | ||
| <ArrowLeft className="h-4 w-4" /> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add accessible label for back button
The back button only shows an icon without text, which may not be accessible for screen reader users.
<Button
variant="ghost"
size="sm"
onClick={() => router.push("/content-library")}
+ aria-label="返回内容库"
>
<ArrowLeft className="h-4 w-4" />
</Button>📝 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.
| <ArrowLeft className="h-4 w-4" /> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => router.push("/content-library")} | |
| aria-label="返回内容库" | |
| > | |
| <ArrowLeft className="h-4 w-4" /> | |
| </Button> |
🤖 Prompt for AI Agents
In frontend/app/content-library/reader/[id]/ClientContent.tsx at line 433, the
back button uses only an icon without an accessible label, which is not screen
reader friendly. Add an aria-label attribute to the ArrowLeft component or its
button wrapper with a descriptive label like "Go back" to improve accessibility
for screen reader users.
| useEffect(() => { | ||
| if (item) { | ||
| // 只有当传入的 item 与栈顶的 item 内容不同时才添加新面板 | ||
| if (item.id !== panels[panels.length - 1]?.item.id) { | ||
| panelIdCounter++; | ||
| const newPanels = [...panels, { id: panelIdCounter, item: item }].slice( | ||
| -2, | ||
| ); | ||
| setPanels(newPanels); | ||
| } | ||
| } | ||
| }, [item, panels]); |
There was a problem hiding this comment.
Fix useEffect dependency to prevent potential infinite loops
The effect depends on panels but also modifies it, which could cause unexpected re-renders.
Update the dependency check to use only the last panel's ID:
if (item) {
// 只有当传入的 item 与栈顶的 item 内容不同时才添加新面板
- if (item.id !== panels[panels.length - 1]?.item.id) {
- panelIdCounter++;
- const newPanels = [...panels, { id: panelIdCounter, item: item }].slice(
+ const lastPanelItemId = panels[panels.length - 1]?.item.id;
+ if (item.id !== lastPanelItemId) {
+ const newPanels = [...panels, { id: Date.now(), item: item }].slice(
-2,
);
setPanels(newPanels);
}
}
- }, [item, panels]);
+ }, [item, panels.length > 0 ? panels[panels.length - 1].item.id : null]);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/app/content-library/components/ContentPreview.tsx around lines 24 to
35, the useEffect hook depends on the entire panels array but also updates it,
which can cause infinite re-renders. To fix this, change the dependency array to
include only the ID of the last panel instead of the whole panels array, so the
effect only runs when the last panel changes.
| item: ContentItemPublic; | ||
| } | ||
|
|
||
| let panelIdCounter = 0; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid module-level mutable state for better SSR compatibility
Using a module-level counter can cause issues in server-side rendering or when the module is re-imported. Consider using a more robust ID generation strategy.
Replace the module-level counter with a timestamp-based or UUID approach:
-let panelIdCounter = 0;Then update the panel creation logic:
- panelIdCounter++;
- const newPanels = [...panels, { id: panelIdCounter, item: item }].slice(
+ const newPanels = [...panels, { id: Date.now(), item: item }].slice(🤖 Prompt for AI Agents
In frontend/app/content-library/components/ContentPreview.tsx at line 15,
replace the module-level mutable variable panelIdCounter with a safer ID
generation method such as using a timestamp or UUID to avoid issues in
server-side rendering or module re-imports. Remove the let panelIdCounter
declaration and update the code that assigns IDs to panels to generate unique
IDs dynamically using the new approach.
| const preview = | ||
| (typeof content === "string" && content) || | ||
| (contentObj?.analysis_result && | ||
| typeof contentObj.analysis_result === "string" | ||
| ? (contentObj.analysis_result as string) | ||
| : null) || | ||
| (contentObj?.raw_text && typeof contentObj.raw_text === "string" | ||
| ? (contentObj.raw_text as string) | ||
| : null) || | ||
| (isObject ? JSON.stringify(content).substring(0, 150) : null); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Extract complex preview logic to a utility function
The preview extraction logic is complex and would benefit from being in a separate utility function for better maintainability and testability.
Create a utility function:
function extractPreviewText(content: unknown): string | null {
if (typeof content === "string") return content;
if (typeof content === "object" && content !== null) {
const obj = content as Record<string, unknown>;
// Try common fields first
if (typeof obj.analysis_result === "string") return obj.analysis_result;
if (typeof obj.raw_text === "string") return obj.raw_text;
// Fallback to JSON (but limit length)
return JSON.stringify(content).substring(0, 150);
}
return null;
}Then simplify the component:
- const preview =
- (typeof content === "string" && content) ||
- (contentObj?.analysis_result &&
- typeof contentObj.analysis_result === "string"
- ? (contentObj.analysis_result as string)
- : null) ||
- (contentObj?.raw_text && typeof contentObj.raw_text === "string"
- ? (contentObj.raw_text as string)
- : null) ||
- (isObject ? JSON.stringify(content).substring(0, 150) : null);
+ const preview = extractPreviewText(content);🤖 Prompt for AI Agents
In frontend/app/content-library/components/AIAnalysisCard.tsx around lines 29 to
39, the preview extraction logic is complex and embedded directly in the
component, reducing maintainability. Refactor by creating a separate utility
function named extractPreviewText that takes the content as input and returns
the preview string or null. This function should handle string content directly,
check for analysis_result and raw_text fields if the content is an object, and
fallback to a truncated JSON string if needed. Replace the existing inline
preview logic with a call to this new utility function to simplify the
component.
| contentCache.setMarkdownContent(i.id, data.markdown_content); | ||
| } | ||
| setPrefetchStats((p) => ({ ...p, cached: p.cached + 1 })); | ||
| } catch {} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add error logging for debugging
Empty catch blocks make debugging difficult. At minimum, log errors to console in development.
- } catch {}
+ } catch (err) {
+ console.debug(`Prefetch failed for content ${i.id}:`, err);
+ }📝 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.
| } catch {} | |
| } catch (err) { | |
| console.debug(`Prefetch failed for content ${i.id}:`, err); | |
| } |
🤖 Prompt for AI Agents
In frontend/app/content-library/hooks/useContentItems.ts at line 192, the catch
block is empty, which hinders debugging. Modify the catch block to log the
caught error to the console, but only do this logging when in development mode
to avoid exposing errors in production.
| {false && ( | ||
| <div className="flex flex-col md:flex-row gap-4 items-start md:items-center justify-between px-6 pt-4 pb-2"> | ||
| {/* 搜索框 */} | ||
| <div className="relative w-full"> | ||
| <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | ||
| <Input | ||
| placeholder="搜索标题或摘要..." | ||
| value={searchQuery} | ||
| onChange={(e) => setSearchQuery(e.target.value)} | ||
| className="pl-10 h-9" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Content Preview */} | ||
| <div className="lg:col-span-1"> | ||
| <Card className="sticky top-6 border-0 shadow-lg"> | ||
| <CardHeader className="pb-4"> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <FileText className="h-5 w-5 text-primary" /> | ||
| 内容预览 | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| {selectedItem ? ( | ||
| <div className="space-y-6"> | ||
| <div> | ||
| <h3 className="font-semibold mb-3 text-lg"> | ||
| {selectedItem.title || "无标题"} | ||
| </h3> | ||
| <div className="flex items-center gap-2 mb-4 flex-wrap"> | ||
| <div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center"> | ||
| {getContentIcon(selectedItem.type)} | ||
| </div> | ||
| <Badge variant="outline" className="text-xs"> | ||
| {selectedItem.type.toUpperCase()} | ||
| </Badge> | ||
| <ProcessingStatusBadge | ||
| status={ | ||
| selectedItem.processing_status as ProcessingStatus | ||
| } | ||
| size="sm" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-4"> | ||
| <div> | ||
| <label className="text-sm font-medium text-muted-foreground block mb-2"> | ||
| 摘要 | ||
| </label> | ||
| <p className="text-sm leading-relaxed bg-muted/30 p-3 rounded-lg"> | ||
| {selectedItem.summary || "暂无摘要"} | ||
| </p> | ||
| </div> | ||
|
|
||
| {selectedItem.source_uri && ( | ||
| <div> | ||
| <label className="text-sm font-medium text-muted-foreground block mb-2"> | ||
| 来源 | ||
| </label> | ||
| <p className="text-sm break-all bg-muted/30 p-3 rounded-lg"> | ||
| <a | ||
| href={selectedItem.source_uri} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-primary hover:underline" | ||
| > | ||
| {selectedItem.source_uri} | ||
| </a> | ||
| </p> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="grid grid-cols-2 gap-4 text-sm"> | ||
| <div> | ||
| <label className="text-muted-foreground block mb-1"> | ||
| 创建时间 | ||
| </label> | ||
| <div className="flex items-center gap-1"> | ||
| <Calendar className="h-3 w-3" /> | ||
| {new Date( | ||
| selectedItem.created_at, | ||
| ).toLocaleDateString("zh-CN")} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <label className="text-muted-foreground block mb-1"> | ||
| 更新时间 | ||
| </label> | ||
| <div className="flex items-center gap-1"> | ||
| <Clock className="h-3 w-3" /> | ||
| {new Date( | ||
| selectedItem.updated_at, | ||
| ).toLocaleDateString("zh-CN")} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-3"> | ||
| <Button | ||
| onClick={() => handleOpenReader(selectedItem)} | ||
| className="w-full h-11 bg-gradient-to-r from-primary to-primary/80 hover:from-primary/90 hover:to-primary/70" | ||
| disabled={ | ||
| selectedItem.processing_status !== "completed" | ||
| } | ||
| > | ||
| <BookOpen className="mr-2 h-4 w-4" /> | ||
| 阅读内容 | ||
| </Button> | ||
| <div className="grid grid-cols-2 gap-2"> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={() => handleShare(selectedItem)} | ||
| className="h-9" | ||
| > | ||
| <Share2 className="mr-1 h-3 w-3" /> | ||
| 分享 | ||
| </Button> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={() => handleDownload(selectedItem)} | ||
| className="h-9" | ||
| > | ||
| <Download className="mr-1 h-3 w-3" /> | ||
| 下载 | ||
| </Button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* AI 智能分析 */} | ||
| <AIAnalysisCard analysis={selectedItem.ai_analysis} /> | ||
| </div> | ||
| ) : ( | ||
| <div className="text-center py-12"> | ||
| <FileText className="h-12 w-12 mx-auto text-muted-foreground opacity-50 mb-4" /> | ||
| <div className="space-y-2"> | ||
| <p className="text-sm text-muted-foreground"> | ||
| 悬停内容卡片查看预览 | ||
| </p> | ||
| <p className="text-xs text-muted-foreground/70"> | ||
| 点击卡片直接开始阅读 | ||
| </p> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| {/* 状态筛选 */} | ||
| <Select value={statusFilter} onValueChange={setStatusFilter}> | ||
| <SelectTrigger className="min-w-[120px]"> | ||
| <SelectValue placeholder="所有状态" /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value="all">所有状态</SelectItem> | ||
| <SelectItem value="pending">待处理</SelectItem> | ||
| <SelectItem value="processing">处理中</SelectItem> | ||
| <SelectItem value="completed">已完成</SelectItem> | ||
| <SelectItem value="failed">失败</SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
|
|
||
| {/* 类型筛选 */} | ||
| <Select value={typeFilter} onValueChange={setTypeFilter}> | ||
| <SelectTrigger className="min-w-[120px]"> | ||
| <SelectValue placeholder="所有类型" /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value="all">所有类型</SelectItem> | ||
| <SelectItem value="pdf">PDF</SelectItem> | ||
| <SelectItem value="url">网页</SelectItem> | ||
| <SelectItem value="text">文本</SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove dead code or use proper feature flag
Large blocks of code hidden with false && should be removed or controlled with a proper feature flag.
Either remove this entire block if the search/filter functionality is no longer needed, or use a proper feature flag:
- {/* 筛选控件(暂时隐藏) */}
- {false && (
+ {/* 筛选控件 */}
+ {process.env.NEXT_PUBLIC_ENABLE_CONTENT_FILTERS === 'true' && (Or simply remove the entire block if it's not planned for future use.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/app/content-library/page.tsx around lines 74 to 114, there is a
large block of JSX code wrapped in `false &&` which effectively disables it as
dead code. To fix this, either remove the entire block if the search and filter
UI is not needed, or replace the `false` condition with a proper feature flag
variable that controls the visibility of this UI. This ensures the code is
either cleanly removed or conditionally rendered based on a meaningful flag.
- 修改了 `package.json` 中的 `generate-client` 命令,添加了删除 `index.ts` 文件中对 `types` 和 `utils` 的导入的操作。 - 该更改有助于保持代码的整洁性,确保生成的客户端代码反映实际需求。
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
admin/package.json(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: test-playwright (1, 4)
- GitHub Check: test-playwright (2, 4)
- GitHub Check: test-playwright (4, 4)
- GitHub Check: test-playwright (3, 4)
- GitHub Check: test-backend
- GitHub Check: Complete CI/CD Pipeline
| "test:ui": "playwright test --ui", | ||
| "format": "biome format --write ./", | ||
| "generate-client": "openapi-ts" | ||
| "generate-client": "openapi-ts && sed -i -e '/from \".\\/types\"/d' -e '/from \".\\/utils\"/d' src/client/index.ts" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make generate-client script cross-platform and maintainable
The inline sed -i hack is brittle on macOS/Windows and depends on GNU sed. Extracting this logic into a Node script or using a cross-platform tool ensures consistent behavior across environments.
Apply this diff:
--- a/admin/package.json
+++ b/admin/package.json
@@ "scripts": {
- "generate-client": "openapi-ts && sed -i -e '/from \".\\/types\"/d' -e '/from \".\\/utils\"/d' src/client/index.ts"
+ "generate-client": "openapi-ts && node scripts/remove-client-exports.cjs"Then add scripts/remove-client-exports.cjs:
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const filePath = path.resolve(__dirname, '..', 'src', 'client', 'index.ts');
const filtered = fs
.readFileSync(filePath, 'utf8')
.split('\n')
.filter(line => !/from \.\/(types|utils)/.test(line))
.join('\n');
fs.writeFileSync(filePath, filtered, 'utf8');
console.log('✅ Removed types/utils exports from client index.');🤖 Prompt for AI Agents
In admin/package.json at line 14, the "generate-client" script uses an inline
sed command that is not cross-platform and can fail on macOS or Windows. To fix
this, replace the sed command with a call to a new Node.js script that reads
src/client/index.ts, removes lines importing from "./types" or "./utils", and
writes the filtered content back. Create the script as
scripts/remove-client-exports.cjs with the provided logic to ensure consistent
behavior across environments.
7ddbe94
User description
🫰 Thanks for your Pull Request! Here are some helpful tips:
👀 Purpose and Importance of this PR:
🅰 Fixes for Issues:
Fixes #44 #189 #190 #186
📝 Notes for the Reviewer:
🎯 How to Verify this PR:
📑 Additional Documentation, e.g., KEPs (Telepace Enhancement Proposals), Usage Docs, etc.:
PR Type
Enhancement, Tests
Description
• Major UI overhaul for content library with new two-column layout architecture
• Complete refactor from monolithic component to modular design using custom hooks
• Added comprehensive content management with
useContentItemshook for state, caching, and real-time updates• Introduced new components:
ContentCard,ContentList,ContentPreview,AIAnalysisCard, andLibraryHeader• Implemented Material Design ripple effects and smooth panel transitions using Framer Motion
• Updated CSS design system with Linear design tokens and comprehensive color palettes
• Simplified UI by removing search/filter controls, sharing functionality, and user management features
• Enhanced layout components with cleaner styling and reduced visual clutter
• Updated tests to reflect new architecture and UI behavior
• Added TypeScript interfaces for content library data structures
Changes walkthrough 📝
19 files
useContentItems.ts
Add comprehensive content library state management hookfrontend/app/content-library/hooks/useContentItems.ts
• Created a comprehensive custom hook for managing content library
state and operations
• Implements content fetching, filtering,
caching, and real-time updates via SSE
• Includes navigation state
persistence and content prefetching functionality
• Handles
authentication, error states, and keyboard shortcuts
ripple.ts
Add Material Design ripple effect utilityfrontend/app/content-library/utils/ripple.ts
• Created utility function for generating Material Design-style ripple
effects
• Handles click position calculation and dynamic ripple
element creation
• Includes CSS animation and automatic cleanup after
animation completion
types.ts
Define content library TypeScript interfacesfrontend/app/content-library/types.ts
• Defined TypeScript interface for
ContentItemPublicwithcomprehensive properties
• Includes AI analysis structure with
summarizer and key points extractor types
• Supports dynamic analysis
types and nested content structures
globals.css
Major CSS design system update with Linear palettefrontend/app/globals.css
• Updated CSS variables for background and foreground colors using
Linear design tokens
• Added extensive custom color palettes including
Tailwind, Notion, macOS, and Linear themes
• Introduced new utility
classes for layout widths, shadows, borders, and scrollbar hiding
•
Added support for both light and dark color schemes with comprehensive
token definitions
page.tsx
Major refactor to clean two-column layout architecturefrontend/app/content-library/page.tsx
• Completely refactored from complex monolithic component to clean
two-column layout
• Replaced inline logic with custom hook
(
useContentItems) for better separation of concerns• Simplified UI to
focus on content list and preview with removed search/filter controls
• Integrated new
ContentListandContentPreviewcomponents for modulardesign
enhanced-llm-analysis-sidebar.tsx
Simplify LLM analysis sidebar to analysis-only viewfrontend/components/ui/enhanced-llm-analysis-sidebar.tsx
• Simplified sidebar by removing conversation history tab and related
functionality
• Streamlined header design and removed clear all
functionality
• Updated layout to focus solely on analysis view with
cleaner UI
• Removed complex tab switching logic and conversation
selection handlers
ClientContent.tsx
Simplify content reader interface and remove sharingfrontend/app/content-library/reader/[id]/ClientContent.tsx
• Removed sharing functionality and related modal components
•
Simplified header layout with cleaner design and reduced visual
clutter
• Removed content type indicators and processing status badges
• Streamlined content rendering by removing gradient backgrounds and
decorative elements
ContentPreview.tsx
Add animated content preview component with panel transitionsfrontend/app/content-library/components/ContentPreview.tsx
• Created new component for animated content preview with panel
stacking
• Implements smooth transitions using Framer Motion for panel
animations
• Includes content details display with AI analysis
integration
• Features responsive design with scroll management and
keyboard navigation
MainLayout.tsx
Simplify main layout by removing user management UIfrontend/components/layout/MainLayout.tsx
• Removed user avatar dropdown and authentication sync functionality
•
Simplified layout by removing user status indicators and sync buttons
• Cleaned up header area and removed complex user management UI
•
Added
page-top-highlightclass for visual enhancementAIAnalysisCard.tsx
Add dedicated AI analysis display componentfrontend/app/content-library/components/AIAnalysisCard.tsx
• Created dedicated component for displaying AI analysis results
•
Supports multiple analysis types including summarizer and key points
extractor
• Features clean card-based layout with icons and structured
content display
• Handles dynamic analysis types with fallback
rendering
prompt-recommendations.tsx
Update prompt recommendations to horizontal scrollable layoutfrontend/components/ui/prompt-recommendations.tsx
• Changed layout from vertical grid to horizontal scrollable layout
•
Updated button styling with rounded corners and improved spacing
•
Commented out loading indicator to reduce visual clutter
• Enhanced
responsive design for better mobile experience
ContentCard.tsx
Add ContentCard component for content library displayfrontend/app/content-library/components/ContentCard.tsx
• New React component for displaying content items in a card format
•
Implements selection state, click handling, and keyboard navigation
•
Includes content type icons (PDF, URL, text) and processing status
badges
• Features ripple effect on mouse down and responsive design
ProcessingStatusBadge.tsx
Simplify ProcessingStatusBadge styling to neutral themefrontend/components/ui/ProcessingStatusBadge.tsx
• Simplified status badge styling by removing colored backgrounds and
borders
• Changed all status indicators to use neutral gray text color
• Updated default
showTextprop fromtruetofalse• Refactored icon
sizing logic with dedicated size mapping
AppSidebar.tsx
Update sidebar styling and navigation structurefrontend/components/layout/AppSidebar.tsx
• Changed Upload Content icon from
IconCirclePlusFilledtoIconCirclePlus• Removed primary button styling from Upload Content
button
• Adjusted sidebar header padding and reorganized navigation
structure
• Moved Upload Content into main navigation group
ContentList.tsx
Add ContentList component for content library layoutfrontend/app/content-library/components/ContentList.tsx
• New React component for rendering a list of content items
•
Integrates
ContentCardcomponents with separators between items•
Handles item selection and content prefetching functionality
•
Includes responsive separator styling with calculated widths
VirtualScrollRenderer.tsx
Remove borders and backgrounds from VirtualScrollRendererfrontend/components/ui/VirtualScrollRenderer.tsx
• Removed border styling from main container element
• Removed border
and background styling from chunk items
• Simplified visual appearance
by eliminating gray borders and backgrounds
llm-analysis-card.tsx
Hide AI analysis loading indicatorfrontend/components/ui/llm-analysis-card.tsx
• Commented out loading indicator with spinner and text
• Removed "AI
正在分析中..." loading message display
• Kept streaming content rendering
functionality intact
ReaderLayout.tsx
Add background styling to reader layout panelfrontend/components/layout/ReaderLayout.tsx
• Added
bg-muted/30background class to resizable panel• Applied
subtle background styling to the analysis sidebar area
LibraryHeader.tsx
Add LibraryHeader component for content library pagefrontend/app/content-library/components/LibraryHeader.tsx
• New React component for content library page header
• Simple header
with "内容库" (Content Library) title
• Includes responsive padding and
background styling
1 files
page.test.tsx
Update tests for new content library layoutfrontend/tests/app/content-library/page.test.tsx
• Updated tests to reflect new two-column layout and removed search
functionality
• Added mock for
scrollTofunction to support JSDOMtesting environment
• Modified test expectations to match new UI
behavior with preview panels
• Updated navigation tests to account for
new click-to-focus then click-to-navigate pattern
2 files
Summary by CodeRabbit
New Features
Refactor
Style
Bug Fixes
Chores