Skip to content

feat(config,usage): integrate usage persistence settings and runtime status UI#150

Open
shaoyuanyu wants to merge 2 commits intorouter-for-me:mainfrom
shaoyuanyu:main
Open

feat(config,usage): integrate usage persistence settings and runtime status UI#150
shaoyuanyu wants to merge 2 commits intorouter-for-me:mainfrom
shaoyuanyu:main

Conversation

@shaoyuanyu
Copy link

Summary

This PR integrates usage persistence configuration and runtime status into the Web UI, and improves usability by saving immediately on Usage page refresh and showing “last saved” information.

Motivation

After introducing backend persistence capability, users need a clear and centralized UI to configure it (in Config panel) and observe runtime behavior (in Usage page).

Changes

  • Config panel (VisualConfigEditor) now supports:
    • usage persistence enable switch
    • persistence file path
    • persistence interval seconds
  • Added validation and YAML mapping for usage-persistence.
  • Extended config/store/types/transformers support for usage-persistence.
  • Added usage persistence API methods:
    • get/update persistence config
    • get runtime status
    • save now / load now
  • Extended usage store/hook state and actions for persistence.
  • Usage page updates:
    • refresh now triggers immediate save
    • displays Last saved timestamp
    • displays persistence-specific error message
  • Added i18n strings for zh-CN / en / ru.

Backward Compatibility

  • Existing configs remain valid.
  • New UI fields are optional and map cleanly to YAML.
  • No breaking changes to existing usage statistics display.

Validation

  • Type-check passed.
  • Build passed (tsc && vite build).
  • No new errors in modified frontend files.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience by integrating usage statistics persistence capabilities directly into the Web UI. Users can now easily configure how usage data is saved and loaded through a dedicated section in the Config panel. Furthermore, the Usage page has been improved to provide real-time feedback on the persistence status, including the last saved timestamp, and to automatically save data upon refresh, ensuring data integrity and usability.

Highlights

  • Configuration UI: Introduced new UI elements in the Config panel for managing usage persistence settings, including enabling/disabling, file path, and interval.
  • Validation and YAML Mapping: Implemented validation and YAML mapping to support the new usage-persistence configuration.
  • Usage Data Management: Extended the usage data store and hooks to manage usage persistence state and actions, such as saving and loading statistics.
  • API Enhancements: Added new API methods for interacting with usage persistence configurations and runtime status.
  • Usage Page Improvements: Enhanced the Usage page to automatically save statistics on refresh, display the last saved timestamp, and show persistence-specific error messages.
  • Internationalization: Provided internationalization strings for English, Russian, and Simplified Chinese to support the new features.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/components/config/VisualConfigEditor.tsx
    • Added usagePersistenceIntervalError validation message.
    • Introduced a ToggleRow for usagePersistenceEnabled.
    • Added Input fields for usagePersistenceFilePath and usagePersistenceIntervalSeconds.
  • src/components/usage/hooks/useUsageData.ts
    • Imported UsagePersistenceConfig and UsagePersistenceStatus types.
    • Extended UseUsageDataReturn interface with persistence-related state and actions.
    • Retrieved persistence state and actions from useUsageStatsStore.
    • Modified loadUsage to call saveUsageStatsNowInStore after loading stats.
    • Added useEffect to load usagePersistenceState on component mount.
    • Created memoized callbacks for updateUsagePersistenceConfig, saveUsageStatsNow, and loadUsageStatsNow.
    • Exported new persistence-related values from the hook.
  • src/hooks/useVisualConfig.ts
    • Added validation for usagePersistenceIntervalSeconds.
    • Parsed usage-persistence section from raw config.
    • Mapped usagePersistence properties to VisualConfigValues.
    • Included logic to set usage-persistence fields in the YAML document.
  • src/i18n/locales/en.json
    • Added new translation keys for usage persistence title, enabled status, file path, interval, save/load actions, last saved/loaded timestamps, and save/load counts.
    • Added new translation keys for config panel sections related to usage persistence.
  • src/i18n/locales/ru.json
    • Added new translation keys for usage persistence in Russian.
    • Added new translation keys for config panel sections related to usage persistence in Russian.
  • src/i18n/locales/zh-CN.json
    • Added new translation keys for usage persistence in Simplified Chinese.
    • Added new translation keys for config panel sections related to usage persistence in Simplified Chinese.
  • src/pages/UsagePage.tsx
    • Destructured usagePersistenceStatus and usagePersistenceError from useUsageData.
    • Displayed usagePersistenceStatus.lastSavedAt timestamp.
    • Displayed usagePersistenceError in an error box.
  • src/services/api/transformers.ts
    • Added normalizeNumber helper function.
    • Extended normalizeConfigResponse to parse usage-persistence section from API response.
  • src/services/api/usage.ts
    • Defined new interfaces: UsagePersistenceConfig, UsagePersistenceStatus, UsagePersistenceLoadResult.
    • Added helper functions parseUsagePersistenceConfig, parseUsagePersistenceStatus, parseUsagePersistenceLoadResult.
    • Introduced new API methods: getUsagePersistenceConfig, updateUsagePersistenceConfig, getUsagePersistenceStatus, saveUsageStatistics, loadUsageStatistics.
  • src/stores/useConfigStore.ts
    • Added 'usage-persistence' to SECTION_KEYS.
    • Extended extractSectionValue to handle usagePersistence.
    • Updated updateConfig to handle usagePersistence changes.
  • src/stores/useUsageStatsStore.ts
    • Imported UsagePersistenceConfig and UsagePersistenceStatus types.
    • Extended UsageStatsState with persistence-related state and actions.
    • Initialized new persistence state variables.
    • Implemented loadUsagePersistenceState, updateUsagePersistenceConfig, saveUsageStatsNow, and loadUsageStatsNow actions.
    • Updated clearUsageStats to reset persistence state.
  • src/types/config.ts
    • Defined the UsagePersistenceConfig type and integrated it into the main Config interface.
    • Added 'usage-persistence' to RawConfigSection type.
  • src/types/visualConfig.ts
    • Added 'usagePersistenceIntervalSeconds' to VisualConfigFieldPath.
    • Added usagePersistenceEnabled, usagePersistenceFilePath, usagePersistenceIntervalSeconds to VisualConfigValues.
    • Updated DEFAULT_VISUAL_VALUES with default values for new persistence fields.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@shaoyuanyu
