Skip to content

Commit 7672bf4

Browse files
hych0317Hermes Agent
andauthored
fix: AI搜索不触发/无搜索结果时无交互提示 (#146)
* fix: trigger AI search and preserve empty results * fix: include sortBy/sortOrder in active filter check; prevent showAISummary reset during search - Extract hasActiveSearchFilters as utility function with sortBy/sortOrder checks (defaults: stars/desc). Fixes sorting being ignored when no other filters active. - Only reset showAISummary on category change, not during search/filter updates. Prevents AI analysis display from being incorrectly turned off during AI search. * docs: add JSDoc comments to RepositoriesView and searchRepositoriesWithReranking Addresses CodeRabbit docstring coverage warning (50% -> meets threshold). --------- Co-authored-by: Hermes Agent <hermes@agent.local>
1 parent fee0908 commit 7672bf4

3 files changed

Lines changed: 104 additions & 30 deletions

File tree

src/App.tsx

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,66 @@ import { useAutoUpdateCheck } from './components/UpdateChecker';
1515
import { UpdateNotificationBanner } from './components/UpdateNotificationBanner';
1616
import { backend } from './services/backendAdapter';
1717
import { syncFromBackend, startAutoSync, stopAutoSync } from './services/autoSync';
18-
import type { AppState } from './types';
19-
20-
const RepositoriesView = React.memo(({
21-
repositories,
22-
searchResults,
23-
selectedCategory,
24-
onCategorySelect
25-
}: {
18+
import type { AppState, SearchFilters } from './types';
19+
20+
/**
21+
* Check if any search/filter/sort condition is active (non-default).
22+
* Used to decide whether to display searchResults or the full repository list.
23+
*/
24+
function hasActiveSearchFilters(filters: SearchFilters): boolean {
25+
return (
26+
!!filters.query.trim() ||
27+
filters.languages.length > 0 ||
28+
filters.tags.length > 0 ||
29+
filters.platforms.length > 0 ||
30+
filters.minStars !== undefined ||
31+
filters.maxStars !== undefined ||
32+
filters.isAnalyzed !== undefined ||
33+
filters.isSubscribed !== undefined ||
34+
filters.isEdited !== undefined ||
35+
filters.isCategoryLocked !== undefined ||
36+
filters.analysisFailed !== undefined ||
37+
filters.sortBy !== 'stars' ||
38+
filters.sortOrder !== 'desc'
39+
);
40+
}
41+
42+
/**
43+
* Main repository view combining category sidebar, search bar, and repository list.
44+
* Switches between search results and full list based on active search filters.
45+
*/
46+
const RepositoriesView = React.memo(({
47+
repositories,
48+
searchResults,
49+
searchFilters,
50+
selectedCategory,
51+
onCategorySelect
52+
}: {
2653
repositories: AppState['repositories'];
2754
searchResults: AppState['searchResults'];
55+
searchFilters: AppState['searchFilters'];
2856
selectedCategory: string;
2957
onCategorySelect: (category: string) => void;
30-
}) => (
31-
<div className="flex flex-col gap-4 lg:flex-row lg:gap-6">
32-
<CategorySidebar
33-
repositories={repositories}
34-
selectedCategory={selectedCategory}
35-
onCategorySelect={onCategorySelect}
36-
/>
37-
<div className="flex-1 space-y-6">
38-
<SearchBar />
39-
<RepositoryList
40-
repositories={searchResults.length > 0 ? searchResults : repositories}
58+
}) => {
59+
const isActive = hasActiveSearchFilters(searchFilters);
60+
61+
return (
62+
<div className="flex flex-col gap-4 lg:flex-row lg:gap-6">
63+
<CategorySidebar
64+
repositories={repositories}
4165
selectedCategory={selectedCategory}
66+
onCategorySelect={onCategorySelect}
4267
/>
68+
<div className="flex-1 space-y-6">
69+
<SearchBar />
70+
<RepositoryList
71+
repositories={isActive ? searchResults : repositories}
72+
selectedCategory={selectedCategory}
73+
/>
74+
</div>
4375
</div>
44-
</div>
45-
));
76+
);
77+
});
4678
RepositoriesView.displayName = 'RepositoriesView';
4779

4880
const ReleasesView = React.memo(() => <ReleaseTimeline />);
@@ -62,6 +94,7 @@ function App() {
6294
theme,
6395
hasHydrated,
6496
searchResults,
97+
searchFilters,
6598
repositories,
6699
setSelectedCategory,
67100
} = useAppStore();
@@ -115,6 +148,7 @@ function App() {
115148
<RepositoriesView
116149
repositories={repositories}
117150
searchResults={searchResults}
151+
searchFilters={searchFilters}
118152
selectedCategory={selectedCategory}
119153
onCategorySelect={handleCategorySelect}
120154
/>
@@ -134,7 +168,7 @@ function App() {
134168
default:
135169
return null;
136170
}
137-
}, [currentView, repositories, searchResults, selectedCategory, handleCategorySelect]);
171+
}, [currentView, repositories, searchResults, searchFilters, selectedCategory, handleCategorySelect]);
138172

