Skip to content

fix(DiscoveryView): 移除分页自动加载逻辑并优化滚动行为#97

Merged
AmintaCCCP merged 8 commits intoAmintaCCCP:mainfrom
SummerRay160:Dev
Apr 24, 2026
Merged

fix(DiscoveryView): 移除分页自动加载逻辑并优化滚动行为#97
AmintaCCCP merged 8 commits intoAmintaCCCP:mainfrom
SummerRay160:Dev

Conversation

@SummerRay160
Copy link
Copy Markdown
Contributor

@SummerRay160 SummerRay160 commented Apr 23, 2026

refactor(RepositoryEditModal): 修复自定义描述为空时的处理逻辑

“趋势”页移除分页加载功能

Summary by CodeRabbit

  • New Features

    • Discovery list now uses an incremental "Load more" flow with live loaded/total counts, a single Load More control, spinner, retryable error state, and scroll anchoring after appends.
    • Settings add an "OpenAI Compatible (Custom Endpoint)" option and show the computed final request URL when an API base is provided.
  • Bug Fixes

    • Repository edit dialog treats null/empty descriptions consistently, preventing save/display errors.

refactor(RepositoryEditModal): 修复自定义描述为空时的处理逻辑
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces page-based pagination in Discovery with an incremental "load more" flow and per-channel load-more loading/error state; makes RepositoryEditModal null-safe for custom_description and description trimming; centralizes API URL building and adds an openai-compatible API type across server, service, and settings.

Changes

Cohort / File(s) Summary
Discovery view & UI
src/components/DiscoveryView.tsx
Replaced page-based navigation with append-style "load more": removed page state/ITEMS_PER_PAGE slicing and pagination components, added LoadMoreButton/DataStats, updated AI/empty-state logic to use accumulated allRepos, added append-specific loading/error state and scroll anchoring after appends.
Repository edit modal
src/components/RepositoryEditModal.tsx
Null-safe handling for custom_description and description trimming: treat null as "not provided" and use `(formData.description
Store: discovery paging state & persistence
src/store/useAppStore.ts
Added per-channel discoveryIsLoadingMore and discoveryLoadMoreError state and actions (setDiscoveryLoadingMore, setDiscoveryLoadMoreError); setSelectedDiscoveryChannel now resets per-channel discovery maps; normalized rehydration/persistence and included discoveryChannels in persisted keys.
Types
src/types/index.ts
Added openai-compatible to AIApiType and added discoveryIsLoadingMore: Record<DiscoveryChannelId, boolean> and `discoveryLoadMoreError: Record<DiscoveryChannelId, string
AI service, server proxy, and URL builder
src/services/aiService.ts, server/src/routes/proxy.ts, src/utils/apiUrlBuilder.ts, src/components/settings/AIConfigPanel.tsx
Centralized API URL construction (buildApiUrl, buildFinalApiUrl), added openai-compatible handling across aiService and proxy (treat base URL as full endpoint for compatible mode), adjusted settings UI to show computed final request URL and updated placeholders/text.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DiscoveryView as DiscoveryView (UI)
  participant ChannelSvc as ChannelService / refreshChannel
  participant Store as App Store

  rect rgba(135,206,250,0.5)
    User->>DiscoveryView: Click "Load more"
    DiscoveryView->>DiscoveryView: check discoveryIsLoadingMore / discoveryHasMore
    DiscoveryView->>ChannelSvc: refreshChannel(channelId, nextPage, append=true)
    ChannelSvc->>Store: request next page repos
    Store-->>ChannelSvc: return repos + hasMore/nextPage
    ChannelSvc-->>DiscoveryView: deliver repos, hasMore, nextPage
    DiscoveryView->>DiscoveryView: append to allRepos, set discoveryIsLoadingMore/LoadMoreError
    DiscoveryView->>DiscoveryView: locate repo at prev-count index and scroll into view
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop and I gather, one extra load more,
I stack all the repos and nudge open the door.
Null crumbs of descriptions I sweep from the trail,
I scroll to the spot and follow the trail.
A twitch, a soft thump — the list grows some more.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title is in Chinese and refers to removing pagination auto-load logic and optimizing scroll behavior in DiscoveryView, but the raw_summary shows that a critical reviewer issue exists: removing auto-load in handlePageChange causes the trending page to show empty lists when users navigate to page 2, since getTrendingRepositories slices data by page/perPage. The title accurately describes the intended changes but does not reflect the breaking issue identified in code review. Update the title to reflect the actual status of the changes. The PR removes pagination but introduces a breaking issue with data loading that requires resolution before merge. Consider revising to indicate this is a work-in-progress fix pending implementation of either pagination auto-load restoration or full data fetching.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/RepositoryEditModal.tsx (1)

748-748: Inconsistent null-safety in this branch.

Every other formData.description access in this PR was hardened with (formData.description || '').trim(), but this condition still calls formData.description.trim() directly. Since formData.description is initialized as '' and typed as string, none of these guards are strictly required, but if you intend to keep them defensively, this branch should match for consistency (and to avoid a future TS-strict crash if the field type ever widens to string | null).

Proposed change
-          ) : editIntent.description === 'keep-custom' && formData.description.trim() !== '' && !customStatus.description ? (
+          ) : editIntent.description === 'keep-custom' && (formData.description || '').trim() !== '' && !customStatus.description ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryEditModal.tsx` at line 748, The condition in
RepositoryEditModal's JSX branch uses formData.description.trim() directly which
is inconsistent with the defensive null-safety used elsewhere; update that check
to use (formData.description || '').trim() so the condition reads
editIntent.description === 'keep-custom' && (formData.description || '').trim()
!== '' && !customStatus.description, matching the other guarded accesses and
preventing future TS-strict issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 938-950: The handlePageChange currently only calls setCurrentPage,
which can move to pages without loaded data because totalPages uses
currentTotalCount while currentPageRepos slices allRepos; update
handlePageChange to either (A) trigger loading the missing page: call the
existing fetch/load function (e.g., fetchReposForPage or loadMoreRepos) with the
target page when the requested slice is not present, then setCurrentPage after
the data resolves, or (B) constrain page selection to loaded data by checking
whether allRepos contains items for the requested page (compute expected
start/end index from PAGE_SIZE) and if not, prevent changing page (or clamp to
maxLoadedPage) so currentPageRepos never becomes empty; reference
handlePageChange, currentPageRepos, allRepos, totalPages, currentTotalCount, and
your repo-fetching function when implementing the fix.

---

Nitpick comments:
In `@src/components/RepositoryEditModal.tsx`:
- Line 748: The condition in RepositoryEditModal's JSX branch uses
formData.description.trim() directly which is inconsistent with the defensive
null-safety used elsewhere; update that check to use (formData.description ||
'').trim() so the condition reads editIntent.description === 'keep-custom' &&
(formData.description || '').trim() !== '' && !customStatus.description,
matching the other guarded accesses and preventing future TS-strict issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfaa4876-ca4c-4e91-94cd-5c96f29361ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef0131 and 40726e2.

📒 Files selected for processing (2)
  • src/components/DiscoveryView.tsx
  • src/components/RepositoryEditModal.tsx

Comment thread src/components/DiscoveryView.tsx Outdated
@SummerRay160 SummerRay160 marked this pull request as draft April 23, 2026 12:45
@SummerRay160 SummerRay160 marked this pull request as ready for review April 23, 2026 12:49
移除传统的分页组件,改为实现无限滚动加载模式
新增加载更多按钮组件,优化大数据量下的用户体验
简化页码管理逻辑,减少不必要的状态和计算
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/DiscoveryView.tsx (1)

638-652: ⚠️ Potential issue | 🟡 Minor

Stale "current page" copy and scope change in handleAnalyzePage after pagination removal.

Two concerns in this block:

  1. Line 650 still says 当前页面所有项目已完成AI分析 / All projects on current page have been analyzed, but there is no "current page" anymore — pageRepos is now the full allRepos set. Update the string to reflect "all loaded projects" (or equivalent).
  2. Behavior change worth calling out: previously this button analyzed only the visible page slice; now it analyzes every loaded repo across all fetched pages. Once a user clicks "加载更多" several times, a single AI Analyze click can fan out into many concurrent API calls / much higher token spend than before. Consider either renaming/relabeling the button (line 940 AI分析 / AI Analyze) to make the new scope explicit, or constraining analysis to a bounded batch.
💡 Minimal copy fix
-    if (unanalyzed.length === 0) {
-      alert(t('当前页面所有项目已完成AI分析。', 'All projects on current page have been analyzed.'));
-      return;
-    }
+    if (unanalyzed.length === 0) {
+      alert(t('已加载的所有项目均已完成AI分析。', 'All loaded projects have been analyzed.'));
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 638 - 652, The copy and scope
in handleAnalyzePage are stale: update the user-facing string that currently
reads "当前页面所有项目已完成AI分析" / "All projects on current page have been analyzed" to
reflect the full loaded set (e.g., "所有已加载项目已完成AI分析" / "All loaded projects have
been analyzed") because pageRepos now equals allRepos; then address the scope
change by either (A) renaming the AI Analyze button label referenced as "AI分析"
to clarify it runs across all loaded repos, or (B) constrain handleAnalyzePage
to operate on a bounded batch (slice) instead of the entire allRepos/unanalyzed
set to avoid unbounded concurrent API calls — update the related symbols
pageRepos, allRepos, unanalyzed, and the handler handleAnalyzePage accordingly.
🧹 Nitpick comments (1)
src/components/DiscoveryView.tsx (1)

1101-1114: Remove the leftover "Page Info" block — it duplicates DataStats and has a dangling empty column.

The counter on lines 1106-1111 (共 N 个项目 / Total N items) is already rendered by DataStats in the toolbar (line 943) and again inside LoadMoreButton (line 351-358). On top of the duplication, the sibling <div> that used to hold the "more available" pill is now empty (lines 1112-1113), so justify-between leaves an orphaned whitespace gap on the right. Drop the entire block.

♻️ Proposed cleanup
-            {/* Page Info */}
-            {allRepos.length > 0 && (
-              <div className={isDesktopSafeMode
-                ? 'flex items-center justify-between py-3.5 px-5 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 text-sm'
-                : 'flex items-center justify-between py-3.5 px-5 bg-gradient-to-r from-gray-50 to-slate-50 dark:from-gray-800/60 dark:to-slate-800/40 rounded-xl border border-gray-100 dark:border-gray-700/50 text-sm'}>
-                <div className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
-                  <div className="w-1.5 h-1.5 rounded-full bg-blue-500" />
-                  <span>
-                    {t('共', 'Total')} <strong className="text-gray-900 dark:text-white">{allRepos.length}</strong> {t('个项目', 'items')}
-                  </span>
-                </div>
-
-              </div>
-            )}
-
             {/* Load More Button */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 1101 - 1114, Remove the
redundant "Page Info" JSX block that displays "共 N 个项目" — it duplicates
DataStats (rendered in the toolbar) and LoadMoreButton counts; specifically
delete the conditional block that checks allRepos.length > 0 and its inner div
(the fragment with the two className variants and the span showing the total),
which also contains the now-empty right-hand column causing layout gap; after
removal ensure no remaining orphaned markup or styles referencing that block
remain in DiscoveryView.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 638-652: The copy and scope in handleAnalyzePage are stale: update
the user-facing string that currently reads "当前页面所有项目已完成AI分析" / "All projects on
current page have been analyzed" to reflect the full loaded set (e.g.,
"所有已加载项目已完成AI分析" / "All loaded projects have been analyzed") because pageRepos
now equals allRepos; then address the scope change by either (A) renaming the AI
Analyze button label referenced as "AI分析" to clarify it runs across all loaded
repos, or (B) constrain handleAnalyzePage to operate on a bounded batch (slice)
instead of the entire allRepos/unanalyzed set to avoid unbounded concurrent API
calls — update the related symbols pageRepos, allRepos, unanalyzed, and the
handler handleAnalyzePage accordingly.

---

Nitpick comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 1101-1114: Remove the redundant "Page Info" JSX block that
displays "共 N 个项目" — it duplicates DataStats (rendered in the toolbar) and
LoadMoreButton counts; specifically delete the conditional block that checks
allRepos.length > 0 and its inner div (the fragment with the two className
variants and the span showing the total), which also contains the now-empty
right-hand column causing layout gap; after removal ensure no remaining orphaned
markup or styles referencing that block remain in DiscoveryView.tsx.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 347aafd6-f19f-491f-8c5c-c0cbf7208408

📥 Commits

Reviewing files that changed from the base of the PR and between 40726e2 and 1465a90.

📒 Files selected for processing (1)
  • src/components/DiscoveryView.tsx

@AmintaCCCP
Copy link
Copy Markdown
Owner

PR Review

@SummerRay160 感谢修复 modal 的空值处理问题。不过 DiscoveryView 的改动有一个关键问题需要解决:

🔴 问题:分页切换后数据加载逻辑被移除

PR #96 引入了 RSS 数据源的趋势页功能,该功能需要分页加载

// getTrendingRepositories (PR #96)
const startIndex = (page - 1) * perPage;  // 按页切片
const endIndex = Math.min(startIndex + perPage, items.length);

PR #97 删除了 handlePageChange 中的自动加载逻辑:

// 删除的代码(导致分页后数据无法加载)
if (allRepos.length < requiredItems && currentHasMore && !currentIsLoading) {
    refreshChannel(selectedDiscoveryChannel, currentNextPage, true);
}

后果:趋势页初始只加载 20 条数据,用户切到第 2 页时列表为空(数据未加载)。

✅ 建议修复

方案 1:保留分页数据加载(推荐)

const handlePageChange = useCallback((page: number) => {
    setCurrentPage(page);
    
    // 保留数据加载检查
    const requiredItems = page * ITEMS_PER_PAGE;
    if (allRepos.length < requiredItems && currentHasMore && !currentIsLoading) {
        refreshChannel(selectedDiscoveryChannel, currentNextPage, true);
    }
    
    // 保留滚动到顶部
    scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
}, [allRepos.length, currentHasMore, currentIsLoading, currentNextPage, 
    refreshChannel, selectedDiscoveryChannel]);

方案 2:RSS 数据量不大(~100条),可一次性全部加载

如果 RSS 数据量可控,可以在 refreshChannel 中一次性拉取全部数据,然后前端纯分页:

// 读取整个 RSS 而非分页
const repos = await githubApi.getTrendingRepositories(..., 1, 999);

🟢 RepositoryEditModal 的修复 ✅

custom_description 的 null-safety 修复是正确的,这部分没有问题。

请确认下 DiscoveryView 的修复方案,我这边再跑一遍测试。

@SummerRay160
Copy link
Copy Markdown
Contributor Author

SummerRay160 commented Apr 23, 2026

在新的更改中移除了分页功能 增加了增量加载功能
这样避免了很多性能问题

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/DiscoveryView.tsx (1)

648-662: ⚠️ Potential issue | 🟡 Minor

Stale "current page" wording and silently broader analysis scope after the load-more refactor.

pageRepos now aliases allRepos, so clicking AI Analyze will analyze every repo that has been accumulated via repeated Load More clicks (potentially hundreds), not a single page's 20. Two concrete issues:

  • Line 660 still says 当前页面所有项目已完成AI分析 / All projects on current page have been analyzed. — there is no "current page" anymore. Please reword to reflect "已加载的所有项目 / all loaded projects".
  • The scope change can meaningfully inflate AI API usage/cost/latency vs. the previous per-page behavior. Consider either documenting this in the button tooltip (line 961 title={t('AI分析', 'Analyze with AI')} is generic) or confirming with a count before kicking off analysis on a large accumulated list.
✏️ Wording fix
-    if (unanalyzed.length === 0) {
-      alert(t('当前页面所有项目已完成AI分析。', 'All projects on current page have been analyzed.'));
-      return;
-    }
+    if (unanalyzed.length === 0) {
+      alert(t('已加载的所有项目均已完成AI分析。', 'All loaded projects have been analyzed.'));
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 648 - 662, The UI currently
aliases pageRepos = allRepos so the alerts and behavior reference a "current
page" but actually operate on all loaded repositories; update the user-facing
message in the unanalyzed-empty branch to say "已加载的所有项目已完成AI分析 / All loaded
projects have been analyzed" instead of "当前页面...", and add a confirmation or
explicit count before starting analysis when unanalyzed.length is large (e.g.,
show modal/confirm with unanalyzed.length) or update the AI Analyze button
tooltip/title (the prop using title={t('AI分析', 'Analyze with AI')}) to indicate
it will analyze all loaded projects; change references to pageRepos/unanalyzed
in the Analyze trigger logic so messaging and confirmation reflect the actual
scope.
🧹 Nitpick comments (3)
src/components/DiscoveryView.tsx (3)

761-782: Double-click race on Load More uses stale currentIsLoading.

currentIsLoading is captured from the store snapshot at render time, so two rapid clicks on the button can both pass the gate before React re-renders with the updated loading state, triggering duplicate refreshChannel calls for the same page. Reading the latest state synchronously via useAppStore.getState() (similarly to how the channel-switch effect at line 565-566 already does it) avoids this. The button's disabled={isLoading} already covers most UI cases, but the programmatic guard is still worth tightening.

🔒 Safer gate
   const handleLoadMore = useCallback(async () => {
-    if (!discoveryHasMore[selectedDiscoveryChannel]) {
+    const state = useAppStore.getState();
+    if (!state.discoveryHasMore[selectedDiscoveryChannel]) {
       return;
     }
-    
-    if (currentIsLoading) {
+
+    if (state.discoveryIsLoading[selectedDiscoveryChannel]) {
       return;
     }
-    
-    const nextPage = discoveryNextPage[selectedDiscoveryChannel];
+
+    const nextPage = state.discoveryNextPage[selectedDiscoveryChannel];
     if (!nextPage) {
       return;
     }
-    
+
     await refreshChannel(selectedDiscoveryChannel, nextPage, true);
-  }, [
-    discoveryHasMore,
-    discoveryNextPage,
-    selectedDiscoveryChannel,
-    currentIsLoading,
-    refreshChannel
-  ]);
+  }, [selectedDiscoveryChannel, refreshChannel]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 761 - 782, The handleLoadMore
callback uses the render-time snapshot currentIsLoading which can be stale on
rapid double clicks; update the guard to read the latest loading state
synchronously from the store (e.g. call useAppStore.getState().currentIsLoading)
before returning, while keeping the existing checks for
discoveryHasMore[selectedDiscoveryChannel],
discoveryNextPage[selectedDiscoveryChannel], and invoking
refreshChannel(selectedDiscoveryChannel, nextPage, true) if safe; ensure you
reference currentIsLoading only via the store read inside handleLoadMore so the
debounce gate is reliable even between renders.

1125-1138: Dead layout and duplicated count after removing pagination.

This "Page Info" footer block now just repeats the DataStats already rendered in the toolbar (lines 967–971), and the right-side <div> that used to host the pagination controls is gone, leaving the justify-between container with a single left item and empty whitespace on the right. Either remove this block entirely or drop the justify-between wrapper since there's no second child. The LoadMoreButton below also already shows 已加载 currentCount / totalCount, so the three surfaces overlap.

🧹 Proposed cleanup
-            {/* Page Info */}
-            {allRepos.length > 0 && (
-              <div className={isDesktopSafeMode
-                ? 'flex items-center justify-between py-3.5 px-5 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 text-sm'
-                : 'flex items-center justify-between py-3.5 px-5 bg-gradient-to-r from-gray-50 to-slate-50 dark:from-gray-800/60 dark:to-slate-800/40 rounded-xl border border-gray-100 dark:border-gray-700/50 text-sm'}>
-                <div className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
-                  <div className="w-1.5 h-1.5 rounded-full bg-blue-500" />
-                  <span>
-                    {t('共', 'Total')} <strong className="text-gray-900 dark:text-white">{allRepos.length}</strong> {t('个项目', 'items')}
-                  </span>
-                </div>
-
-              </div>
-            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 1125 - 1138, The "Page Info"
footer block rendering the total count duplicates DataStats and leaves an empty
right side after pagination removal; remove the entire conditional block that
uses allRepos and isDesktopSafeMode (the div starting with the Page Info
comment) so DataStats and LoadMoreButton remain the single sources of truth, or
alternatively replace the outer container's 'justify-between' with a single-side
layout and remove the duplicated count rendering to avoid overlap; locate
references in DiscoveryView.tsx to allRepos, the Page Info JSX, and
isDesktopSafeMode/DataStats/LoadMoreButton to apply the change.

1119-1123: Unbounded list rendering with the new infinite load-more.

With pagination removed, allRepos grows monotonically as users click Load More, and every item is mounted into the DOM. For channels with large total counts this can noticeably degrade scroll/analyze performance and memory over a session. Since scrollContainerRef is already a single scroll container, dropping in a windowing library like react-window or @tanstack/react-virtual would be a good follow-up. Not blocking, but worth tracking as the feature sees real use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 1119 - 1123, The allRepos list
is rendered unbounded (mapping to SubscriptionRepoCard) and will grow as users
click Load More, so replace the direct map with a virtualized list (e.g.,
react-window or `@tanstack/react-virtual`) to only mount visible items; keep using
scrollContainerRef as the scrollParent, set itemCount to allRepos.length, render
SubscriptionRepoCard inside the virtualized item renderer and pass
desktopSafeMode (isDesktopSafeMode) and key via the renderer, and ensure spacing
logic (isDesktopSafeMode ? 'space-y-3' : 'space-y-4') is preserved around the
virtualized container in DiscoveryView.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 648-662: The UI currently aliases pageRepos = allRepos so the
alerts and behavior reference a "current page" but actually operate on all
loaded repositories; update the user-facing message in the unanalyzed-empty
branch to say "已加载的所有项目已完成AI分析 / All loaded projects have been analyzed" instead
of "当前页面...", and add a confirmation or explicit count before starting analysis
when unanalyzed.length is large (e.g., show modal/confirm with
unanalyzed.length) or update the AI Analyze button tooltip/title (the prop using
title={t('AI分析', 'Analyze with AI')}) to indicate it will analyze all loaded
projects; change references to pageRepos/unanalyzed in the Analyze trigger logic
so messaging and confirmation reflect the actual scope.

---

Nitpick comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 761-782: The handleLoadMore callback uses the render-time snapshot
currentIsLoading which can be stale on rapid double clicks; update the guard to
read the latest loading state synchronously from the store (e.g. call
useAppStore.getState().currentIsLoading) before returning, while keeping the
existing checks for discoveryHasMore[selectedDiscoveryChannel],
discoveryNextPage[selectedDiscoveryChannel], and invoking
refreshChannel(selectedDiscoveryChannel, nextPage, true) if safe; ensure you
reference currentIsLoading only via the store read inside handleLoadMore so the
debounce gate is reliable even between renders.
- Around line 1125-1138: The "Page Info" footer block rendering the total count
duplicates DataStats and leaves an empty right side after pagination removal;
remove the entire conditional block that uses allRepos and isDesktopSafeMode
(the div starting with the Page Info comment) so DataStats and LoadMoreButton
remain the single sources of truth, or alternatively replace the outer
container's 'justify-between' with a single-side layout and remove the
duplicated count rendering to avoid overlap; locate references in
DiscoveryView.tsx to allRepos, the Page Info JSX, and
isDesktopSafeMode/DataStats/LoadMoreButton to apply the change.
- Around line 1119-1123: The allRepos list is rendered unbounded (mapping to
SubscriptionRepoCard) and will grow as users click Load More, so replace the
direct map with a virtualized list (e.g., react-window or
`@tanstack/react-virtual`) to only mount visible items; keep using
scrollContainerRef as the scrollParent, set itemCount to allRepos.length, render
SubscriptionRepoCard inside the virtualized item renderer and pass
desktopSafeMode (isDesktopSafeMode) and key via the renderer, and ensure spacing
logic (isDesktopSafeMode ? 'space-y-3' : 'space-y-4') is preserved around the
virtualized container in DiscoveryView.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a343dd97-4905-49d8-98a8-a153776140f1

📥 Commits

Reviewing files that changed from the base of the PR and between 1465a90 and d0c9b56.

📒 Files selected for processing (1)
  • src/components/DiscoveryView.tsx

@SummerRay160
Copy link
Copy Markdown
Contributor Author

我检查下代码 先不合并

@AmintaCCCP
Copy link
Copy Markdown
Owner

我检查下代码 先不合并

好的,注意身体啊兄弟,多休息。现在主分支还有几个小问题我刚测出来的:

  • 趋势页搜索发现初始状态(列表为空)显示刷新按钮会对用户产生误导,那个按钮其实是不能点的。可以改为文字引导用户在搜索框输入关键字。
  • 趋势页仓库 AI 分析后刷新,AI 分析结果没有持久化保存,会丢。
  • 趋势页仓库卡片鼠标 hover 时没有可点击反馈
  • 仓库查看 readme 唤起的大纲模式一级大纲的锚点点不了。

添加 discoveryIsLoadingMore 和 discoveryLoadMoreError 状态用于管理加载更多操作
实现加载更多时的滚动定位和错误处理
优化频道切换时的状态重置逻辑
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store/useAppStore.ts`:
- Around line 337-343: The persisted per-channel restoration in useAppStore
(e.g., the persistedRepos assignment and keys like 'trending', 'hot-release',
'most-popular', 'topic', 'search') blindly trusts the persisted shape; change it
to validate and normalize each value before restoring (ensure discoveryRepos
entries are arrays, discoveryNextPage values are finite non-negative numbers,
discoveryLoading values are booleans, and page indices are integers > 0) using
the provided type-guard patterns (e.g., Array.isArray(...) ? value : [], typeof
value === 'boolean' ? value : false, Number.isFinite(value) && value >= 0 ?
value : 0, Number.isInteger(value) && value > 0 ? value : 1) and apply the same
guards to the other maps referenced in this file (the blocks around the other
commented ranges) so consumers always get the expected array/boolean/number
shapes.
- Around line 1179-1205: The setSelectedDiscoveryChannel updater currently
resets per-channel state (discoveryRepos, discoveryNextPage, discoveryHasMore,
discoveryTotalCount, discoveryIsLoadingMore, discoveryLoadMoreError) whenever a
channel is selected; change it so setSelectedDiscoveryChannel only updates
selectedDiscoveryChannel and leaves all discovery* maps untouched to preserve
per-channel cache/scroll state. Move any logic that clears those maps into the
code paths that actually change queries/filters (where query/filter change
handlers live) so that only real query changes reset discoveryRepos,
discoveryNextPage, discoveryHasMore, discoveryTotalCount,
discoveryIsLoadingMore, and discoveryLoadMoreError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d41dc9d-532a-4431-b505-5223c8813672

