feat: add subscription feature - GitHub Top10 ranking with AI analysis#86
feat: add subscription feature - GitHub Top10 ranking with AI analysis#86chan-yuu wants to merge 2 commits intoAmintaCCCP:mainfrom
Conversation
新增 3 个默认订阅频道: - Most Stars:GitHub Star 数量最多的项目 Top 10 - Most Forks:GitHub Fork 数量最多的项目 Top 10 - Most DEV:GitHub 最受关注的开发者 Top 10 及其最热项目 主要变更: - types/index.ts: 新增 SubscriptionChannel, SubscriptionRepo, SubscriptionDev 类型 - githubApi.ts: 新增 searchMostStars/searchMostForks/searchDailyDevs 搜索方法 - backendAdapter.ts: 新增 searchRepositories/searchUsers 后端代理方法 - server/proxy.ts: 新增 Search API 代理路由 - useAppStore.ts: 新增 subscription 状态管理、持久化(migrate v5)、channel ID 迁移(daily-dev→most-dev) - Header.tsx: 三个导航断点(桌面/平板/手机)新增订阅入口(TrendingUp 图标) - App.tsx: 新增 subscription 视图渲染 - SubscriptionView.tsx: 主视图含频道切换、手动刷新、批量 AI 分析 - SubscriptionSidebar.tsx: 左侧频道光标栏 - SubscriptionRepoCard.tsx: 排行仓库卡片(排名徽章+AI标签+平台图标) - SubscriptionDevCard.tsx: 开发者排行卡片(头像+bio+最热项目) - ErrorBoundary.tsx: 错误消息显示防御性处理 频道功能:手动刷新更新数据、批量 AI 分析(复用 AIAnalysisOptimizer)、 数据持久化到 IndexedDB、后端代理支持 Search API
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a new "Subscription" feature: backend proxy endpoints for GitHub searches, frontend Subscription view + UI components, GitHub search-based services, store persistence/migrations for subscription data, and AI analysis orchestration for repos/devs. Changes
Sequence DiagramsequenceDiagram
participant User
participant SubscriptionView
participant AppStore
participant AIAnalysisOptimizer
participant GitHubAPI
participant AIService
User->>SubscriptionView: Click "Analyze Channel"
SubscriptionView->>AppStore: Verify github token & AI config
alt missing token or config
SubscriptionView->>User: alert & abort
else proceed
SubscriptionView->>AIAnalysisOptimizer: initialize with target repo list
AIAnalysisOptimizer->>GitHubAPI: prefetch READMEs for repos
GitHubAPI-->>AIAnalysisOptimizer: README contents
loop for each repo needing analysis
AIAnalysisOptimizer->>AIService: analyze (name, desc, README)
AIService-->>AIAnalysisOptimizer: ai_summary, ai_tags, ai_platforms
AIAnalysisOptimizer->>AppStore: updateSubscriptionRepo/updateSubscriptionDev with AI results
SubscriptionView->>User: update progress UI
end
AIAnalysisOptimizer-->>SubscriptionView: complete
SubscriptionView->>AppStore: clear analyzing state & set timestamps
SubscriptionView->>User: completion alert
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/store/useAppStore.ts (1)
131-164:⚠️ Potential issue | 🔴 CriticalFix type mismatches in subscription migration and prevent loading state leakage.
subscriptionReposis incorrectly cast to an array type on line 894 when it should be a record keyed by channel. Additionally,subscriptionIsLoadingis migrated on lines 899–900 despite not being part ofPersistedAppState, creating a type mismatch with the return statement. SincesubscriptionIsLoadingis transient UI state and not included in the persist config, it should be excluded from migration and explicitly reset on rehydrate.🐛 Proposed fix
type PersistedAppState = Partial< Pick< AppState, @@ | 'subscriptionRepos' | 'subscriptionDevs' | 'subscriptionLastRefresh' > > & { releaseSubscriptions?: unknown; readReleases?: unknown; releaseExpandedRepositories?: unknown; + subscriptionIsLoading?: unknown; };subscriptionLastRefresh: (() => { const raw = (safePersisted as Record<string, unknown>).subscriptionLastRefresh; if (raw && typeof raw === 'object') { const lr = { ...raw as Record<string, unknown> }; @@ } return { 'most-stars': null, 'most-forks': null, 'most-dev': null }; })(), + // Loading is transient UI state; never restore stale persisted values. + subscriptionIsLoading: { 'most-stars': false, 'most-forks': false, 'most-dev': false }, }; };const reposMigrated = migrateRecordKeys((state as Record<string, unknown>).subscriptionRepos); - if (reposMigrated) state.subscriptionRepos = reposMigrated as Record<string, unknown>[]; + if (reposMigrated) { + state.subscriptionRepos = reposMigrated as Record<SubscriptionChannelId, SubscriptionRepo[]>; + } const refreshMigrated = migrateRecordKeys((state as Record<string, unknown>).subscriptionLastRefresh); if (refreshMigrated) state.subscriptionLastRefresh = refreshMigrated as Record<string, unknown>; - const loadingMigrated = migrateRecordKeys((state as Record<string, unknown>).subscriptionIsLoading); - if (loadingMigrated) state.subscriptionIsLoading = loadingMigrated as Record<string, unknown>; + delete (state as Record<string, unknown>).subscriptionIsLoading; }Affects lines 192–194, 245–257, 878–900.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/useAppStore.ts` around lines 131 - 164, PersistedAppState's subscriptionRepos is a record keyed by channel, but the migration code currently casts it to an array and also migrates transient UI state subscriptionIsLoading (causing a type mismatch); update the migration to convert legacy subscriptionRepos into the correct shape (e.g., Record<channel, string[]>) and remove any migration/assignment of subscriptionIsLoading so the migrated object matches PersistedAppState, and then explicitly reset subscriptionIsLoading to its default (false) in the store's rehydrate/afterRestore logic (or initial state) so UI loading state never leaks from persisted data; reference PersistedAppState, subscriptionRepos, subscriptionIsLoading and the store rehydrate/migration block to locate the changes.
🧹 Nitpick comments (4)
server/src/routes/proxy.ts (1)
219-295: Refactor: the two new handlers are near-duplicates — extract a helper.Both handlers are identical except for the
githubPathconstant. Consider extracting acreateGithubSearchHandler(path: string)(or a single/api/proxy/github/search/:resourceroute with a small allowlist ofrepositories|users) to eliminate ~35 lines of duplication and keep token-decryption/error-code behavior in one place.🤖 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 219 - 295, Both POST handlers for '/api/proxy/github/search/repositories' and '/api/proxy/github/search/users' are duplicates; extract a shared handler generator to remove repetition. Implement a createGithubSearchHandler(path: string) that encapsulates the common logic (calls to getDb(), reading 'github_token', decrypt(..., config.encryptionKey), building queryString, setting headers, invoking proxyRequest and error handling) and then export two routes using createGithubSearchHandler('search/repositories') and createGithubSearchHandler('search/users') (or replace both with a single router.post('/api/proxy/github/search/:resource', ...) that validates resource against an allowlist ['repositories','users'] and forwards to the same inner logic). Ensure error codes and messages (GITHUB_TOKEN_NOT_CONFIGURED, GITHUB_TOKEN_DECRYPT_FAILED, GITHUB_SEARCH_PROXY_FAILED) and logging remain identical to the originals.src/types/index.ts (1)
229-234: Optional:forksvsforks_countonSubscriptionRepo.Having both optional
forks?andforks_count?and letting the consumer (SubscriptionRepoCardline 147) doforks_count ?? forks ?? 0invites data-shape drift. The GitHub REST API returnsforks_counton search results, so prefer normalizing to that single field in the service layer and droppingforks?from the type.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/index.ts` around lines 229 - 234, The SubscriptionRepo type exposes both forks? and forks_count? causing data-shape drift; update the type by removing the optional forks property and standardize on forks_count, then update the service layer that constructs SubscriptionRepo objects (the code path used by SubscriptionRepoCard) to normalize incoming data so forks_count is always set (e.g., if a source provides forks, map it to forks_count), and update any consumers to use only forks_count.src/services/backendAdapter.ts (1)
373-399: Duplicated inline type for thesearchUsersuser shape.The user item type is written out twice (as the return type annotation and again as the
res.json()cast). Extract a named interface (e.g.GitHubUserSearchItem) and reuse it in both positions. This also makes it easier to share withSubscriptionDevconstruction in the store.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/backendAdapter.ts` around lines 373 - 399, Extract the duplicated inline user shape into a single named interface (e.g. GitHubUserSearchItem) and replace both the explicit return type in searchUsers and the res.json() cast to use that interface (Promise<{ items: GitHubUserSearchItem[] }>). Add the new interface near other service types or export it if used elsewhere, and update any usages such as SubscriptionDev construction in the store to reference GitHubUserSearchItem instead of re-declaring the shape.src/components/SubscriptionRepoCard.tsx (1)
20-46: Optional: hoist static maps to module scope.
languageColorsandplatformIconMapare constant; wrapping them inuseMemo(..., [])still allocates on every render before memoization and adds noise. SinceSubscriptionDevCard.tsxalso defines an identicalplatformIconMap+getPlatformEmoji, consider moving them to a shared module (e.g.src/utils/platformIcons.ts).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/SubscriptionRepoCard.tsx` around lines 20 - 46, languageColors and platformIconMap are static but are created inside useMemo (and duplicated in SubscriptionDevCard), causing unnecessary allocation and duplication; hoist these maps to module scope or a small shared module (e.g., platformIcons) and remove the useMemo wrappers in SubscriptionRepoCard.tsx, updating getLanguageColor and getPlatformEmoji to reference the top-level constants (and export the maps/getters from the shared module so SubscriptionDevCard.tsx can reuse them).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/src/routes/proxy.ts`:
- Around line 219-295: The specific search routes
router.post('/api/proxy/github/search/repositories') and
router.post('/api/proxy/github/search/users') are being shadowed by the earlier
wildcard router.post('/api/proxy/github/*') and the wildcard handler also
ignores body.query_params (backendAdapter sends query params in body), causing
empty query strings and 422 errors; fix by either moving the two search route
blocks to be registered before router.post('/api/proxy/github/*') so they match
first, or modify the wildcard handler (router.post('/api/proxy/github/*')) to
detect and use req.body.query_params as a fallback when new
URL(req.url...).search is empty (build the query string with new
URLSearchParams(req.body.query_params)).
In `@src/components/SubscriptionDevCard.tsx`:
- Line 89: In SubscriptionDevCard (the div with className containing "bg-gray-50
dark:bg-gray-750 dark:bg-gray-900/40 rounded-lg p-3 border border-gray-200
dark:border-gray-600"), remove the duplicate/conflicting dark:bg utility and
replace the non-standard shade with a valid Tailwind color (e.g., use
"dark:bg-gray-900/40" or "dark:bg-gray-700") or, if you intentionally need
"gray-750", add that custom shade to tailwind.config; update the className to
contain a single dark:bg-* entry so only one dark background is applied.
- Around line 138-149: The tag rendering falls back to ai_tags using a truthy
check so an empty ai_tags array prevents topics from being shown; update the
rendering to use a length-aware fallback (e.g. choose dev.topRepo.ai_tags if
ai_tags.length>0 else dev.topRepo.topics or an empty array) where the map is
called (the expression currently written as (dev.topRepo.ai_tags ||
dev.topRepo.topics || [])). Adjust the outer conditional and the mapped array
expression in SubscriptionDevCard (references: dev.topRepo.ai_tags,
dev.topRepo.topics) so the UI only renders when there are tags and topics are
used when ai_tags is empty.
In `@src/components/SubscriptionRepoCard.tsx`:
- Around line 103-114: The tag rendering uses (repo.ai_tags || repo.topics ||
[]) which wrongly prefers an empty ai_tags array and hides repo.topics; change
the selection to be length-aware: compute the source array for mapping using the
lengths (e.g., prefer repo.ai_tags when repo.ai_tags?.length > 0 else use
repo.topics when repo.topics?.length > 0, otherwise empty) and then call
.slice(0, 5).map(...) in SubscriptionRepoCard (the JSX block that references
repo.ai_tags and repo.topics) so topics are shown when ai_tags is an empty
array.
In `@src/services/githubApi.ts`:
- Around line 304-315: The code calls this.makeRequest<Repository[]> with
`/users/${searchUser.login}/repos?sort=stars...` which is invalid because that
endpoint doesn't support sort=stars; change the logic in the block that assigns
topRepo (symbols: topRepo, makeRequest, searchUser.login) to use the GitHub
search API instead by requesting
`/search/repositories?q=user:${searchUser.login}&sort=stars&order=desc&per_page=1`,
then extract the top repo from the response's items array (adjust the expected
response type accordingly) and assign it to topRepo with rank: 1 and channel:
'most-dev'. Ensure you handle the search response shape (items may be undefined)
and preserve existing null/empty checks.
---
Outside diff comments:
In `@src/store/useAppStore.ts`:
- Around line 131-164: PersistedAppState's subscriptionRepos is a record keyed
by channel, but the migration code currently casts it to an array and also
migrates transient UI state subscriptionIsLoading (causing a type mismatch);
update the migration to convert legacy subscriptionRepos into the correct shape
(e.g., Record<channel, string[]>) and remove any migration/assignment of
subscriptionIsLoading so the migrated object matches PersistedAppState, and then
explicitly reset subscriptionIsLoading to its default (false) in the store's
rehydrate/afterRestore logic (or initial state) so UI loading state never leaks
from persisted data; reference PersistedAppState, subscriptionRepos,
subscriptionIsLoading and the store rehydrate/migration block to locate the
changes.
---
Nitpick comments:
In `@server/src/routes/proxy.ts`:
- Around line 219-295: Both POST handlers for
'/api/proxy/github/search/repositories' and '/api/proxy/github/search/users' are
duplicates; extract a shared handler generator to remove repetition. Implement a
createGithubSearchHandler(path: string) that encapsulates the common logic
(calls to getDb(), reading 'github_token', decrypt(..., config.encryptionKey),
building queryString, setting headers, invoking proxyRequest and error handling)
and then export two routes using
createGithubSearchHandler('search/repositories') and
createGithubSearchHandler('search/users') (or replace both with a single
router.post('/api/proxy/github/search/:resource', ...) that validates resource
against an allowlist ['repositories','users'] and forwards to the same inner
logic). Ensure error codes and messages (GITHUB_TOKEN_NOT_CONFIGURED,
GITHUB_TOKEN_DECRYPT_FAILED, GITHUB_SEARCH_PROXY_FAILED) and logging remain
identical to the originals.
In `@src/components/SubscriptionRepoCard.tsx`:
- Around line 20-46: languageColors and platformIconMap are static but are
created inside useMemo (and duplicated in SubscriptionDevCard), causing
unnecessary allocation and duplication; hoist these maps to module scope or a
small shared module (e.g., platformIcons) and remove the useMemo wrappers in
SubscriptionRepoCard.tsx, updating getLanguageColor and getPlatformEmoji to
reference the top-level constants (and export the maps/getters from the shared
module so SubscriptionDevCard.tsx can reuse them).
In `@src/services/backendAdapter.ts`:
- Around line 373-399: Extract the duplicated inline user shape into a single
named interface (e.g. GitHubUserSearchItem) and replace both the explicit return
type in searchUsers and the res.json() cast to use that interface (Promise<{
items: GitHubUserSearchItem[] }>). Add the new interface near other service
types or export it if used elsewhere, and update any usages such as
SubscriptionDev construction in the store to reference GitHubUserSearchItem
instead of re-declaring the shape.
In `@src/types/index.ts`:
- Around line 229-234: The SubscriptionRepo type exposes both forks? and
forks_count? causing data-shape drift; update the type by removing the optional
forks property and standardize on forks_count, then update the service layer
that constructs SubscriptionRepo objects (the code path used by
SubscriptionRepoCard) to normalize incoming data so forks_count is always set
(e.g., if a source provides forks, map it to forks_count), and update any
consumers to use only forks_count.
🪄 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: 1055674f-38e9-4c37-8cd7-9ed0699a00ba
📒 Files selected for processing (12)
server/src/routes/proxy.tssrc/App.tsxsrc/components/ErrorBoundary.tsxsrc/components/Header.tsxsrc/components/SubscriptionDevCard.tsxsrc/components/SubscriptionRepoCard.tsxsrc/components/SubscriptionSidebar.tsxsrc/components/SubscriptionView.tsxsrc/services/backendAdapter.tssrc/services/githubApi.tssrc/store/useAppStore.tssrc/types/index.ts
|
抱歉,我之前的修复没有按照要求在此 PR 下提交,而是错误地创建了新 PR #87 并已合并。现在 PR #87 包含了本 PR 的所有功能以及以下修复:
由于 #87 已合并,本 PR 可以关闭。再次抱歉没有按照要求操作。 |
|
🤣🤣interesting, I forgiven it. Btw, is it a telegram robot? Use Hermes or OpenClaw? I want to train a robot like that. |
目前用的是openclaw,Hermes其实也可以。只是我的Openclaw用的早,对这个仓库的记忆比较多,就没有切到Hermes维护。鉴于最近openclaw的抽风表现,我也考虑切到Hermes试试了。平时我用Openclaw维护这个仓库主要是处理issue,提PR,发版,之前完成的都不错,不知道最近为什么频繁翻车。 |
|
本来只是让他修复一下合并上一个PR产生的代码冲突,顺便再加个trending分类,结果闹了这么大的乌龙,实在抱歉 |


Summary
Add Subscription feature as a top-level navigation entry alongside Categories, supporting GitHub ranking Top 10 projects with AI analysis.
Default Subscription Channels
Features
Changed Files
Summary by CodeRabbit
New Features
Improvements