Files
store_ai_extension/src/popup/hook/use-scan.ts
2026-05-11 17:24:53 +08:00

171 lines
4.6 KiB
TypeScript

import { onMounted, onUnmounted, ref } from 'vue';
import { platformConfigs } from '@/config/platforms';
import type { CrawlTaskState } from '@/types';
import { sendBackgroundMessage } from '@/shared/message';
const CRAWL_TASK_STORAGE_KEY = 'crawlTaskState';
const ACTIVE_STATUSES = new Set(['running', 'paused']);
export const useScan = () => {
const selectedPlatformId = ref(platformConfigs[0]?.id ?? '');
const isScanning = ref<boolean>(false);
const crawlState = ref<CrawlTaskState | null>(null);
const elapsedSeconds = ref<number>(0);
let timer: number | undefined;
const handleScan = async () => {
if (isScanning.value) {
return;
}
isScanning.value = true;
try {
ensureElapsedTimer();
const response = await sendBackgroundMessage<CrawlTaskState>({
action: 'START_CRAWL',
payload: { platformId: selectedPlatformId.value },
});
if (response.ok) {
syncCrawlState(response.data ?? null);
} else {
console.error('[crawl] start failed', response.error);
}
} finally {
isScanning.value = false;
}
};
const handleCancelCrawl = async () => {
const response = await sendBackgroundMessage<CrawlTaskState | null>({ action: 'CANCEL_CRAWL' });
if (response.ok) {
syncCrawlState(response.data ?? null);
return;
}
console.error('[crawl] cancel failed', response.error);
await refreshCrawlState();
};
const handleResumeCrawl = async () => {
const response = await sendBackgroundMessage<CrawlTaskState | null>({ action: 'RESUME_CRAWL' });
if (response.ok) {
syncCrawlState(response.data ?? null);
return;
}
console.error('[crawl] resume failed', response.error);
await refreshCrawlState();
};
const handleDismissCrawl = async () => {
const response = await sendBackgroundMessage<CrawlTaskState | null>({ action: 'DISMISS_CRAWL' });
if (response.ok) {
syncCrawlState(response.data ?? null);
return;
}
console.error('[crawl] dismiss failed', response.error);
await refreshCrawlState();
};
function syncCrawlState(state: CrawlTaskState | null) {
crawlState.value = state;
updateSeconds();
if (state && ACTIVE_STATUSES.has(state.status)) {
ensureElapsedTimer();
return;
}
clearElapsedTimer();
}
function ensureElapsedTimer() {
if (timer !== undefined) {
return;
}
timer = window.setInterval(() => {
updateSeconds();
}, 1000);
}
function clearElapsedTimer() {
if (timer === undefined) {
return;
}
window.clearInterval(timer);
timer = undefined;
}
function updateSeconds() {
if (!crawlState.value) {
elapsedSeconds.value = 0;
return;
}
elapsedSeconds.value = Math.max(0, Math.floor((Date.now() - crawlState.value.startedAt) / 1000));
}
async function refreshCrawlState() {
const response = await sendBackgroundMessage<CrawlTaskState | null>({ action: 'GET_CRAWL_STATE' });
if (response.ok) {
syncCrawlState(response.data ?? null);
}
}
function handleStorageChanged(changes: Record<string, chrome.storage.StorageChange>, areaName: string) {
if (areaName !== 'local') {
return;
}
const change = changes[CRAWL_TASK_STORAGE_KEY];
if (!change) {
return;
}
syncCrawlState(isCrawlTaskState(change.newValue) ? change.newValue : null);
}
onMounted(async () => {
await refreshCrawlState();
if (typeof chrome !== 'undefined' && chrome.storage?.onChanged) {
chrome.storage.onChanged.addListener(handleStorageChanged);
}
});
onUnmounted(() => {
clearElapsedTimer();
if (typeof chrome !== 'undefined' && chrome.storage?.onChanged) {
chrome.storage.onChanged.removeListener(handleStorageChanged);
}
});
return {
selectedPlatformId,
isScanning,
crawlState,
handleScan,
handleCancelCrawl,
handleResumeCrawl,
handleDismissCrawl,
elapsedSeconds,
};
};
function isCrawlTaskState(value: unknown): value is CrawlTaskState {
return typeof value === 'object' && value !== null && 'id' in value && 'steps' in value;
}