139173
// Show loading state while store is hydrating to ensure correct theme is applied
140174
if (!hasHydrated) {

src/components/RepositoryList.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,19 @@ export const RepositoryList: React.FC<RepositoryListProps> = ({
116116
[filteredRepositories]
117117
);
118118

119-
// 当筛选的仓库变化时,如果没有AI分析的仓库,自动切换到原始描述
119+
// 当切换分类时,如果目标分类没有AI分析的仓库,自动切换到原始描述
120+
// 注意:不要在搜索/过滤过程中触发,否则会误关用户的显示偏好
121+
const prevHasAnalyzedRef = useRef(hasAnalyzedRepos);
122+
const prevCategoryRefForAI = useRef(selectedCategory);
120123
useEffect(() => {
121-
if (!hasAnalyzedRepos && showAISummary) {
124+
const categoryChanged = prevCategoryRefForAI.current !== selectedCategory;
125+
prevCategoryRefForAI.current = selectedCategory;
126+
prevHasAnalyzedRef.current = hasAnalyzedRepos;
127+
128+
if (categoryChanged && !hasAnalyzedRepos && showAISummary) {
122129
setShowAISummary(false);
123130
}
124-
}, [hasAnalyzedRepos]);
131+
}, [hasAnalyzedRepos, selectedCategory]);
125132

126133
// Infinite scroll (瀑布流按需加载)
127134
const LOAD_BATCH = 50;

src/services/aiService.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -629,16 +629,49 @@ Focus on practicality and accurate categorization to help users quickly understa
629629
return this.performBasicSearch(repositories, query);
630630
}
631631

632+
/**
633+
* Search repositories using AI semantic search with fallback to enhanced basic search.
634+
* Attempts to call the configured AI service to parse search intent and extract
635+
* multilingual keywords, then delegates to performEnhancedSearch. Falls back to
636+
* performEnhancedBasicSearch with intelligent ranking if AI is unavailable or fails.
637+
*
638+
* @param repositories - The full list of repositories to search
639+
* @param query - The user's search query string
640+
* @returns Filtered and ranked repositories matching the query
641+
*/
632642
async searchRepositoriesWithReranking(repositories: Repository[], query: string): Promise<Repository[]> {
633643
console.log('🤖 AI Service: Starting enhanced search for:', query);
634644
if (!query.trim()) return repositories;
635645

636-
// 直接使用增强的基础搜索,提供智能排序
646+
try {
647+
console.log('🚀 AI Service: Calling configured AI service for semantic search');
648+
const searchPrompt = this.createSearchPrompt(query);
649+
const system = this.language === 'zh'
650+
? '你是一个智能搜索助手。请分析用户的搜索意图,提取关键词并提供多语言翻译。'
651+
: 'You are an intelligent search assistant. Please analyze user search intent, extract keywords and provide multilingual translations.';
652+
653+
const content = await this.requestText({
654+
system,
655+
user: searchPrompt,
656+
temperature: 0.1,
657+
maxTokens: 200,
658+
});
659+
660+
if (content) {
661+
const searchTerms = this.parseSearchResponse(content);
662+
const results = this.performEnhancedSearch(repositories, query, searchTerms);
663+
console.log('✨ AI Service: AI semantic search completed, results:', results.length);
664+
return results;
665+
}
666+
} catch (error) {
667+
console.warn('❌ AI Service: AI semantic search failed, falling back to enhanced basic search:', error);
668+
}
669+
637670
console.log('🔄 AI Service: Using enhanced basic search with intelligent ranking');
638-
const results = this.performEnhancedBasicSearch(repositories, query);
639-
console.log('✨ AI Service: Enhanced search completed, results:', results.length);
640-
641-
return results;
671+
const fallbackResults = this.performEnhancedBasicSearch(repositories, query);
672+
console.log('✨ AI Service: Enhanced search completed, results:', fallbackResults.length);
673+
674+
return fallbackResults;
642675
}
643676

644677
// Enhanced basic search with intelligent ranking (fallback when AI fails)

0 commit comments

Comments
 (0)