mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-13 15:10:05 +00:00
Polish settings anti-revoke and model directory UI
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user