mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-13 23:11:06 +00:00
feat(cli): implement comprehensive sort strategy with backend and client-side sorting
- Backend sorts (direct API): newest, downloads, rating - Client-side sorts (fetch then re-sort): hot, stars - Add --rating option for rating-based sorting (backend supported) - Add applyClientSort() function for client-side sorting logic - Hot sort formula: downloads * 0.6 + stars * 0.4 - Update SearchSkill interface to include stars, rating, updatedAt fields - Ensure all sort options work correctly with both list and search APIs
This commit is contained in:
parent
2fffc2b377
commit
4a7ebb4efc
2 changed files with 50 additions and 28 deletions
|
|
@ -221,20 +221,23 @@ export function registerExplore(program: Command) {
|
|||
.description("Browse or search skills from the registry")
|
||||
.argument("[query]", "Search query for finding skills")
|
||||
.option("-n, --limit <n>", "Max results", "20")
|
||||
.option("-s, --sort <sort>", "Sort by: hot, newest, downloads, stars (default: interactive mode)")
|
||||
.option("-s, --sort <sort>", "Sort by: hot, newest, downloads, stars, rating (default: interactive mode)")
|
||||
.option("--hot", "Sort by comprehensive popularity (downloads + stars)")
|
||||
.option("--newest", "Sort by newest first (shorthand for --sort newest)")
|
||||
.option("--downloads", "Sort by download count (shorthand for --sort downloads)")
|
||||
.option("--stars", "Sort by star count (shorthand for --sort stars)")
|
||||
.action(async (query: string | undefined, opts: { limit: string; sort?: string; hot?: boolean; newest?: boolean; downloads?: boolean; stars?: boolean }) => {
|
||||
.option("--rating", "Sort by average rating (shorthand for --sort rating)")
|
||||
.action(async (query: string | undefined, opts: { limit: string; sort?: string; hot?: boolean; newest?: boolean; downloads?: boolean; stars?: boolean; rating?: boolean }) => {
|
||||
const config = loadConfigFromProgram(program);
|
||||
const token = await readToken();
|
||||
const client = new ApiClient({ baseUrl: config.registry, token: token || undefined });
|
||||
const sortMap: Record<string, string> = {
|
||||
hot: "hot",
|
||||
newest: "newest",
|
||||
|
||||
// Backend-supported sorts: newest, downloads, rating
|
||||
// Client-side sorts: hot, stars (fetch with newest, then re-sort)
|
||||
const backendSortMap: Record<string, string> = {
|
||||
newest: "newest",
|
||||
downloads: "downloads",
|
||||
stars: "stars"
|
||||
rating: "rating"
|
||||
};
|
||||
|
||||
// Resolve sort priority: explicit --sort > shorthand flags > default
|
||||
|
|
@ -244,12 +247,17 @@ export function registerExplore(program: Command) {
|
|||
else if (opts.newest) effectiveSort = "newest";
|
||||
else if (opts.downloads) effectiveSort = "downloads";
|
||||
else if (opts.stars) effectiveSort = "stars";
|
||||
else if (opts.rating) effectiveSort = "rating";
|
||||
}
|
||||
const apiSort = sortMap[effectiveSort || "newest"] || "newest";
|
||||
|
||||
// Determine API sort and client-side re-sort
|
||||
const isClientSort = effectiveSort === "hot" || effectiveSort === "stars";
|
||||
const apiSort = isClientSort ? "newest" : (backendSortMap[effectiveSort || "newest"] || "newest");
|
||||
const clientSort = isClientSort ? effectiveSort : undefined;
|
||||
|
||||
try {
|
||||
// Enter interactive mode only if no query AND no sort option (explicit or shorthand)
|
||||
const hasSortOption = opts.sort || opts.hot || opts.newest || opts.downloads || opts.stars;
|
||||
const hasSortOption = opts.sort || opts.hot || opts.newest || opts.downloads || opts.stars || opts.rating;
|
||||
if (!query && !hasSortOption) {
|
||||
const selected = await runInteractiveSearch(client, "", apiSort);
|
||||
if (!selected) {
|
||||
|
|
@ -261,7 +269,7 @@ export function registerExplore(program: Command) {
|
|||
return;
|
||||
}
|
||||
|
||||
const results = await searchSkills(client, query || "", parseInt(opts.limit, 10), apiSort);
|
||||
const results = await searchSkills(client, query || "", parseInt(opts.limit, 10), apiSort, clientSort);
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log(`${DIM}No skills found${RESET}`);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ export interface SearchSkill {
|
|||
version?: string;
|
||||
summary?: string;
|
||||
installs?: number;
|
||||
stars?: number;
|
||||
rating?: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
interface SkillDetail {
|
||||
|
|
@ -56,16 +59,39 @@ async function fetchSkillDetail(client: ApiClient, namespace: string, name: stri
|
|||
}
|
||||
}
|
||||
|
||||
function applyClientSort(skills: SearchSkill[], sort: string): SearchSkill[] {
|
||||
switch (sort) {
|
||||
case "downloads":
|
||||
return skills.sort((a, b) => (b.installs || 0) - (a.installs || 0));
|
||||
case "stars":
|
||||
return skills.sort((a, b) => (b.stars || 0) - (a.stars || 0));
|
||||
case "rating":
|
||||
return skills.sort((a, b) => (b.rating || 0) - (a.rating || 0));
|
||||
case "hot":
|
||||
return skills.sort((a, b) => {
|
||||
const hotA = (a.installs || 0) * 0.6 + (a.stars || 0) * 0.4;
|
||||
const hotB = (b.installs || 0) * 0.6 + (b.stars || 0) * 0.4;
|
||||
return hotB - hotA;
|
||||
});
|
||||
case "newest":
|
||||
default:
|
||||
return skills.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchSkills(
|
||||
client: ApiClient,
|
||||
query: string,
|
||||
limit: number = 10,
|
||||
sort?: string
|
||||
apiSort: string = "newest",
|
||||
clientSort?: string
|
||||
): Promise<SearchSkill[]> {
|
||||
const needsClientSort = clientSort && clientSort !== apiSort;
|
||||
|
||||
if (!query) {
|
||||
const params = new URLSearchParams({ limit: limit.toString() });
|
||||
if (sort && sort !== "newest") {
|
||||
params.set("sort", sort);
|
||||
if (apiSort && apiSort !== "newest") {
|
||||
params.set("sort", apiSort);
|
||||
}
|
||||
const result = await client.get<SkillsListResponse>(
|
||||
`${ApiRoutes.skills}?${params.toString()}`
|
||||
|
|
@ -87,18 +113,12 @@ export async function searchSkills(
|
|||
updatedAt: s.updatedAt || 0,
|
||||
};
|
||||
});
|
||||
if (sort === "downloads") {
|
||||
return skills.sort((a, b) => b.installs - a.installs);
|
||||
} else if (sort === "stars") {
|
||||
return skills.sort((a, b) => b.stars - a.stars);
|
||||
} else {
|
||||
return skills.sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
}
|
||||
return needsClientSort ? applyClientSort(skills, clientSort) : applyClientSort(skills, apiSort);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({ q: query, limit: limit.toString() });
|
||||
if (sort) {
|
||||
params.set("sort", sort);
|
||||
if (apiSort && apiSort !== "newest") {
|
||||
params.set("sort", apiSort);
|
||||
}
|
||||
const result = await client.get<SearchResponse>(
|
||||
`${ApiRoutes.search}?${params.toString()}`
|
||||
|
|
@ -123,13 +143,7 @@ export async function searchSkills(
|
|||
};
|
||||
});
|
||||
|
||||
if (sort === "downloads") {
|
||||
return skills.sort((a, b) => b.installs - a.installs);
|
||||
} else if (sort === "stars") {
|
||||
return skills.sort((a, b) => b.stars - a.stars);
|
||||
} else {
|
||||
return skills.sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
}
|
||||
return needsClientSort ? applyClientSort(skills, clientSort) : applyClientSort(skills, apiSort);
|
||||
}
|
||||
|
||||
export async function runInteractiveSearch(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue