feat(usage): add confirmed database cleanup controls

This commit is contained in:
lzt404 committed 2026-09-18 18:17:31 +08:00
1 parent 93525f0ac7
commit bd57545c7f
7 files changed
+181 -15

No files matched your search

+10 -1
View File
@@ -448,7 +448,6 @@ export const jaOverrides = {
'usage.pricing.deleted': 'モデル料金を削除し、概要のコストを更新しました',
'usage.pricing.deleteConfirm': 'モデル {model} の料金を削除しますか?',
'usage.dataManagement.title': '履歴データ管理',
'usage.dataManagement.description': '使用履歴の保存容量を管理し、既知の履歴データ異常を修復します。',
'usage.dataManagement.manualBadge': '手動操作',
'usage.dataManagement.storageTitle': 'データベース容量上限',
'usage.dataManagement.storageDescription': '0 にすると無制限です。上限に達すると古い使用履歴から自動的に削除します。',
@@ -460,6 +459,16 @@ export const jaOverrides = {
'usage.dataManagement.storageInvalid': 'データベース容量上限には 0 または正の整数を入力してください。',
'usage.dataManagement.storageSaved': 'データベース容量上限を保存しました。',
'usage.dataManagement.storageSavedWithCleanup': 'データベース容量上限を保存し、古い記録を {deleted} 件削除しました。',
'usage.dataManagement.shrinkTitle': 'データベースをワンクリックで縮小',
'usage.dataManagement.shrinkDescription': 'データベースを目標サイズに調整します。確認後、目標サイズ以下になるまで古い使用履歴から削除します。',
'usage.dataManagement.shrinkInput': '縮小後の目標サイズ(MB)',
'usage.dataManagement.shrinkRun': '今すぐ縮小',
'usage.dataManagement.shrinking': '縮小中…',
'usage.dataManagement.shrinkInvalid': '縮小後の目標サイズには 0 より大きい整数を入力してください。',
'usage.dataManagement.shrinkConfirmTitle': 'データベース縮小の確認',
'usage.dataManagement.shrinkConfirm': 'データベースを {size} MB 以下に縮小しますか?古い使用履歴から削除され、この操作は元に戻せません。',
'usage.dataManagement.shrinkSuccess': '縮小が完了しました。古い記録を {deleted} 件削除し、現在の使用量は {size} です。',
'usage.dataManagement.shrinkNoCleanup': 'データベースを整理しました。現在の使用量は {size} のため、使用履歴は削除されませんでした。',
'usage.dataManagement.actionTitle': 'キャッシュ記録を修復',
'usage.dataManagement.actionDescription': 'キャッシュ読み取りと作成を含む入力 Token の集計方式に統一します。',
'usage.dataManagement.run': 'スキャンして修復',
+10 -1
View File
@@ -435,7 +435,6 @@ export const en: Record<MessageKey, string> = {
'usage.pricing.deleted': 'Model price deleted; overview cost updated',
'usage.pricing.deleteConfirm': 'Delete the price for {model}?',
'usage.dataManagement.title': 'Historical Data Management',
'usage.dataManagement.description': 'Manage usage record storage and repair known historical data anomalies.',
'usage.dataManagement.manualBadge': 'Manual action',
'usage.dataManagement.storageTitle': 'Database size limit',
'usage.dataManagement.storageDescription': 'Set to 0 for no limit. When the limit is reached, the oldest usage records are deleted automatically.',
@@ -447,6 +446,16 @@ export const en: Record<MessageKey, string> = {
'usage.dataManagement.storageInvalid': 'Enter 0 or a positive integer for the database size limit.',
'usage.dataManagement.storageSaved': 'Database size limit saved.',
'usage.dataManagement.storageSavedWithCleanup': 'Database size limit saved and {deleted} oldest records deleted.',
'usage.dataManagement.shrinkTitle': 'One-click database cleanup',
'usage.dataManagement.shrinkDescription': 'Set the database to the target size. After confirmation, the oldest usage records are deleted until the database is at or below that size.',
'usage.dataManagement.shrinkInput': 'Cleanup target size (MB)',
'usage.dataManagement.shrinkRun': 'Clean up now',
'usage.dataManagement.shrinking': 'Cleaning up…',
'usage.dataManagement.shrinkInvalid': 'Enter a positive integer for the cleanup target size.',
'usage.dataManagement.shrinkConfirmTitle': 'Confirm database cleanup',
'usage.dataManagement.shrinkConfirm': 'Reduce the database to {size} MB or less? The oldest usage records will be deleted and this action cannot be undone.',
'usage.dataManagement.shrinkSuccess': 'Cleanup complete. Deleted {deleted} oldest records; the database now uses {size}.',
'usage.dataManagement.shrinkNoCleanup': 'Database cleanup complete. It currently uses {size}, so no usage records were deleted.',
'usage.dataManagement.actionTitle': 'Repair historical anomalies',
'usage.dataManagement.actionDescription': 'Correct Claude cache token accounting and delete records recorded as unknown usage.',
'usage.dataManagement.run': 'Scan and repair',
+10 -1
View File
@@ -434,7 +434,6 @@ export const zhCN = {
'usage.pricing.deleted': '模型价格已删除,总览成本已更新',
'usage.pricing.deleteConfirm': '确定删除模型 {model} 的价格吗?',
'usage.dataManagement.title': '历史数据管理',
'usage.dataManagement.description': '管理使用记录存储并修复历史数据中的已知异常。',
'usage.dataManagement.manualBadge': '手动操作',
'usage.dataManagement.storageTitle': '数据库大小限制',
'usage.dataManagement.storageDescription': '设为 0 时不限制;达到上限后自动删除最旧的使用记录。',
@@ -446,6 +445,16 @@ export const zhCN = {
'usage.dataManagement.storageInvalid': '请输入 0 或正整数作为数据库大小上限。',
'usage.dataManagement.storageSaved': '数据库大小限制已保存。',
'usage.dataManagement.storageSavedWithCleanup': '数据库大小限制已保存,并删除了最旧的 {deleted} 条记录。',
'usage.dataManagement.shrinkTitle': '数据库一键瘦身',
'usage.dataManagement.shrinkDescription': '将数据库大小调整到目标大小,确认后将从最旧的使用记录开始删除,直到数据库小于或等于目标大小。',
'usage.dataManagement.shrinkInput': '瘦身目标大小(MB)',
'usage.dataManagement.shrinkRun': '一键瘦身',
'usage.dataManagement.shrinking': '瘦身中…',
'usage.dataManagement.shrinkInvalid': '请输入大于 0 的整数作为瘦身目标大小。',
'usage.dataManagement.shrinkConfirmTitle': '确认数据库瘦身',
'usage.dataManagement.shrinkConfirm': '确定将数据库瘦身到 {size} MB 以内吗?确认后将从最旧的使用记录开始删除,此操作无法撤销。',
'usage.dataManagement.shrinkSuccess': '瘦身完成,已删除最旧的 {deleted} 条记录,当前占用 {size}。',
'usage.dataManagement.shrinkNoCleanup': '数据库已整理,当前占用 {size},无需删除使用记录。',
'usage.dataManagement.actionTitle': '修复历史异常记录',
'usage.dataManagement.actionDescription': '修正错误缓存信息,并删除被误记为 unknown 的记录。',
'usage.dataManagement.run': '扫描并修复',
+75 -11
View File
@@ -733,6 +733,8 @@ function UsageDataManagementView() {
const [limitDraft, setLimitDraft] = useState('0');
const [loadingLimit, setLoadingLimit] = useState(true);
const [savingLimit, setSavingLimit] = useState(false);
const [shrinkDraft, setShrinkDraft] = useState('');
const [shrinking, setShrinking] = useState(false);
const [storageNotice, setStorageNotice] = useState('');
useEffect(() => {
@@ -781,6 +783,40 @@ function UsageDataManagementView() {
}
};
const shrinkDatabase = async () => {
const normalized = shrinkDraft.trim();
const targetDatabaseSizeMb = Number(normalized);
if (!/^\d+$/.test(normalized) || !Number.isSafeInteger(targetDatabaseSizeMb) || targetDatabaseSizeMb <= 0) {
setError(t('usage.dataManagement.shrinkInvalid'));
return;
}
const confirmed = await askConfirmation({
title: t('usage.dataManagement.shrinkConfirmTitle'),
message: t('usage.dataManagement.shrinkConfirm', { size: targetDatabaseSizeMb }),
});
if (!confirmed) return;
setShrinking(true);
setError('');
setStorageNotice('');
try {
const next = await invoke<UsageStorageSettings>('shrink_usage_database', { targetDatabaseSizeMb });
setStorage(next);
setStorageNotice(t(
next.deletedRecords > 0
? 'usage.dataManagement.shrinkSuccess'
: 'usage.dataManagement.shrinkNoCleanup',
{
deleted: next.deletedRecords.toLocaleString(),
size: formatStorageBytes(next.databaseSizeBytes),
},
));
} catch (requestError) {
setError(String(requestError));
} finally {
setShrinking(false);
}
};
const repair = async () => {
if (!await askConfirmation({ title: t('usage.dataManagement.title'), message: t('usage.dataManagement.confirm') })) return;
setRunning(true);
@@ -804,10 +840,7 @@ function UsageDataManagementView() {
<div className="usage-data-management-heading">
<div>
<Wrench size={20} aria-hidden="true" />
<div>
<h2>{t('usage.dataManagement.title')}</h2>
<p>{t('usage.dataManagement.description')}</p>
</div>
<h2>{t('usage.dataManagement.title')}</h2>
</div>
<span className="usage-data-management-badge">{t('usage.dataManagement.manualBadge')}</span>
</div>
@@ -828,15 +861,14 @@ function UsageDataManagementView() {
<div className="usage-storage-limit-editor">
<label>
<input
type="number"
min="0"
step="1"
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={limitDraft}
disabled={loadingLimit || savingLimit}
disabled={loadingLimit || savingLimit || shrinking || running}
onChange={(event) => setLimitDraft(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === 'Enter' && !loadingLimit && !savingLimit) void saveStorageLimit();
if (event.key === 'Enter' && !loadingLimit && !savingLimit && !shrinking && !running) void saveStorageLimit();
}}
aria-label={t('usage.dataManagement.storageInput')}
/>
@@ -846,13 +878,45 @@ function UsageDataManagementView() {
type="button"
className="primary-button"
onClick={() => void saveStorageLimit()}
disabled={loadingLimit || savingLimit}
disabled={loadingLimit || savingLimit || shrinking || running}
>
{savingLimit ? t('usage.dataManagement.storageSaving') : t('usage.dataManagement.storageSave')}
</button>
</div>
</div>
<div className="usage-data-management-action">
<div>
<strong>{t('usage.dataManagement.shrinkTitle')}</strong>
<span>{t('usage.dataManagement.shrinkDescription')}</span>
</div>
<div className="usage-storage-limit-editor">
<label>
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={shrinkDraft}
disabled={savingLimit || shrinking || running}
onChange={(event) => setShrinkDraft(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === 'Enter' && !savingLimit && !shrinking && !running) void shrinkDatabase();
}}
aria-label={t('usage.dataManagement.shrinkInput')}
/>
<span>{t('usage.dataManagement.storageUnit')}</span>
</label>
<button
type="button"
className="primary-button"
onClick={() => void shrinkDatabase()}
disabled={savingLimit || shrinking || running}
>
{shrinking ? t('usage.dataManagement.shrinking') : t('usage.dataManagement.shrinkRun')}
</button>
</div>
</div>
{storageNotice ? <MessageNotice tone="success" message={storageNotice} onDismiss={() => setStorageNotice('')} /> : null}
<div className="usage-data-management-action">
@@ -860,7 +924,7 @@ function UsageDataManagementView() {
<strong>{t('usage.dataManagement.actionTitle')}</strong>
<span>{t('usage.dataManagement.actionDescription')}</span>
</div>
<button type="button" className="primary-button" onClick={() => void repair()} disabled={running}>
<button type="button" className="primary-button" onClick={() => void repair()} disabled={running || savingLimit || shrinking}>
{running ? t('usage.dataManagement.running') : t('usage.dataManagement.run')}
</button>
</div>
+3 -1
View File
@@ -9399,7 +9399,9 @@ body.table-col-resizing * {
}
.usage-storage-limit-editor input {
width: 104px;
width: 168px;
min-height: 40px;
padding: 0 12px;
border: 0;
background: transparent;
box-shadow: none;