📥 Commits

Reviewing files that changed from the base of the PR and between d0c9b56 and 3239ddd.

📒 Files selected for processing (3)
  • src/components/DiscoveryView.tsx
  • src/store/useAppStore.ts
  • src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/DiscoveryView.tsx

Comment thread src/store/useAppStore.ts
Comment thread src/store/useAppStore.ts
扩展 AI 服务类型以支持兼容 OpenAI API 的自定义端点,包括:
1. 在类型定义中添加 'openai-compatible' 选项
2. 修改请求处理逻辑以直接使用自定义端点的完整 URL
3. 在配置面板中增加相关说明和最终请求地址预览
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
server/src/routes/proxy.ts (1)

140-148: ⚠️ Potential issue | 🟡 Minor

Fix misleading UI copy and document reasoning field compatibility across OpenAI-compatible endpoints.

The code injects reasoning: { effort: ... } on any openai-compatible apiType, but this format is not universally supported:

  • NVIDIA NIM (placeholder on line 380): Does not accept the reasoning field; uses special system prompts or custom reasoning_effort parameter for specific models only.
  • vLLM: Does not accept input reasoning parameters; reasoning is extracted server-side via CLI flags only.
  • llama.cpp: Uses reasoning_content field (not reasoning); does not accept the reasoning: {effort: ...} format.
  • Ollama: Accepts reasoning_effort parameter (not reasoning with nested effort).
  • LocalAI: Supports reasoning: {effort: ...} in /v1/responses (as of Jan 2026).

