From 93bdac7fb87cf63ecd1d1fff17f85749ea936b01 Mon Sep 17 00:00:00 2001 From: shaocc1234 <363606263@qq.com> Date: Mon, 27 Apr 2026 10:46:53 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B0=8F=E7=BA=A2?= =?UTF-8?q?=E4=B9=A6=E7=AC=94=E8=AE=B0=E8=A7=86=E9=A2=91/=E5=9B=BE?= =?UTF-8?q?=E6=96=87=E5=88=86=E7=B1=BB=E8=AF=86=E5=88=AB=E5=8F=8A=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=99=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. desktop/electron/main.ts: - persistStructuredKnowledgeNote() 保存笔记时写入 captureKind 字段, 使前端能正确区分视频/图文笔记 2. desktop/src/pages/Knowledge.tsx: - 卡片 kind 映射增加 note.hasVideo 兜底,兼容旧笔记 - 新增 all-image / all-video 聚合快捷筛选器(图文/视频跨平台分类) - 修复「全部」计数来源错误 - 搜索防抖从 180ms 调整为 500ms --- desktop/electron/main.ts | 1 + desktop/src/pages/Knowledge.tsx | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts index 6681605..4de4bad 100644 --- a/desktop/electron/main.ts +++ b/desktop/electron/main.ts @@ -10562,6 +10562,7 @@ async function persistStructuredKnowledgeNote(input: { const meta: Record = { id: noteId, type: input.kind || 'webpage', + captureKind: input.kind || '', title: input.title || existingMeta?.title || '未命名内容', author: input.author || existingMeta?.author || '未知', authorProfileUrl: input.authorProfileUrl || existingMeta?.authorProfileUrl || '', diff --git a/desktop/src/pages/Knowledge.tsx b/desktop/src/pages/Knowledge.tsx index da9b3ac..1e0dae3 100644 --- a/desktop/src/pages/Knowledge.tsx +++ b/desktop/src/pages/Knowledge.tsx @@ -53,7 +53,7 @@ interface YouTubeVideo { folderPath?: string; } -type KnowledgeTypeFilter = 'all' | 'xhs-image' | 'xhs-video' | 'douyin-video' | 'link-article' | 'wechat-article' | 'youtube' | 'docs'; +type KnowledgeTypeFilter = 'all' | 'xhs-image' | 'xhs-video' | 'douyin-video' | 'link-article' | 'wechat-article' | 'youtube' | 'docs' | 'all-image' | 'all-video'; interface DocumentKnowledgeSource { id: string; @@ -713,10 +713,11 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = void loadAllKnowledge(); }, [loadAllKnowledge]); + // 搜索输入防抖:避免打字过程中频繁触发搜索 useEffect(() => { const timeout = window.setTimeout(() => { void loadAllKnowledge(); - }, 180); + }, 500); return () => window.clearTimeout(timeout); }, [searchQuery, selectedTypeFilter, loadAllKnowledge]); @@ -843,7 +844,7 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = : 'link-article' : note.captureKind === 'douyin-video' ? 'douyin-video' - : (note.captureKind === 'xhs-video' || note.video) + : (note.captureKind === 'xhs-video' || note.video || note.hasVideo) ? 'xhs-video' : 'xhs-image'; @@ -909,8 +910,11 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = } counts[item.kind] += 1; }); - return [ - { key: 'all' as const, label: '全部', count: Number(kindCounts['redbook-note'] || 0) + counts.youtube + counts.docs }, + // 聚合类型数量 + const allImageCount = (counts['xhs-image'] || 0) + (counts['link-article'] || 0) + (counts['wechat-article'] || 0); + const allVideoCount = (counts['xhs-video'] || 0) + (counts['douyin-video'] || 0) + (counts['youtube'] || 0); + const platformFilters = [ + { key: 'all' as const, label: '全部', count: knowledgeItems.length + youtubeVideos.length + documentSources.length }, { key: 'xhs-image' as const, label: '小红书图文', count: counts['xhs-image'] }, { key: 'xhs-video' as const, label: '小红书视频', count: counts['xhs-video'] }, { key: 'douyin-video' as const, label: '抖音视频', count: counts['douyin-video'] }, @@ -919,15 +923,29 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = { key: 'youtube' as const, label: 'YouTube', count: counts.youtube }, { key: 'docs' as const, label: '文档', count: counts.docs }, ].filter((item) => item.key === 'all' || item.count > 0); + // 聚合快捷筛选器置于末尾 + const aggFilters = [ + ...(allImageCount > 0 ? [{ key: 'all-image' as const, label: '图文' }] : []), + ...(allVideoCount > 0 ? [{ key: 'all-video' as const, label: '视频' }] : []), + ]; + return [...platformFilters, ...aggFilters]; }, [kindCounts, knowledgeItems]); const youtubeSummaryPendingCount = useMemo(() => { return youtubeVideos.filter((video) => video.hasSubtitle && !String(video.summary || '').trim()).length; }, [youtubeVideos]); + // all-image: 图文笔记(跨平台纯图片/文字内容) + // all-video: 视频笔记(跨平台视频内容) const filteredKnowledgeItems = useMemo(() => { + const IMAGE_KINDS = new Set(['xhs-image', 'link-article', 'wechat-article']); + const VIDEO_KINDS = new Set(['xhs-video', 'douyin-video', 'youtube']); const filtered = knowledgeItems.filter((item) => { - if (selectedTypeFilter !== 'all' && item.kind !== selectedTypeFilter) { + if (selectedTypeFilter === 'all-image') { + if (!IMAGE_KINDS.has(item.kind)) return false; + } else if (selectedTypeFilter === 'all-video') { + if (!VIDEO_KINDS.has(item.kind)) return false; + } else if (selectedTypeFilter !== 'all' && item.kind !== selectedTypeFilter) { return false; } if (selectedTag && !item.tags.includes(selectedTag)) { From 0383ef96968483abb2008d7fa6568cd46c667b89 Mon Sep 17 00:00:00 2001 From: shaocc1234 <363606263@qq.com> Date: Mon, 27 Apr 2026 11:56:30 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=E5=BA=93=E7=AD=9B=E9=80=89=E5=99=A8=20UI=20=E5=8F=8A?= =?UTF-8?q?=20All=20Tags=20=E6=B7=B1=E8=89=B2=E6=A8=A1=E5=BC=8F=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增「仅图文」「仅视频」跨平台聚合筛选器(Checkbox 形态,与平台按钮风格区分) - 移除聚合筛选器无意义的计数 badge - All Tags 按钮及标签项改用设计 token(bg-surface-*),替代硬编码 bg-black/*,修复深色模式适配 - 全部标签抽屉容器及内部元素深色模式适配 --- desktop/src/pages/Knowledge.tsx | 91 ++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/desktop/src/pages/Knowledge.tsx b/desktop/src/pages/Knowledge.tsx index 1e0dae3..fbc0091 100644 --- a/desktop/src/pages/Knowledge.tsx +++ b/desktop/src/pages/Knowledge.tsx @@ -910,23 +910,20 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = } counts[item.kind] += 1; }); - // 聚合类型数量 - const allImageCount = (counts['xhs-image'] || 0) + (counts['link-article'] || 0) + (counts['wechat-article'] || 0); - const allVideoCount = (counts['xhs-video'] || 0) + (counts['douyin-video'] || 0) + (counts['youtube'] || 0); const platformFilters = [ - { key: 'all' as const, label: '全部', count: knowledgeItems.length + youtubeVideos.length + documentSources.length }, - { key: 'xhs-image' as const, label: '小红书图文', count: counts['xhs-image'] }, - { key: 'xhs-video' as const, label: '小红书视频', count: counts['xhs-video'] }, - { key: 'douyin-video' as const, label: '抖音视频', count: counts['douyin-video'] }, - { key: 'link-article' as const, label: '链接文章', count: counts['link-article'] }, - ...(SHOW_WECHAT_KNOWLEDGE_ACTIONS ? [{ key: 'wechat-article' as const, label: '公众号文章', count: counts['wechat-article'] }] : []), - { key: 'youtube' as const, label: 'YouTube', count: counts.youtube }, - { key: 'docs' as const, label: '文档', count: counts.docs }, + { key: 'all' as const, label: '全部', count: knowledgeItems.length + youtubeVideos.length + documentSources.length, isAggregate: false }, + { key: 'xhs-image' as const, label: '小红书图文', count: counts['xhs-image'], isAggregate: false }, + { key: 'xhs-video' as const, label: '小红书视频', count: counts['xhs-video'], isAggregate: false }, + { key: 'douyin-video' as const, label: '抖音视频', count: counts['douyin-video'], isAggregate: false }, + { key: 'link-article' as const, label: '链接文章', count: counts['link-article'], isAggregate: false }, + ...(SHOW_WECHAT_KNOWLEDGE_ACTIONS ? [{ key: 'wechat-article' as const, label: '公众号文章', count: counts['wechat-article'], isAggregate: false }] : []), + { key: 'youtube' as const, label: 'YouTube', count: counts.youtube, isAggregate: false }, + { key: 'docs' as const, label: '文档', count: counts.docs, isAggregate: false }, ].filter((item) => item.key === 'all' || item.count > 0); - // 聚合快捷筛选器置于末尾 + // 聚合快捷筛选器(无计数,始终显示) const aggFilters = [ - ...(allImageCount > 0 ? [{ key: 'all-image' as const, label: '图文' }] : []), - ...(allVideoCount > 0 ? [{ key: 'all-video' as const, label: '视频' }] : []), + { key: 'all-image' as const, label: '仅图文', isAggregate: true }, + { key: 'all-video' as const, label: '仅视频', isAggregate: true }, ]; return [...platformFilters, ...aggFilters]; }, [kindCounts, knowledgeItems]); @@ -1630,21 +1627,43 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = key={item.key} onClick={() => setSelectedTypeFilter(item.key)} className={clsx( - 'shrink-0 px-3.5 py-1.5 text-[12px] font-bold rounded-xl border transition-all flex items-center gap-2 active:scale-95', + item.isAggregate + ? 'shrink-0 px-3 py-1 text-[12px] rounded-lg border transition-all flex items-center gap-1.5 active:scale-95 font-medium' + : 'shrink-0 px-3.5 py-1.5 text-[12px] font-bold rounded-xl border transition-all flex items-center gap-2 active:scale-95', selectedTypeFilter === item.key - ? 'border-transparent bg-accent-primary text-white shadow-lg shadow-accent-primary/20' - : 'border-border/70 bg-surface-secondary/70 text-text-secondary hover:bg-surface-tertiary/70 hover:text-text-primary' + ? item.isAggregate + ? 'border-accent-primary/60 bg-accent-primary/10 text-accent-primary' + : 'border-transparent bg-accent-primary text-white shadow-lg shadow-accent-primary/20' + : item.isAggregate + ? 'border-border/50 bg-transparent text-text-tertiary hover:border-border hover:text-text-secondary' + : 'border-border/70 bg-surface-secondary/70 text-text-secondary hover:bg-surface-tertiary/70 hover:text-text-primary' )} > + {item.isAggregate && ( + + {selectedTypeFilter === item.key && ( + + + + )} + + )} {item.label} - - {item.count} - + {item.count !== undefined && ( + + {item.count} + + )} ))} @@ -1747,8 +1766,8 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = className={clsx( 'shrink-0 px-3 py-1 text-[11px] font-bold rounded-lg transition-all border uppercase tracking-wider inline-flex items-center gap-1.5', !selectedTag - ? 'bg-black/[0.04] text-text-primary border-transparent shadow-sm' - : 'bg-transparent text-text-tertiary border-transparent hover:bg-black/[0.03] hover:text-text-secondary' + ? 'bg-surface-secondary text-text-primary border-transparent shadow-sm' + : 'bg-transparent text-text-tertiary border-transparent hover:bg-surface-secondary hover:text-text-secondary' )} > All Tags @@ -1756,8 +1775,8 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = className={clsx( 'inline-flex items-center justify-center rounded-md px-1.5 py-0.5 text-[9px] font-bold', !selectedTag - ? 'bg-black/5 text-text-tertiary/80' - : 'bg-black/[0.04] text-text-tertiary/70' + ? 'bg-surface-primary text-text-tertiary/80' + : 'bg-surface-secondary text-text-tertiary/70' )} > {allTags.length} @@ -1779,7 +1798,7 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = 'shrink-0 px-3 py-1 text-[11px] rounded-lg transition-all flex items-center gap-1.5 border font-bold', selectedTag === tag ? 'bg-accent-primary text-white border-transparent shadow-md shadow-accent-primary/20' - : 'bg-black/[0.02] text-text-tertiary border-transparent hover:bg-black/[0.04] hover:text-text-primary' + : 'bg-surface-secondary text-text-tertiary border-transparent hover:bg-surface-tertiary hover:text-text-primary' )} > # @@ -1789,7 +1808,7 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = 'text-[9px] py-0.5 px-1.5 rounded-md font-bold', selectedTag === tag ? 'bg-white/20 text-white' - : 'bg-black/5 text-text-tertiary/60' + : 'bg-surface-primary text-text-tertiary/60' )} > {count} @@ -1800,8 +1819,8 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded = {!selectedTag && isAllTagsDrawerOpen && hasHiddenTags && (
-
-
+
+
全部标签
@@ -1810,7 +1829,7 @@ export function Knowledge({ onNavigateToChat, onNavigateToRedClaw, isEmbedded =
+
+ + + +
任务队列
diff --git a/Plugin/sidepanel.js b/Plugin/sidepanel.js index 9a38d91..c042027 100644 --- a/Plugin/sidepanel.js +++ b/Plugin/sidepanel.js @@ -14,6 +14,22 @@ const elements = { captureSubtitle: document.getElementById('capture-subtitle'), captureActions: document.getElementById('capture-actions'), captureStatus: document.getElementById('capture-status'), + bloggerNotesPanel: document.getElementById('blogger-notes-panel'), + bloggerNotesModePill: document.getElementById('blogger-notes-mode-pill'), + bloggerNotesApiMode: document.getElementById('blogger-notes-api-mode'), + bloggerNotesModeLabel: document.getElementById('blogger-notes-mode-label'), + bloggerNotesLimit: document.getElementById('blogger-notes-limit'), + bloggerNotesIntervalMax: document.getElementById('blogger-notes-interval-max'), + bloggerNotesStart: document.getElementById('blogger-notes-start'), + bloggerNotesProgress: document.getElementById('blogger-notes-progress'), + bloggerNotesProgressLabel: document.getElementById('blogger-notes-progress-label'), + bloggerNotesProgressPercent: document.getElementById('blogger-notes-progress-percent'), + bloggerNotesProgressFill: document.getElementById('blogger-notes-progress-fill'), + bloggerNotesProgressMeta: document.getElementById('blogger-notes-progress-meta'), + bloggerNotesControls: document.getElementById('blogger-notes-controls'), + bloggerNotesPause: document.getElementById('blogger-notes-pause'), + bloggerNotesResume: document.getElementById('blogger-notes-resume'), + bloggerNotesCancel: document.getElementById('blogger-notes-cancel'), taskQueueBadge: document.getElementById('task-queue-badge'), taskCurrent: document.getElementById('task-current'), taskQueueMeta: document.getElementById('task-queue-meta'), @@ -26,6 +42,19 @@ let refreshing = false; let capturePendingAction = ''; let captureFeedback = null; let captureSignature = ''; +let currentSettings = { + xhsBloggerNoteLimit: 50, + xhsIntervalMaxSeconds: 6, + xhsBloggerCollectionMode: 'api', +}; + +function debugLog(scope, details) { + console.debug(`[redbox-plugin][sidepanel][${scope}]`, details); +} + +function debugWarn(scope, details) { + console.warn(`[redbox-plugin][sidepanel][${scope}]`, details); +} init().catch((error) => { renderConnection(false, error instanceof Error ? error.message : String(error)); @@ -47,6 +76,13 @@ async function init() { function bindEvents() { elements.refresh.addEventListener('click', () => void refreshContext()); elements.openSettings.addEventListener('click', () => chrome.runtime.openOptionsPage()); + elements.bloggerNotesApiMode.addEventListener('change', () => { + renderBloggerNotesMode(); + }); + elements.bloggerNotesStart.addEventListener('click', () => void startBloggerNotesCollection()); + elements.bloggerNotesPause.addEventListener('click', () => void controlBloggerNotesTask('pause')); + elements.bloggerNotesResume.addEventListener('click', () => void controlBloggerNotesTask('resume')); + elements.bloggerNotesCancel.addEventListener('click', () => void controlBloggerNotesTask('cancel')); elements.captureActions.addEventListener('click', (event) => { const button = event.target?.closest?.('button[data-action]'); if (!button) return; @@ -64,8 +100,13 @@ function bindEvents() { }); chrome.runtime?.onMessage?.addListener((message) => { if (message?.type === 'xhs:task-queue:update') { + debugLog('task-queue-update', message.queue || {}); renderTaskQueue(message.queue || {}); renderTaskLogs(message.queue?.logs || []); + renderBloggerNotesPanel({ + ...context, + queue: message.queue || {}, + }); } }); } @@ -83,9 +124,24 @@ async function refreshContext() { refreshing = true; elements.refresh.disabled = true; try { - context = await sendMessage({ type: 'sidepanel:get-context' }); + const [nextContext, settingsResponse] = await Promise.all([ + sendMessage({ type: 'sidepanel:get-context' }), + sendMessage({ type: 'settings:get' }), + ]); + context = nextContext; + currentSettings = { + ...currentSettings, + ...(settingsResponse?.settings || {}), + }; + debugLog('refresh-context', { + context: nextContext, + settings: currentSettings, + }); renderContext(context); } catch (error) { + debugWarn('refresh-context-failed', { + error: error instanceof Error ? error.message : String(error), + }); renderConnection(false, error instanceof Error ? error.message : String(error)); renderPageIdentity({ platform: 'redbox', @@ -105,6 +161,7 @@ function renderContext(nextContext) { renderConnection(Boolean(health.success), health.error || ''); renderPageIdentity(resolvePageIdentity(nextContext)); renderCaptureActions(nextContext); + renderBloggerNotesPanel(nextContext); renderTaskQueue(nextContext?.queue || {}); renderTaskLogs(nextContext?.logs || nextContext?.queue?.logs || []); } @@ -238,6 +295,12 @@ async function runCaptureAction(action) { renderCaptureActions(context); try { const tab = context?.tab || {}; + debugLog('capture-action-start', { + action, + messageType: meta.type, + tabId, + tabUrl: tab.url || '', + }); const response = await sendMessage({ type: meta.type, tabId, @@ -248,12 +311,20 @@ async function runCaptureAction(action) { renderTaskQueue(response.taskQueue); renderTaskLogs(response.taskQueue.logs || []); } + debugLog('capture-action-success', { + action, + response, + }); captureFeedback = { status: 'success', message: summarizeActionResponse(response, meta.done), }; await refreshTaskQueue(false); } catch (error) { + debugWarn('capture-action-failed', { + action, + error: error instanceof Error ? error.message : String(error), + }); captureFeedback = { status: 'error', message: `执行失败:${error instanceof Error ? error.message : String(error)}`, @@ -271,6 +342,206 @@ function renderCaptureStatus(message, status = 'idle') { elements.captureStatus.hidden = !message; } +function renderBloggerNotesMode() { + const apiMode = elements.bloggerNotesApiMode.checked; + elements.bloggerNotesModeLabel.textContent = apiMode ? 'API 模式(更快)' : '传统模式(更稳定)'; + elements.bloggerNotesModePill.textContent = apiMode ? 'API 模式' : '传统模式'; +} + +function applyBloggerNotesSettings() { + if (elements.bloggerNotesPanel.dataset.hydrated === 'true') { + renderBloggerNotesMode(); + return; + } + const defaultMode = String(currentSettings?.xhsBloggerCollectionMode || 'api') !== 'tab'; + elements.bloggerNotesApiMode.checked = defaultMode; + elements.bloggerNotesLimit.value = Number(currentSettings?.xhsBloggerNoteLimit || 50); + elements.bloggerNotesIntervalMax.value = Math.max(3, Number(currentSettings?.xhsIntervalMaxSeconds || 6)); + elements.bloggerNotesPanel.dataset.hydrated = 'true'; + renderBloggerNotesMode(); +} + +function getBloggerNotesOptions() { + const limit = Math.max(1, Math.min(Number(elements.bloggerNotesLimit.value || 50), 200)); + const intervalMaxSeconds = Math.max(3, Math.min(Number(elements.bloggerNotesIntervalMax.value || 6), 60)); + return { + mode: elements.bloggerNotesApiMode.checked ? 'api' : 'tab', + limit, + interval: { + minSeconds: 3, + maxSeconds: intervalMaxSeconds, + }, + }; +} + +async function startBloggerNotesCollection() { + const tabId = Number(context?.tab?.id || 0); + if (!tabId) { + renderBloggerNotesProgress({ + label: '未识别到当前标签页', + meta: '请刷新后重试', + status: 'error', + }); + return; + } + elements.bloggerNotesStart.disabled = true; + renderBloggerNotesProgress({ + label: '正在创建采集任务…', + meta: '准备提交到后台队列', + status: 'pending', + }); + try { + const tab = context?.tab || {}; + const options = getBloggerNotesOptions(); + debugLog('blogger-notes-start', { + tabId, + tabUrl: tab.url || '', + options, + }); + const response = await sendMessage({ + type: 'xhs:collect-blogger-notes', + tabId, + tabUrl: tab.url || '', + windowId: Number(tab.windowId || 0) || undefined, + options, + }); + debugLog('blogger-notes-start-success', response); + if (response.taskQueue) { + renderTaskQueue(response.taskQueue); + renderTaskLogs(response.taskQueue.logs || []); + renderBloggerNotesPanel({ + ...context, + queue: response.taskQueue, + }); + } + renderBloggerNotesProgress({ + label: '采集任务已启动', + meta: summarizeActionResponse(response, '采集任务已加入队列'), + status: 'success', + }); + await refreshTaskQueue(false); + } catch (error) { + debugWarn('blogger-notes-start-failed', { + error: error instanceof Error ? error.message : String(error), + }); + renderBloggerNotesProgress({ + label: '启动失败', + meta: error instanceof Error ? error.message : String(error), + status: 'error', + }); + } finally { + elements.bloggerNotesStart.disabled = false; + } +} + +async function controlBloggerNotesTask(action) { + try { + debugLog('blogger-notes-control', { action }); + const response = await sendMessage({ type: 'xhs:control-active-task', action }); + debugLog('blogger-notes-control-success', response); + renderTaskQueue(response.queue || {}); + renderTaskLogs(response.queue?.logs || []); + renderBloggerNotesPanel({ + ...context, + queue: response.queue || {}, + }); + } catch (error) { + debugWarn('blogger-notes-control-failed', { + action, + error: error instanceof Error ? error.message : String(error), + }); + renderBloggerNotesProgress({ + label: '操作失败', + meta: error instanceof Error ? error.message : String(error), + status: 'error', + }); + } +} + +function renderBloggerNotesProgress({ + label = '', + status = 'idle', + current = 0, + total = 0, + meta = '', +} = {}) { + const safeTotal = Math.max(Number(total || 0), 0); + const safeCurrent = Math.max(Number(current || 0), 0); + const percentage = safeTotal > 0 ? Math.max(0, Math.min(100, Math.round((safeCurrent / safeTotal) * 100))) : 0; + const hasContent = Boolean(label || meta || status === 'pending' || status === 'success' || status === 'error'); + elements.bloggerNotesProgress.dataset.state = status; + elements.bloggerNotesProgress.hidden = !hasContent; + elements.bloggerNotesProgressLabel.textContent = label || '准备开始采集'; + elements.bloggerNotesProgressPercent.textContent = `${percentage}%`; + elements.bloggerNotesProgressFill.style.width = `${percentage}%`; + elements.bloggerNotesProgressMeta.textContent = meta || (safeTotal > 0 ? `已完成 ${safeCurrent} / ${safeTotal}` : '等待任务开始'); +} + +function renderBloggerNotesPanel(nextContext) { + const tab = nextContext?.tab || {}; + const pageInfo = nextContext?.pageInfo || {}; + const identity = nextContext?.pageIdentity || {}; + const platform = normalizePlatform(identity.platform || pageInfo.platform || tab.hostname || pageInfo.kind || tab.url); + const pageType = identity.pageType || inferPageType(pageInfo, tab); + const visible = platform === 'xhs' && pageType === 'profile'; + elements.bloggerNotesPanel.classList.toggle('hidden', !visible); + if (!visible) { + elements.bloggerNotesControls.classList.add('hidden'); + elements.bloggerNotesPause.classList.add('hidden'); + elements.bloggerNotesResume.classList.add('hidden'); + elements.bloggerNotesCancel.classList.add('hidden'); + return; + } + + applyBloggerNotesSettings(); + const active = nextContext?.queue?.active || null; + const last = nextContext?.queue?.last || null; + const isRunning = active?.type === 'xhs:collect-blogger-notes'; + const paused = active?.paused === true; + const progress = active?.progress || null; + const disabled = !nextContext?.health?.success || Boolean(capturePendingAction) || isRunning; + + elements.bloggerNotesStart.disabled = disabled; + elements.bloggerNotesApiMode.disabled = isRunning; + elements.bloggerNotesLimit.disabled = isRunning; + elements.bloggerNotesIntervalMax.disabled = isRunning; + + elements.bloggerNotesControls.classList.toggle('hidden', !isRunning); + elements.bloggerNotesPause.classList.toggle('hidden', !isRunning || paused); + elements.bloggerNotesResume.classList.toggle('hidden', !isRunning || !paused); + elements.bloggerNotesCancel.classList.toggle('hidden', !isRunning); + + if (isRunning && progress) { + renderBloggerNotesProgress({ + label: paused ? '任务已暂停' : (progress.message || '正在采集博主笔记'), + status: paused ? 'idle' : 'pending', + current: Number(progress.current || 0), + total: Number(progress.total || 0), + meta: `已完成 ${Number(progress.current || 0)} / ${Number(progress.total || 0)}${paused ? ' · 点击继续恢复' : ''}`, + }); + } else if (!nextContext?.health?.success) { + renderBloggerNotesProgress({ + label: '桌面端未连接', + meta: '请先打开 RedBox 桌面端', + status: 'error', + }); + } else if (last?.type === 'xhs:collect-blogger-notes' && last?.status === 'cancelled') { + renderBloggerNotesProgress({ + label: '采集博主笔记已取消', + meta: `已保存 ${Number(last?.savedCount || 0)} 条`, + status: 'error', + current: Number(last?.savedCount || 0), + total: Math.max(Number(last?.progress?.total || 0), Number(last?.savedCount || 0)), + }); + } else { + renderBloggerNotesProgress({ + label: '准备开始采集', + meta: '设置模式、数量和间隔后即可开始', + status: 'idle', + }); + } +} + function getCaptureActionConfig(nextContext) { const tab = nextContext?.tab || {}; const pageInfo = nextContext?.pageInfo || {}; @@ -292,7 +563,6 @@ function getCaptureActionConfig(nextContext) { subtitle: '小红书博主页', actions: [ { label: '保存博主', action: 'blogger', primary: true, title: '保存当前博主资料到 RedBox' }, - { label: '采集博主笔记', action: 'bloggerNotes', title: '采集当前博主主页笔记' }, ], }; } @@ -421,6 +691,10 @@ async function refreshTaskQueue(showErrors = false) { const response = await sendMessage({ type: 'xhs:get-task-queue' }); renderTaskQueue(response.queue || {}); renderTaskLogs(response.queue?.logs || []); + renderBloggerNotesPanel({ + ...context, + queue: response.queue || {}, + }); } catch (error) { if (showErrors) { renderTaskQueue({ @@ -441,7 +715,13 @@ function renderTaskQueue(queue) { elements.taskQueueBadge.textContent = queued.length > 0 ? `执行中 · 排队 ${queued.length}` : '执行中'; elements.taskQueueBadge.className = 'task-badge running'; elements.taskCurrent.textContent = active.title || '小红书采集任务'; + const progressText = active?.progress?.total + ? `进度 ${Number(active.progress.current || 0)}/${Number(active.progress.total || 0)}` + : ''; elements.taskQueueMeta.textContent = [ + active?.paused ? '已暂停' : '', + progressText, + active?.progress?.message || '', active.startedAt ? `开始 ${formatTime(active.startedAt)}` : '', queued.length > 0 ? `后续 ${queued.map((item) => item.title || '任务').slice(0, 2).join('、')}${queued.length > 2 ? '...' : ''}` : '队列无等待任务', ].filter(Boolean).join(' · '); diff --git a/Plugin/vendor/md5.min.js b/Plugin/vendor/md5.min.js new file mode 100644 index 0000000..76bcc0c --- /dev/null +++ b/Plugin/vendor/md5.min.js @@ -0,0 +1,10 @@ +/** + * [js-md5]{@link https://github.com/emn178/js-md5} + * + * @namespace md5 + * @version 0.8.0 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2023 + * @license MIT + */ +!function(){"use strict";function t(t){if(t)b[0]=b[16]=b[1]=b[2]=b[3]=b[4]=b[5]=b[6]=b[7]=b[8]=b[9]=b[10]=b[11]=b[12]=b[13]=b[14]=b[15]=0,this.blocks=b,this.buffer8=a;else if(u){var r=new ArrayBuffer(68);this.buffer8=new Uint8Array(r),this.blocks=new Uint32Array(r)}else this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];this.h0=this.h1=this.h2=this.h3=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}function r(r,e){var i,s=_(r);if(r=s[0],s[1]){var h,n=[],a=r.length,o=0;for(i=0;i>>6,n[o++]=128|63&h):h<55296||h>=57344?(n[o++]=224|h>>>12,n[o++]=128|h>>>6&63,n[o++]=128|63&h):(h=65536+((1023&h)<<10|1023&r.charCodeAt(++i)),n[o++]=240|h>>>18,n[o++]=128|h>>>12&63,n[o++]=128|h>>>6&63,n[o++]=128|63&h);r=n}r.length>64&&(r=new t(!0).update(r).array());var f=[],u=[];for(i=0;i<64;++i){var c=r[i]||0;f[i]=92^c,u[i]=54^c}t.call(this,e),this.update(u),this.oKeyPad=f,this.inner=!0,this.sharedMemory=e}var e="input is invalid type",i="object"==typeof window,s=i?window:{};s.JS_MD5_NO_WINDOW&&(i=!1);var h=!i&&"object"==typeof self,n=!s.JS_MD5_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node;n?s=global:h&&(s=self);var a,o=!s.JS_MD5_NO_COMMON_JS&&"object"==typeof module&&module.exports,f="function"==typeof define&&define.amd,u=!s.JS_MD5_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,c="0123456789abcdef".split(""),y=[128,32768,8388608,-2147483648],p=[0,8,16,24],d=["hex","array","digest","buffer","arrayBuffer","base64"],l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),b=[];if(u){var v=new ArrayBuffer(68);a=new Uint8Array(v),b=new Uint32Array(v)}var w=Array.isArray;!s.JS_MD5_NO_NODE_JS&&w||(w=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var A=ArrayBuffer.isView;!u||!s.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW&&A||(A=function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer});var _=function(t){var r=typeof t;if("string"===r)return[t,!0];if("object"!==r||null===t)throw new Error(e);if(u&&t.constructor===ArrayBuffer)return[new Uint8Array(t),!1];if(!w(t)&&!A(t))throw new Error(e);return[t,!1]},B=function(r){return function(e){return new t(!0).update(e)[r]()}},g=function(t){var r,i=require("crypto"),h=require("buffer").Buffer;r=h.from&&!s.JS_MD5_NO_BUFFER_FROM?h.from:function(t){return new h(t)};return function(s){if("string"==typeof s)return i.createHash("md5").update(s,"utf8").digest("hex");if(null===s||void 0===s)throw new Error(e);return s.constructor===ArrayBuffer&&(s=new Uint8Array(s)),w(s)||A(s)||s.constructor===h?i.createHash("md5").update(r(s)).digest("hex"):t(s)}},m=function(t){return function(e,i){return new r(e,!0).update(i)[t]()}};t.prototype.update=function(t){if(this.finalized)throw new Error("finalize already called");var r=_(t);t=r[0];for(var e,i,s=r[1],h=0,n=t.length,a=this.blocks,o=this.buffer8;h>>6,o[i++]=128|63&e):e<55296||e>=57344?(o[i++]=224|e>>>12,o[i++]=128|e>>>6&63,o[i++]=128|63&e):(e=65536+((1023&e)<<10|1023&t.charCodeAt(++h)),o[i++]=240|e>>>18,o[i++]=128|e>>>12&63,o[i++]=128|e>>>6&63,o[i++]=128|63&e);else for(i=this.start;h>>2]|=e<>>2]|=(192|e>>>6)<>>2]|=(128|63&e)<=57344?(a[i>>>2]|=(224|e>>>12)<>>2]|=(128|e>>>6&63)<>>2]|=(128|63&e)<>>2]|=(240|e>>>18)<>>2]|=(128|e>>>12&63)<>>2]|=(128|e>>>6&63)<>>2]|=(128|63&e)<>>2]|=t[h]<=64?(this.start=i-64,this.hash(),this.hashed=!0):this.start=i}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,r=this.lastByteIndex;t[r>>>2]|=y[3&r],r>=56&&(this.hashed||this.hash(),t[0]=t[16],t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.bytes<<3,t[15]=this.hBytes<<3|this.bytes>>>29,this.hash()}},t.prototype.hash=function(){var t,r,e,i,s,h,n=this.blocks;this.first?r=((r=((t=((t=n[0]-680876937)<<7|t>>>25)-271733879<<0)^(e=((e=(-271733879^(i=((i=(-1732584194^2004318071&t)+n[1]-117830708)<<12|i>>>20)+t<<0)&(-271733879^t))+n[2]-1126478375)<<17|e>>>15)+i<<0)&(i^t))+n[3]-1316259209)<<22|r>>>10)+e<<0:(t=this.h0,r=this.h1,e=this.h2,r=((r+=((t=((t+=((i=this.h3)^r&(e^i))+n[0]-680876936)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+n[1]-389564586)<<12|i>>>20)+t<<0)&(t^r))+n[2]+606105819)<<17|e>>>15)+i<<0)&(i^t))+n[3]-1044525330)<<22|r>>>10)+e<<0),r=((r+=((t=((t+=(i^r&(e^i))+n[4]-176418897)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+n[5]+1200080426)<<12|i>>>20)+t<<0)&(t^r))+n[6]-1473231341)<<17|e>>>15)+i<<0)&(i^t))+n[7]-45705983)<<22|r>>>10)+e<<0,r=((r+=((t=((t+=(i^r&(e^i))+n[8]+1770035416)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+n[9]-1958414417)<<12|i>>>20)+t<<0)&(t^r))+n[10]-42063)<<17|e>>>15)+i<<0)&(i^t))+n[11]-1990404162)<<22|r>>>10)+e<<0,r=((r+=((t=((t+=(i^r&(e^i))+n[12]+1804603682)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+n[13]-40341101)<<12|i>>>20)+t<<0)&(t^r))+n[14]-1502002290)<<17|e>>>15)+i<<0)&(i^t))+n[15]+1236535329)<<22|r>>>10)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+n[1]-165796510)<<5|t>>>27)+r<<0)^r))+n[6]-1069501632)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+n[11]+643717713)<<14|e>>>18)+i<<0)^i))+n[0]-373897302)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+n[5]-701558691)<<5|t>>>27)+r<<0)^r))+n[10]+38016083)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+n[15]-660478335)<<14|e>>>18)+i<<0)^i))+n[4]-405537848)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+n[9]+568446438)<<5|t>>>27)+r<<0)^r))+n[14]-1019803690)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+n[3]-187363961)<<14|e>>>18)+i<<0)^i))+n[8]+1163531501)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+n[13]-1444681467)<<5|t>>>27)+r<<0)^r))+n[2]-51403784)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+n[7]+1735328473)<<14|e>>>18)+i<<0)^i))+n[12]-1926607734)<<20|r>>>12)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+n[5]-378558)<<4|t>>>28)+r<<0))+n[8]-2022574463)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+n[11]+1839030562)<<16|e>>>16)+i<<0))+n[14]-35309556)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+n[1]-1530992060)<<4|t>>>28)+r<<0))+n[4]+1272893353)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+n[7]-155497632)<<16|e>>>16)+i<<0))+n[10]-1094730640)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+n[13]+681279174)<<4|t>>>28)+r<<0))+n[0]-358537222)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+n[3]-722521979)<<16|e>>>16)+i<<0))+n[6]+76029189)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+n[9]-640364487)<<4|t>>>28)+r<<0))+n[12]-421815835)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+n[15]+530742520)<<16|e>>>16)+i<<0))+n[2]-995338651)<<23|r>>>9)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+n[0]-198630844)<<6|t>>>26)+r<<0)|~e))+n[7]+1126891415)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+n[14]-1416354905)<<15|e>>>17)+i<<0)|~t))+n[5]-57434055)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+n[12]+1700485571)<<6|t>>>26)+r<<0)|~e))+n[3]-1894986606)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+n[10]-1051523)<<15|e>>>17)+i<<0)|~t))+n[1]-2054922799)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+n[8]+1873313359)<<6|t>>>26)+r<<0)|~e))+n[15]-30611744)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+n[6]-1560198380)<<15|e>>>17)+i<<0)|~t))+n[13]+1309151649)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+n[4]-145523070)<<6|t>>>26)+r<<0)|~e))+n[11]-1120210379)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+n[2]+718787259)<<15|e>>>17)+i<<0)|~t))+n[9]-343485551)<<21|r>>>11)+e<<0,this.first?(this.h0=t+1732584193<<0,this.h1=r-271733879<<0,this.h2=e-1732584194<<0,this.h3=i+271733878<<0,this.first=!1):(this.h0=this.h0+t<<0,this.h1=this.h1+r<<0,this.h2=this.h2+e<<0,this.h3=this.h3+i<<0)},t.prototype.hex=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,i=this.h3;return c[t>>>4&15]+c[15&t]+c[t>>>12&15]+c[t>>>8&15]+c[t>>>20&15]+c[t>>>16&15]+c[t>>>28&15]+c[t>>>24&15]+c[r>>>4&15]+c[15&r]+c[r>>>12&15]+c[r>>>8&15]+c[r>>>20&15]+c[r>>>16&15]+c[r>>>28&15]+c[r>>>24&15]+c[e>>>4&15]+c[15&e]+c[e>>>12&15]+c[e>>>8&15]+c[e>>>20&15]+c[e>>>16&15]+c[e>>>28&15]+c[e>>>24&15]+c[i>>>4&15]+c[15&i]+c[i>>>12&15]+c[i>>>8&15]+c[i>>>20&15]+c[i>>>16&15]+c[i>>>28&15]+c[i>>>24&15]},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,i=this.h3;return[255&t,t>>>8&255,t>>>16&255,t>>>24&255,255&r,r>>>8&255,r>>>16&255,r>>>24&255,255&e,e>>>8&255,e>>>16&255,e>>>24&255,255&i,i>>>8&255,i>>>16&255,i>>>24&255]},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(16),r=new Uint32Array(t);return r[0]=this.h0,r[1]=this.h1,r[2]=this.h2,r[3]=this.h3,t},t.prototype.buffer=t.prototype.arrayBuffer,t.prototype.base64=function(){for(var t,r,e,i="",s=this.array(),h=0;h<15;)t=s[h++],r=s[h++],e=s[h++],i+=l[t>>>2]+l[63&(t<<4|r>>>4)]+l[63&(r<<2|e>>>6)]+l[63&e];return t=s[h],i+=l[t>>>2]+l[t<<4&63]+"=="},(r.prototype=new t).finalize=function(){if(t.prototype.finalize.call(this),this.inner){this.inner=!1;var r=this.array();t.call(this,this.sharedMemory),this.update(this.oKeyPad),this.update(r),t.prototype.finalize.call(this)}};var O=function(){var r=B("hex");n&&(r=g(r)),r.create=function(){return new t},r.update=function(t){return r.create().update(t)};for(var e=0;e