From ac195260064045b6e7a03d20484f008fe752a6c9 Mon Sep 17 00:00:00 2001 From: palfans Date: Sat, 29 Nov 2025 21:09:32 -0600 Subject: [PATCH] Add bulk fill for descriptions and favicons Adds a favicon URL builder and extends the bulk-generation flow to fill both missing descriptions and icons. Only requires an API key when descriptions need generation, aggregates missing counts in the confirmation prompt, and updates progress tracking to reflect total targets. Improves UI copy to indicate dual completion, makes progress bar safe against zero totals, and tightens error logging when individual items fail. --- components/SettingsModal.tsx | 71 +++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/components/SettingsModal.tsx b/components/SettingsModal.tsx index 5f79d4bd..b07d9b94 100644 --- a/components/SettingsModal.tsx +++ b/components/SettingsModal.tsx @@ -53,37 +53,72 @@ const SettingsModal: React.FC = ({ onClose(); }; + const getFaviconUrl = (rawUrl: string) => { + if (!rawUrl) return ''; + try { + let formatted = rawUrl.trim(); + if (!formatted.startsWith('http://') && !formatted.startsWith('https://')) { + formatted = 'https://' + formatted; + } + const url = new URL(formatted); + return `https://www.faviconextractor.com/favicon/${url.hostname}?larger=true`; + } catch (err) { + console.warn('Failed to build favicon url for', rawUrl, err); + return ''; + } + }; + const handleBulkGenerate = async () => { - if (!localConfig.apiKey) { - alert("请先配置并保存 API Key"); + const targets = links.filter(l => !l.description || !l.icon); + if (targets.length === 0) { + alert("所有链接都已有描述和图标!"); return; } - const missingLinks = links.filter(l => !l.description); - if (missingLinks.length === 0) { - alert("所有链接都已有描述!"); + const needsDescription = targets.some(l => !l.description); + if (needsDescription && !localConfig.apiKey) { + alert("请先配置并保存 API Key,用于生成缺失的描述"); return; } - if (!confirm(`发现 ${missingLinks.length} 个链接缺少描述,确定要使用 AI 自动生成吗?这可能需要一些时间。`)) return; + const missingDescCount = targets.filter(l => !l.description).length; + const missingIconCount = targets.filter(l => !l.icon).length; + const confirmMessage = `发现 ${targets.length} 个链接缺少信息,其中 ${missingDescCount} 个缺少描述,${missingIconCount} 个缺少图标。确定要一键补全吗?这可能需要一些时间。`; + if (!confirm(confirmMessage)) return; setIsProcessing(true); shouldStopRef.current = false; - setProgress({ current: 0, total: missingLinks.length }); + setProgress({ current: 0, total: targets.length }); let currentLinks = [...links]; - for (let i = 0; i < missingLinks.length; i++) { + for (let i = 0; i < targets.length; i++) { if (shouldStopRef.current) break; - const link = missingLinks[i]; + const link = targets[i]; try { - const desc = await generateLinkDescription(link.title, link.url, localConfig); - currentLinks = currentLinks.map(l => l.id === link.id ? { ...l, description: desc } : l); - onUpdateLinks(currentLinks); - setProgress({ current: i + 1, total: missingLinks.length }); + const updates: Partial = {}; + + if (!link.description) { + const desc = await generateLinkDescription(link.title, link.url, localConfig); + updates.description = desc; + } + + if (!link.icon) { + const iconUrl = getFaviconUrl(link.url); + if (iconUrl) { + updates.icon = iconUrl; + } + } + + if (Object.keys(updates).length > 0) { + currentLinks = currentLinks.map(l => l.id === link.id ? { ...l, ...updates } : l); + onUpdateLinks(currentLinks); + } } catch (e) { - console.error(`Failed to generate for ${link.title}`, e); + console.error(`Failed to process ${link.title}`, e); + } finally { + setProgress({ current: i + 1, total: targets.length }); } } @@ -366,13 +401,13 @@ document.addEventListener('DOMContentLoaded', async () => { {isProcessing ? (
- 正在生成描述... + 正在补全描述 / 图标... {progress.current} / {progress.total}
)}