The UI text at line 467 ("仅对 OpenAI 兼容接口生效" / "Only applies to OpenAI-compatible APIs") is misleading. Most widely-used compatible endpoints will reject the injected field as unknown. Update the UI text to clarify which endpoints actually accept this format, or restrict injection to explicitly supported backends (e.g., only openai, openai-responses, and localai). At minimum, document the compatibility limitations for users configuring third-party endpoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/proxy.ts` around lines 140 - 148, The current injection of
reasoning: { effort: reasoningEffort } into effectiveRequestBody is too broad
for the apiType 'openai-compatible' and will break backends that do not accept
that field; change the condition that builds effectiveRequestBody to only inject
the reasoning object for explicitly supported apiTypes (e.g., 'openai',
'openai-responses', and 'localai') instead of the generic 'openai-compatible'
branch, and update the UI copy shown by the component that renders the "仅对
OpenAI 兼容接口生效" text to clearly list supported backends or say it only applies to
OpenAI, OpenAI Responses, and LocalAI; also add a short comment near
effectiveRequestBody and in relevant docs to enumerate incompatible backends
(NVIDIA NIM, vLLM, llama.cpp, Ollama) and their alternative parameters (e.g.,
reasoning_effort, reasoning_content) so integrators know not to rely on the
injected reasoning field.
🧹 Nitpick comments (2)
src/utils/apiUrlBuilder.ts (1)

3-36: Optional: de-duplicate buildApiUrl with the server implementation.

buildApiUrl here is an almost verbatim copy of the one in server/src/routes/proxy.ts (lines 10–43). If the two ever drift (e.g., new version pattern or host-specific rule), client UI preview and server-side routing will silently diverge. If you have a shared code directory (or can import from src/utils/ on the server via a path alias), consider hoisting this into a single module; otherwise at least add cross-reference comments so future edits stay in sync.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/apiUrlBuilder.ts` around lines 3 - 36, The buildApiUrl function is
duplicated between client and server (see buildApiUrl) which risks drift;
refactor by extracting the logic into a single shared module and import it from
both places (or, if sharing is impossible, add a clear cross-reference comment
pointing to the server implementation and ensure both copies reference a single
source of truth), update all usages to import the shared buildApiUrl function,
and run tests to verify behavior; if exporting/importing requires path aliasing,
add the necessary tsconfig/webpack alias and update imports accordingly.
server/src/routes/proxy.ts (1)

119-123: Optional: extract a server-side buildFinalApiUrl.

The inline ternary here replicates the client-side buildFinalApiUrl logic from src/utils/apiUrlBuilder.ts lines 38-45. Extracting a small local helper (e.g., buildFinalApiUrl(baseUrl, apiType) next to the existing buildApiUrl in this file) would keep the OpenAI-style URL composition in one place and reduce the risk of the server drifting from client behavior when new apiType values are added.

♻️ Suggested helper
+function buildFinalApiUrl(baseUrl: string, apiType: string): string {
+  if (apiType === 'openai-compatible') {
+    return baseUrl.replace(/\/$/, '');
+  }
+  const path = apiType === 'openai-responses' ? 'v1/responses' : 'v1/chat/completions';
+  return buildApiUrl(baseUrl, path);
+}
     if (apiType === 'openai' || apiType === 'openai-responses' || apiType === 'openai-compatible') {
-      // openai-compatible 类型直接使用 baseUrl 作为完整地址
-      targetUrl = apiType === 'openai-compatible'
-        ? baseUrl.replace(/\/$/, '')
-        : buildApiUrl(baseUrl, apiType === 'openai-responses' ? 'v1/responses' : 'v1/chat/completions');
+      targetUrl = buildFinalApiUrl(baseUrl, apiType);
       headers['Authorization'] = `Bearer ${apiKey}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/proxy.ts` around lines 119 - 123, Extract the OpenAI-style
URL selection into a local helper so server logic matches the client: add a
buildFinalApiUrl(baseUrl, apiType) next to the existing buildApiUrl and replace
the inline ternary that sets targetUrl for apiType === 'openai' |
'openai-responses' | 'openai-compatible' to call it; implement buildFinalApiUrl
to return baseUrl.replace(/\/$/, '') for 'openai-compatible', and otherwise call
buildApiUrl(baseUrl, apiType === 'openai-responses' ? 'v1/responses' :
'v1/chat/completions') so the same mapping is centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/src/routes/proxy.ts`:
- Around line 140-148: The current injection of reasoning: { effort:
reasoningEffort } into effectiveRequestBody is too broad for the apiType
'openai-compatible' and will break backends that do not accept that field;
change the condition that builds effectiveRequestBody to only inject the
reasoning object for explicitly supported apiTypes (e.g., 'openai',
'openai-responses', and 'localai') instead of the generic 'openai-compatible'
branch, and update the UI copy shown by the component that renders the "仅对
OpenAI 兼容接口生效" text to clearly list supported backends or say it only applies to
OpenAI, OpenAI Responses, and LocalAI; also add a short comment near
effectiveRequestBody and in relevant docs to enumerate incompatible backends
(NVIDIA NIM, vLLM, llama.cpp, Ollama) and their alternative parameters (e.g.,
reasoning_effort, reasoning_content) so integrators know not to rely on the
injected reasoning field.

---

Nitpick comments:
In `@server/src/routes/proxy.ts`:
- Around line 119-123: Extract the OpenAI-style URL selection into a local
helper so server logic matches the client: add a buildFinalApiUrl(baseUrl,
apiType) next to the existing buildApiUrl and replace the inline ternary that
sets targetUrl for apiType === 'openai' | 'openai-responses' |
'openai-compatible' to call it; implement buildFinalApiUrl to return
baseUrl.replace(/\/$/, '') for 'openai-compatible', and otherwise call
buildApiUrl(baseUrl, apiType === 'openai-responses' ? 'v1/responses' :
'v1/chat/completions') so the same mapping is centralized.

In `@src/utils/apiUrlBuilder.ts`:
- Around line 3-36: The buildApiUrl function is duplicated between client and
server (see buildApiUrl) which risks drift; refactor by extracting the logic
into a single shared module and import it from both places (or, if sharing is
impossible, add a clear cross-reference comment pointing to the server
implementation and ensure both copies reference a single source of truth),
update all usages to import the shared buildApiUrl function, and run tests to
verify behavior; if exporting/importing requires path aliasing, add the
necessary tsconfig/webpack alias and update imports accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 05e25803-bdce-4bed-b4a6-1f7166f93440

📥 Commits

Reviewing files that changed from the base of the PR and between 3239ddd and 520ca42.

📒 Files selected for processing (5)
  • server/src/routes/proxy.ts
  • src/components/settings/AIConfigPanel.tsx
  • src/services/aiService.ts
  • src/types/index.ts
  • src/utils/apiUrlBuilder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/index.ts

HappySummer added 3 commits April 24, 2026 03:50
当选择搜索频道时,显示不同的空状态UI和提示信息,包括搜索图标和搜索引导文本,而非默认的刷新按钮和数据提示
实现使用IndexedDB存储和加载仓库AI分析结果,包括保存、加载、批量加载和删除功能。在DiscoveryView组件中集成该存储服务,确保分析结果在页面刷新后仍可保留。
调整卡片悬停时的阴影、边框和位移效果,统一桌面安全模式和非安全模式下的交互体验
@SummerRay160
Copy link
Copy Markdown
Contributor Author

可以合并

@AmintaCCCP AmintaCCCP merged commit b967517 into AmintaCCCP:main Apr 24, 2026
5 checks passed
@SummerRay160 SummerRay160 deleted the Dev branch April 25, 2026 10:18
@SummerRay160 SummerRay160 restored the Dev branch April 25, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants