feat: compare (premium), rating-history trend, hover previews
Build & Push Docker Image / build (push) Successful in 2m47s

This commit is contained in:
opencode
2026-08-02 12:48:02 +02:00
parent 0c27982098
commit c77610786b
18 changed files with 911 additions and 13 deletions
@@ -145,6 +145,13 @@ export interface Tool {
deletedBy?: string | null;
}
export interface RatingHistoryItem {
date: string;
usefulness: number;
usability: number;
combined: number;
}
export interface ToolWithStats {
id: number;
name: string;
@@ -366,6 +373,13 @@ export const ListToolsSort = {
recently_updated: 'recently_updated',
} as const;
export type ListCompareToolsParams = {
/**
* Comma-separated tool ids
*/
ids: string;
};
export type ListTrashedToolsParams = {
search?: string;
};
+163
View File
@@ -31,11 +31,13 @@ import type {
GetTopToolsParams,
HealthStatus,
ListAuditLogsParams,
ListCompareToolsParams,
ListToolsParams,
ListTrashedToolsParams,
LocalLoginInput,
Rating,
RatingDistribution,
RatingHistoryItem,
RatingInput,
RestoreTools200,
Tool,
@@ -375,6 +377,167 @@ export const useCreateTool = <TError = ErrorType<ErrorResponse>,
return useMutation(getCreateToolMutationOptions(options));
}
export const getListCompareToolsUrl = (params: ListCompareToolsParams,) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? 'null' : value.toString())
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0 ? `/api/compare?${stringifiedParams}` : `/api/compare`
}
/**
* @summary Compare tools side by side (premium)
*/
export const listCompareTools = async (params: ListCompareToolsParams, options?: RequestInit): Promise<ToolWithStats[]> => {
return customFetch<ToolWithStats[]>(getListCompareToolsUrl(params),
{
...options,
method: 'GET'
}
);}
export const getListCompareToolsQueryKey = (params?: ListCompareToolsParams,) => {
return [
`/api/compare`, ...(params ? [params] : [])
] as const;
}
export const getListCompareToolsQueryOptions = <TData = Awaited<ReturnType<typeof listCompareTools>>, TError = ErrorType<ErrorResponse>>(params: ListCompareToolsParams, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
) => {
const {query: queryOptions, request: requestOptions} = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListCompareToolsQueryKey(params);
const queryFn: QueryFunction<Awaited<ReturnType<typeof listCompareTools>>> = ({ signal }) => listCompareTools(params, { signal, ...requestOptions });
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData> & { queryKey: QueryKey }
}
export type ListCompareToolsQueryResult = NonNullable<Awaited<ReturnType<typeof listCompareTools>>>
export type ListCompareToolsQueryError = ErrorType<ErrorResponse>
/**
* @summary Compare tools side by side (premium)
*/
export function useListCompareTools<TData = Awaited<ReturnType<typeof listCompareTools>>, TError = ErrorType<ErrorResponse>>(
params: ListCompareToolsParams, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListCompareToolsQueryOptions(params,options)
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };
return { ...query, queryKey: queryOptions.queryKey };
}
export const getGetToolRatingHistoryUrl = (id: number,) => {
return `/api/tools/${id}/rating-history`
}
/**
* @summary Get a tool's rating history over time
*/
export const getToolRatingHistory = async (id: number, options?: RequestInit): Promise<RatingHistoryItem[]> => {
return customFetch<RatingHistoryItem[]>(getGetToolRatingHistoryUrl(id),
{
...options,
method: 'GET'
}
);}
export const getGetToolRatingHistoryQueryKey = (id: number,) => {
return [
`/api/tools/${id}/rating-history`
] as const;
}
export const getGetToolRatingHistoryQueryOptions = <TData = Awaited<ReturnType<typeof getToolRatingHistory>>, TError = ErrorType<ErrorResponse>>(id: number, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
) => {
const {query: queryOptions, request: requestOptions} = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetToolRatingHistoryQueryKey(id);
const queryFn: QueryFunction<Awaited<ReturnType<typeof getToolRatingHistory>>> = ({ signal }) => getToolRatingHistory(id, { signal, ...requestOptions });
return { queryKey, queryFn, enabled: !!(id), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData> & { queryKey: QueryKey }
}
export type GetToolRatingHistoryQueryResult = NonNullable<Awaited<ReturnType<typeof getToolRatingHistory>>>
export type GetToolRatingHistoryQueryError = ErrorType<ErrorResponse>
/**
* @summary Get a tool's rating history over time
*/
export function useGetToolRatingHistory<TData = Awaited<ReturnType<typeof getToolRatingHistory>>, TError = ErrorType<ErrorResponse>>(
id: number, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetToolRatingHistoryQueryOptions(id,options)
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };
return { ...query, queryKey: queryOptions.queryKey };
}
export const getGetToolUrl = (id: number,) => {