修复批量解密图片逻辑 加快速度

This commit is contained in:
xuncha
2026-02-28 17:32:28 +08:00
parent d63c37cd78
commit b26f8cc43c
2 changed files with 132 additions and 11 deletions

View File

@@ -3331,9 +3331,12 @@
// 批量转写模态框基础样式(共享样式在 styles/batchTranscribe.scss // 批量转写模态框基础样式(共享样式在 styles/batchTranscribe.scss
// 批量转写确认对话框 // 批量转写确认对话框
.batch-confirm-modal { .batch-modal-content.batch-confirm-modal {
width: 480px; width: 480px;
max-width: 90vw; max-width: 90vw;
max-height: none;
overflow: visible;
overflow-y: visible;
.batch-modal-header { .batch-modal-header {
display: flex; display: flex;
@@ -3470,6 +3473,74 @@
font-weight: 600; font-weight: 600;
color: var(--primary-color); color: var(--primary-color);
} }
.batch-concurrency-field {
position: relative;
.batch-concurrency-trigger {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 9999px;
border: 1px solid var(--border-color);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 13px;
cursor: pointer;
&:hover {
border-color: var(--text-tertiary);
}
&.open {
border-color: var(--primary);
}
svg {
color: var(--text-tertiary);
transition: transform 0.2s;
}
&.open svg {
transform: rotate(180deg);
}
}
.batch-concurrency-dropdown {
position: absolute;
top: calc(100% + 6px);
right: 0;
min-width: 180px;
background: color-mix(in srgb, var(--bg-primary) 90%, var(--bg-secondary));
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
z-index: 100;
}
.batch-concurrency-option {
width: 100%;
text-align: left;
padding: 8px 12px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--text-primary);
font-size: 13px;
cursor: pointer;
&:hover {
background: var(--bg-tertiary);
}
&.active {
color: var(--primary);
font-weight: 500;
}
}
}
} }
} }
@@ -3527,7 +3598,7 @@
&.btn-primary, &.btn-primary,
&.batch-transcribe-start-btn { &.batch-transcribe-start-btn {
background: var(--primary-color); background: var(--primary-color);
color: white; color: #000;
&:hover { &:hover {
opacity: 0.9; opacity: 0.9;

View File

@@ -345,6 +345,8 @@ function ChatPage(_props: ChatPageProps) {
const [batchImageMessages, setBatchImageMessages] = useState<BatchImageDecryptCandidate[] | null>(null) const [batchImageMessages, setBatchImageMessages] = useState<BatchImageDecryptCandidate[] | null>(null)
const [batchImageDates, setBatchImageDates] = useState<string[]>([]) const [batchImageDates, setBatchImageDates] = useState<string[]>([])
const [batchImageSelectedDates, setBatchImageSelectedDates] = useState<Set<string>>(new Set()) const [batchImageSelectedDates, setBatchImageSelectedDates] = useState<Set<string>>(new Set())
const [batchDecryptConcurrency, setBatchDecryptConcurrency] = useState(6)
const [showConcurrencyDropdown, setShowConcurrencyDropdown] = useState(false)
// 批量删除相关状态 // 批量删除相关状态
const [isDeleting, setIsDeleting] = useState(false) const [isDeleting, setIsDeleting] = useState(false)
@@ -1662,29 +1664,44 @@ function ChatPage(_props: ChatPageProps) {
let successCount = 0 let successCount = 0
let failCount = 0 let failCount = 0
for (let i = 0; i < images.length; i++) { let completed = 0
const img = images[i] const concurrency = batchDecryptConcurrency
const decryptOne = async (img: typeof images[0]) => {
try { try {
const r = await window.electronAPI.image.decrypt({ const r = await window.electronAPI.image.decrypt({
sessionId: session.username, sessionId: session.username,
imageMd5: img.imageMd5, imageMd5: img.imageMd5,
imageDatName: img.imageDatName, imageDatName: img.imageDatName,
force: false force: true
}) })
if (r?.success) successCount++ if (r?.success) successCount++
else failCount++ else failCount++
} catch { } catch {
failCount++ failCount++
} }
completed++
updateDecryptProgress(completed, images.length)
}
updateDecryptProgress(i + 1, images.length) // 并发池:同时跑 concurrency 个任务
if (i % 5 === 0) { const pool: Promise<void>[] = []
await new Promise(resolve => setTimeout(resolve, 0)) for (const img of images) {
const p = decryptOne(img)
pool.push(p)
if (pool.length >= concurrency) {
await Promise.race(pool)
// 移除已完成的
for (let j = pool.length - 1; j >= 0; j--) {
const settled = await Promise.race([pool[j].then(() => true), Promise.resolve(false)])
if (settled) pool.splice(j, 1)
} }
} }
}
await Promise.all(pool)
finishDecrypt(successCount, failCount) finishDecrypt(successCount, failCount)
}, [batchImageMessages, batchImageSelectedDates, currentSessionId, finishDecrypt, sessions, startDecrypt, updateDecryptProgress]) }, [batchImageMessages, batchImageSelectedDates, batchDecryptConcurrency, currentSessionId, finishDecrypt, sessions, startDecrypt, updateDecryptProgress])
const batchImageCountByDate = useMemo(() => { const batchImageCountByDate = useMemo(() => {
const map = new Map<string, number>() const map = new Map<string, number>()
@@ -2623,6 +2640,39 @@ function ChatPage(_props: ChatPageProps) {
<span className="label">:</span> <span className="label">:</span>
<span className="value">{batchImageSelectedDates.size} {batchImageSelectedCount} </span> <span className="value">{batchImageSelectedDates.size} {batchImageSelectedCount} </span>
</div> </div>
<div className="info-item">
<span className="label">:</span>
<div className="batch-concurrency-field">
<button
type="button"
className={`batch-concurrency-trigger ${showConcurrencyDropdown ? 'open' : ''}`}
onClick={() => setShowConcurrencyDropdown(!showConcurrencyDropdown)}
>
<span>{batchDecryptConcurrency === 1 ? '1最慢最稳' : batchDecryptConcurrency === 6 ? '6推荐' : batchDecryptConcurrency === 20 ? '20最快可能卡顿' : String(batchDecryptConcurrency)}</span>
<ChevronDown size={14} />
</button>
{showConcurrencyDropdown && (
<div className="batch-concurrency-dropdown">
{[
{ value: 1, label: '1最慢最稳' },
{ value: 3, label: '3' },
{ value: 6, label: '6推荐' },
{ value: 10, label: '10' },
{ value: 20, label: '20最快可能卡顿' },
].map(opt => (
<button
key={opt.value}
type="button"
className={`batch-concurrency-option ${batchDecryptConcurrency === opt.value ? 'active' : ''}`}
onClick={() => { setBatchDecryptConcurrency(opt.value); setShowConcurrencyDropdown(false) }}
>
{opt.label}
</button>
))}
</div>
)}
</div>
</div>
</div> </div>
<div className="batch-warning"> <div className="batch-warning">
<AlertCircle size={16} /> <AlertCircle size={16} />