Polish settings anti-revoke and model directory UI

This commit is contained in:
Jason
2026-05-04 21:19:44 +08:00
parent 6eb304ef94
commit 404b06ff16
2 changed files with 149 additions and 169 deletions

View File

@@ -2339,7 +2339,7 @@
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
background: var(--bg-primary);
background: color-mix(in srgb, var(--bg-primary) 96%, var(--bg-secondary) 4%);
}
.model-status-card {
@@ -2381,8 +2381,9 @@
.model-meta {
display: flex;
flex-direction: column;
flex-direction: row;
align-items: flex-start;
flex-wrap: wrap;
gap: 10px;
.status-indicator {
@@ -2408,32 +2409,6 @@
}
}
.model-path-block {
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--bg-secondary);
}
.path-label {
font-size: 11px;
font-weight: 600;
color: var(--text-tertiary);
letter-spacing: 0.02em;
}
.path-text {
font-size: 12px;
color: var(--text-tertiary);
font-family: monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
@@ -2553,6 +2528,46 @@
}
}
.model-directory-control {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
align-items: center;
gap: 8px;
padding-top: 14px;
border-top: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
input {
min-width: 0;
height: 38px;
margin-bottom: 0 !important;
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
font-size: 12px;
color: var(--text-secondary);
background: color-mix(in srgb, var(--bg-secondary) 86%, var(--bg-primary) 14%);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: default !important;
}
.btn {
height: 38px;
padding-inline: 12px;
border-radius: 8px;
white-space: nowrap;
}
}
@media (max-width: 720px) {
.model-directory-control {
grid-template-columns: minmax(0, 1fr);
.btn {
width: 100%;
}
}
}
@keyframes progress-shimmer {
0% {
transform: translateX(-100%);
@@ -2563,59 +2578,6 @@
}
}
.sub-setting {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--border-color);
.sub-label {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 8px;
}
}
.path-selector {
display: flex;
gap: 8px;
flex-wrap: nowrap;
align-items: center;
input {
margin-bottom: 0 !important;
flex: 1;
min-width: 0;
width: auto !important;
font-family: monospace;
font-size: 12px;
}
.btn-icon {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px; // Circle
border: 1px solid var(--border-color);
background: var(--bg-primary);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
&:hover {
color: var(--text-primary);
background: var(--bg-tertiary);
}
&.danger:hover {
color: var(--danger);
background: rgba(220, 38, 38, 0.1);
border-color: rgba(220, 38, 38, 0.2);
}
}
}
@keyframes spin {
from {
transform: rotate(0deg);
@@ -2912,45 +2874,41 @@
.anti-revoke-hero {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 18px;
padding: 16px;
border-radius: 10px;
flex-direction: column;
align-items: stretch;
gap: 14px;
padding: 16px 18px;
border-radius: 12px;
border: 1px solid color-mix(in srgb, var(--border-color) 88%, transparent);
flex-wrap: wrap;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bg-secondary) 94%, var(--primary) 6%) 0%,
color-mix(in srgb, var(--bg-secondary) 96%, var(--bg-primary) 4%) 100%
);
background: color-mix(in srgb, var(--bg-primary) 94%, var(--bg-secondary) 6%);
}
.anti-revoke-hero-main {
min-width: 240px;
min-width: 0;
h3 {
margin: 0;
font-size: 19px;
font-size: 18px;
font-weight: 600;
line-height: 1.3;
color: var(--text-primary);
letter-spacing: 0.3px;
letter-spacing: 0;
}
p {
margin: 8px 0 0;
margin: 7px 0 0;
font-size: 13px;
color: var(--text-secondary);
line-height: 1.6;
max-width: 560px;
}
}
.anti-revoke-metrics {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
gap: 10px;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
width: 100%;
min-width: 0;
}
@@ -2958,21 +2916,22 @@
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
padding: 12px 14px;
border-radius: 12px;
gap: 5px;
min-height: 74px;
padding: 11px 12px;
border-radius: 10px;
border: 1px solid color-mix(in srgb, var(--border-color) 90%, transparent);
background: color-mix(in srgb, var(--bg-primary) 93%, var(--bg-secondary) 7%);
background: color-mix(in srgb, var(--bg-secondary) 82%, var(--bg-primary) 18%);
.label {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.2;
letter-spacing: 0.2px;
letter-spacing: 0;
}
.value {
font-size: 30px;
font-size: 26px;
font-weight: 700;
color: var(--text-primary);
line-height: 1;
@@ -3013,25 +2972,24 @@
.anti-revoke-control-card {
border: 1px solid color-mix(in srgb, var(--border-color) 90%, transparent);
border-radius: 10px;
border-radius: 12px;
padding: 14px;
background: color-mix(in srgb, var(--bg-secondary) 95%, var(--bg-primary) 5%);
}
.anti-revoke-toolbar {
display: flex;
gap: 14px;
gap: 10px;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
flex-wrap: wrap;
}
.anti-revoke-search {
min-width: 280px;
flex: 1;
max-width: 420px;
border-radius: 10px;
max-width: none;
border-radius: 8px;
background: color-mix(in srgb, var(--bg-primary) 85%, var(--bg-secondary) 15%);
input {
@@ -3044,7 +3002,7 @@
display: flex;
align-items: stretch;
justify-content: flex-end;
gap: 10px;
gap: 8px;
flex-wrap: wrap;
margin-left: auto;
}
@@ -3056,14 +3014,50 @@
flex-wrap: wrap;
}
.anti-revoke-selection-strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
}
.anti-revoke-selection-actions {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.anti-revoke-batch-actions {
display: flex;
align-items: flex-start;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: space-between;
border-top: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
padding-top: 12px;
margin-top: 12px;
padding: 12px;
border-radius: 10px;
border: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
background: color-mix(in srgb, var(--bg-primary) 88%, var(--bg-secondary) 12%);
}
.anti-revoke-batch-copy {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
font-size: 12px;
line-height: 1.4;
color: var(--text-tertiary);
}
.anti-revoke-section-label {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.anti-revoke-selected-count {
@@ -3072,11 +3066,7 @@
gap: 14px;
font-size: 12px;
color: var(--text-secondary);
margin-left: auto;
padding: 8px 12px;
border-radius: 10px;
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
background: color-mix(in srgb, var(--bg-primary) 92%, var(--bg-secondary) 8%);
min-width: 0;
span {
position: relative;
@@ -3104,12 +3094,14 @@
}
.anti-revoke-toolbar-actions .btn,
.anti-revoke-selection-actions .btn,
.anti-revoke-batch-actions .btn {
border-radius: 10px;
border-radius: 8px;
padding-inline: 14px;
border-width: 1px;
min-height: 36px;
min-height: 34px;
justify-content: center;
white-space: nowrap;
}
.anti-revoke-summary {
@@ -3136,7 +3128,7 @@
.anti-revoke-list {
border: 1px solid color-mix(in srgb, var(--border-color) 90%, transparent);
border-radius: 16px;
border-radius: 12px;
background: var(--bg-primary);
max-height: 460px;
overflow-y: auto;
@@ -3531,14 +3523,13 @@
}
@media (max-width: 980px) {
.anti-revoke-hero {
flex-direction: column;
.anti-revoke-metrics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.anti-revoke-metrics {
width: 100%;
min-width: 0;
grid-template-columns: repeat(2, minmax(130px, 1fr));
.anti-revoke-selection-strip {
align-items: flex-start;
flex-direction: column;
}
.anti-revoke-batch-actions {
@@ -3547,7 +3538,6 @@
}
.anti-revoke-selected-count {
margin-left: 0;
width: 100%;
justify-content: flex-start;
overflow-x: auto;

View File

@@ -2189,23 +2189,32 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
/>
</div>
<div className="anti-revoke-toolbar-actions">
<div className="anti-revoke-btn-group">
<button className="btn btn-secondary btn-sm" onClick={() => void handleRefreshAntiRevokeStatus()} disabled={busy}>
<RefreshCw size={14} /> {isAntiRevokeRefreshing ? '刷新中...' : '刷新状态'}
</button>
</div>
<div className="anti-revoke-btn-group">
<button className="btn btn-secondary btn-sm" onClick={selectAllFiltered} disabled={busy || filteredSessionIds.length === 0 || allFilteredSelected}>
</button>
<button className="btn btn-secondary btn-sm" onClick={clearSelection} disabled={busy || selectedCount === 0}>
</button>
</div>
<button className="btn btn-secondary btn-sm" onClick={() => void handleRefreshAntiRevokeStatus()} disabled={busy}>
<RefreshCw size={14} /> {isAntiRevokeRefreshing ? '刷新中...' : '刷新状态'}
</button>
</div>
</div>
<div className="anti-revoke-selection-strip">
<div className="anti-revoke-selected-count">
<span> <strong>{selectedCount}</strong> </span>
<span> <strong>{selectedInFilteredCount}</strong> / {filteredSessionIds.length}</span>
</div>
<div className="anti-revoke-selection-actions">
<button className="btn btn-secondary btn-sm" onClick={selectAllFiltered} disabled={busy || filteredSessionIds.length === 0 || allFilteredSelected}>
</button>
<button className="btn btn-secondary btn-sm" onClick={clearSelection} disabled={busy || selectedCount === 0}>
</button>
</div>
</div>
<div className="anti-revoke-batch-actions">
<div className="anti-revoke-batch-copy">
<span className="anti-revoke-section-label"></span>
<span></span>
</div>
<div className="anti-revoke-btn-group anti-revoke-batch-btns">
<button className="btn btn-primary btn-sm" onClick={() => void handleInstallAntiRevokeTriggers()} disabled={busy || selectedCount === 0}>
{isAntiRevokeInstalling ? '安装中...' : '批量安装'}
@@ -2214,10 +2223,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
{isAntiRevokeUninstalling ? '卸载中...' : '批量卸载'}
</button>
</div>
<div className="anti-revoke-selected-count">
<span> <strong>{selectedCount}</strong> </span>
<span> <strong>{selectedInFilteredCount}</strong> / {filteredSessionIds.length}</span>
</div>
</div>
</div>
@@ -2516,11 +2521,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const renderModelsTab = () => (
<div className="tab-content">
<div className="form-group">
<label></label>
<span className="form-hint"></span>
</div>
<div className="form-group">
<label> (Whisper)</label>
<span className="form-hint"></span>
@@ -2538,12 +2538,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
) : (
<span className="status-indicator warning"></span>
)}
{resolvedWhisperModelPath && (
<div className="model-path-block">
<span className="path-label"></span>
<div className="path-text" title={resolvedWhisperModelPath}>{resolvedWhisperModelPath}</div>
</div>
)}
</div>
</div>
{(!whisperModelStatus?.exists || isWhisperDownloading) && (
@@ -2574,25 +2568,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
)}
</div>
)}
</div>
<div className="sub-setting">
<div className="sub-label"></div>
<div className="path-selector">
<div className="model-directory-control">
<input
type="text"
value={whisperModelDir}
value={resolvedWhisperModelPath}
readOnly
placeholder="默认目录"
title={resolvedWhisperModelPath || '默认目录'}
/>
<button className="btn-icon" onClick={handleSelectWhisperModelDir} title="选择目录">
<FolderOpen size={18} />
<button className="btn btn-secondary btn-sm" onClick={handleSelectWhisperModelDir} title="选择自定义目录">
<FolderOpen size={14} />
</button>
<button className="btn btn-secondary btn-sm" onClick={handleResetWhisperModelDir} disabled={!whisperModelDir} title="恢复默认">
<RotateCcw size={14} />
</button>
{whisperModelDir && (
<button className="btn-icon danger" onClick={handleResetWhisperModelDir} title="重置为默认">
<RotateCcw size={18} />
</button>
)}
</div>
</div>
</div>