Add user file quota display in header - #16
Conversation
- Add UserInfo type with indexed_files, pending_files, total_files, and file_quota fields - Add fetchUserInfo API function to retrieve user info from /users/info endpoint - Display file quota information in the indexer header (indexed files, pending files, quota usage) - Show ∞ symbol when quota is infinite (-1 from backend) - Disable upload button when file quota is reached (gray out with tooltip) - Auto-refresh user info every 10 seconds to keep quota display updated
cda119e to
fcea7e1
Compare
There was a problem hiding this comment.
Pull request overview
Adds UI + API plumbing to surface user file quota information in the indexer header and prevent uploads when the quota is reached.
Changes:
- Introduces
UserInfotype/state and anfetchUserInfo()API call to/users/info. - Displays indexed/pending file counts and quota usage in the indexer header (including ∞ for unlimited).
- Enforces quota in the upload flow by disabling uploads / truncating selection and showing a warning.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/indexer/+layout.svelte | Fetches user info and periodically refreshes it alongside existing route-driven data loading. |
| src/lib/types/user.ts | Adds UserInfo type definition for the new user info endpoint payload. |
| src/lib/types/index.ts | Re-exports the new user types from the central types barrel. |
| src/lib/states.svelte.ts | Adds indexerData.userInfo state for quota display/enforcement. |
| src/lib/components/indexer/UploadModal/UploadModal.svelte | Enforces quota on file selection and disables file picking when quota is exhausted. |
| src/lib/components/indexer/Header.svelte | Renders quota metrics in the header and disables upload button when quota is reached. |
| src/lib/api/indexer.ts | Adds fetchUserInfo() API function calling /users/info. |
Comments suppressed due to low confidence (1)
src/routes/indexer/+layout.svelte:83
handleRouting()is invoked on every$effectre-run (route changes), but it sets newsetIntervaltimers without clearing any existing ones. Navigating between indexer sub-routes can therefore accumulate multiplerefreshTasks/refreshUserInfointervals and duplicate network traffic. Clear existing intervals before creating new ones (or move interval setup into an$effectwith a cleanup function /onMountguard) so only one timer runs at a time.
$effect(() => {
console.log("Handling routing for:", page.route.id);
handleRouting();
});
/**
* This function handles data loading for different routes of the application
*/
async function handleRouting() {
if (ui.showLoginPage) return;
// Load partitions and user info
indexerData.partitions = await api.fetchPartitions();
// Fetch user info (for quota display)
try {
indexerData.userInfo = await api.fetchUserInfo();
refreshUserInfoInterval = setInterval(refreshUserInfo, 5000); // Refresh every 5 seconds
} catch (error) {
console.error("Failed to fetch user info:", error);
}
if (page.route.id === "/indexer") {
// Reset navigation states
indexerData.currentPartition = { partition: null, files: [] };
indexerData.currentFile = null;
// Load tasks
indexerData.tasks = await api.fetchTasks();
refreshTasksInterval = setInterval(refreshTasks, 5000); // Refresh every 5 seconds
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { ListFormat } from "typescript"; | ||
|
|
||
| /** | ||
| * Represents user information (from the GET /user/info endpoint) | ||
| */ | ||
| export interface UserInfo { | ||
| id: string; | ||
| display_name: string; | ||
| is_admin: boolean; | ||
| memberships: ListFormat; | ||
| file_count: number; |
There was a problem hiding this comment.
This file imports ListFormat from the typescript package as a runtime import. That will bloat the client bundle (and may fail to bundle) since typescript is a devDependency and not intended for browser runtime use. Replace memberships: ListFormat with an appropriate app-level type (e.g. string[]/Membership[]/unknown) and remove the typescript import (or at minimum make it import type).
| isQuotaInfinite | ||
| ? Infinity | ||
| : Math.max(0, (indexerData.userInfo?.file_quota ?? 0) - (indexerData.userInfo?.total_files ?? 0)) |
There was a problem hiding this comment.
remainingQuota falls back to 0 when indexerData.userInfo is still null (or if the user-info fetch fails). That makes the file picker disabled and prevents uploads even though the header still allows opening the modal. Consider treating a missing userInfo as "quota unknown" (e.g. allow selection / show a loading state) or disable opening the modal until userInfo is available so the behavior is consistent.
| isQuotaInfinite | |
| ? Infinity | |
| : Math.max(0, (indexerData.userInfo?.file_quota ?? 0) - (indexerData.userInfo?.total_files ?? 0)) | |
| indexerData.userInfo == null | |
| ? Infinity | |
| : isQuotaInfinite | |
| ? Infinity | |
| : Math.max(0, (indexerData.userInfo.file_quota ?? 0) - (indexerData.userInfo.total_files ?? 0)) |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This PR adds frontend support for displaying and enforcing the user file quota in OpenRAG. It surfaces real-time quota information in the indexer header and prevents uploads when the quota is reached.
See related PR: linagora/openrag#232.
Changes
UserInfotype withindexed_files,pending_files,total_files, andfile_quotafetchUserInfoAPI function to retrieve user data from the/users/infoendpoint-1(infinite quota)Impact