Copy link
Author

should be based on router-for-me/CLIProxyAPI#2125

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively integrates usage persistence configuration and runtime status into the UI. The changes are comprehensive, covering configuration, API services, state management, and UI components. The code is well-structured and the new features are implemented as described. I have a couple of suggestions to improve code maintainability and efficiency by refactoring some repetitive parsing logic and optimizing API calls in the state store. Overall, this is a solid contribution.

Comment on lines +80 to +150
const parseUsagePersistenceStatus = (payload: unknown): UsagePersistenceStatus => {
if (!payload || typeof payload !== 'object') return {};
const record = payload as Record<string, unknown>;
return {
enabled: typeof record.enabled === 'boolean' ? record.enabled : undefined,
filePath:
typeof record['file-path'] === 'string'
? record['file-path']
: typeof record.path === 'string'
? record.path
: typeof record.filePath === 'string'
? record.filePath
: undefined,
intervalSeconds:
typeof record['interval-seconds'] === 'number'
? record['interval-seconds']
: typeof record.interval_seconds === 'number'
? record.interval_seconds
: typeof record.intervalSeconds === 'number'
? record.intervalSeconds
: undefined,
lastLoadedAt:
typeof record['last-loaded-at'] === 'string'
? record['last-loaded-at']
: typeof record.last_loaded_at === 'string'
? record.last_loaded_at
: typeof record.lastLoadedAt === 'string'
? record.lastLoadedAt
: undefined,
lastSavedAt:
typeof record['last-saved-at'] === 'string'
? record['last-saved-at']
: typeof record.last_saved_at === 'string'
? record.last_saved_at
: typeof record.lastSavedAt === 'string'
? record.lastSavedAt
: undefined,
saveCount:
typeof record['save-count'] === 'number'
? record['save-count']
: typeof record.saveCount === 'number'
? record.saveCount
: undefined,
loadCount:
typeof record['load-count'] === 'number'
? record['load-count']
: typeof record.loadCount === 'number'
? record.loadCount
: undefined,
lastLoadAdded:
typeof record['last-load-added'] === 'number'
? record['last-load-added']
: typeof record.lastLoadAdded === 'number'
? record.lastLoadAdded
: undefined,
lastLoadSkipped:
typeof record['last-load-skipped'] === 'number'
? record['last-load-skipped']
: typeof record.lastLoadSkipped === 'number'
? record.lastLoadSkipped
: undefined,
lastError:
typeof record['last-error'] === 'string'
? record['last-error']
: typeof record.last_error === 'string'
? record.last_error
: typeof record.lastError === 'string'
? record.lastError
: undefined
};
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parsing functions parseUsagePersistenceConfig, parseUsagePersistenceStatus, and parseUsagePersistenceLoadResult contain repetitive code for checking multiple possible property names (e.g., file-path, filePath). This can be simplified by using a helper function to reduce boilerplate and improve maintainability.

Here's a suggested refactoring for parseUsagePersistenceStatus. A similar pattern can be applied to the other parsing functions in this file.

const parseUsagePersistenceStatus = (payload: unknown): UsagePersistenceStatus => {
  if (!payload || typeof payload !== 'object') return {};
  const record = payload as Record<string, unknown>;

  const getTypedValue = <T extends string | number | boolean>(
    keys: string[],
    type: 'string' | 'number' | 'boolean'
  ): T | undefined => {
    for (const key of keys) {
      const value = record[key];
      if (typeof value === type) {
        return value as T;
      }
    }
    return undefined;
  };

  return {
    enabled: getTypedValue<boolean>(['enabled'], 'boolean'),
    filePath: getTypedValue<string>(['file-path', 'path', 'filePath'], 'string'),
    intervalSeconds: getTypedValue<number>(
      ['interval-seconds', 'interval_seconds', 'intervalSeconds'],
      'number'
    ),
    lastLoadedAt: getTypedValue<string>(
      ['last-loaded-at', 'last_loaded_at', 'lastLoadedAt'],
      'string'
    ),
    lastSavedAt: getTypedValue<string>(['last-saved-at', 'last_saved_at', 'lastSavedAt'], 'string'),
    saveCount: getTypedValue<number>(['save-count', 'saveCount'], 'number'),
    loadCount: getTypedValue<number>(['load-count', 'loadCount'], 'number'),
    lastLoadAdded: getTypedValue<number>(['last-load-added', 'lastLoadAdded'], 'number'),
    lastLoadSkipped: getTypedValue<number>(['last-load-skipped', 'lastLoadSkipped'], 'number'),
    lastError: getTypedValue<string>(['last-error', 'last_error', 'lastError'], 'string')
  };
};

Comment on lines +151 to +171
loadUsagePersistenceState: async () => {
set({ usagePersistenceLoading: true, usagePersistenceError: null });
try {
const [configRes, statusRes] = await Promise.all([
usageApi.getUsagePersistenceConfig(),
usageApi.getUsagePersistenceStatus(),
]);
set({
usagePersistenceConfig: configRes.config,
usagePersistenceStatus: statusRes,
usagePersistenceLoading: false,
usagePersistenceError: null,
});
} catch (error: unknown) {
set({
usagePersistenceLoading: false,
usagePersistenceError: getErrorMessage(error),
});
throw error;
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function makes two separate API calls to fetch the persistence config and status. However, usageApi.getUsagePersistenceConfig() already returns both the configuration and the runtime status in a single response. You can optimize this by making only one API call to improve efficiency and reduce network overhead.

A similar optimization can be applied to the updateUsagePersistenceConfig function on line 173.

  loadUsagePersistenceState: async () => {
    set({ usagePersistenceLoading: true, usagePersistenceError: null });
    try {
      const { config, status } = await usageApi.getUsagePersistenceConfig();
      set({
        usagePersistenceConfig: config,
        usagePersistenceStatus: status,
        usagePersistenceLoading: false,
        usagePersistenceError: null,
      });
    } catch (error: unknown) {
      set({
        usagePersistenceLoading: false,
        usagePersistenceError: getErrorMessage(error),
      });
      throw error;
    }
  },

@shaoyuanyu
Copy link
Author

What changed

1) Usage persistence config/status schema update

  • Add maxDetailsPerModel to frontend types and API parsing:
    • UsagePersistenceConfig
    • UsagePersistenceStatus
  • Support multiple key styles from backend payloads:
    • kebab-case (max-details-per-model)
    • snake_case (max_details_per_model)
    • camelCase (maxDetailsPerModel)
  • Include max-details-per-model when updating usage persistence config.

2) Chart data source optimization

  • Add model bucket collectors for:
    • requests/tokens × hour/day
  • In buildHourlySeriesByModel and buildDailySeriesByModel:
    • Prefer aggregated bucket data when available.
    • Fallback to details-based reconstruction if aggregated buckets are absent.
  • This reduces dependency on large detail arrays and aligns with backend v2 usage payload.

Why

  • Better performance and scalability for usage charts.
  • Consistent visualization with backend pre-aggregated statistics.
  • Preserves compatibility with older payloads that only contain details.

Impact

  • No breaking change for existing data.
  • Charts become more stable/efficient with v2 usage payloads.
  • Frontend config UI/API can now manage maxDetailsPerModel consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant