import type { ComponentType, ReactNode, KeyboardEvent } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Database, Globe, Image as ImageIcon, KeyRound, MessageSquare, Play, Square, X } from 'lucide-react' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Input } from '@/components/ui/input' import { Checkbox } from '@/components/ui/checkbox' import { Button } from '@/components/ui/button' import { useCrawlerStore } from '@/store/crawlerStore' import { usePlatforms, useConfigOptions, useStartCrawler, useStopCrawler } from '@/hooks/useCrawler' import { ParsedIdList } from './ParsedIdList' type SectionProps = { title: string description: string icon: ComponentType<{ className?: string }> children: ReactNode className?: string } function Section({ title, description, icon: Icon, children, className = '' }: SectionProps) { return (
{title}
{description}
{children}
) } type FieldProps = { label: string hint?: string children: ReactNode } function Field({ label, hint, children }: FieldProps) { return (
{hint ? (

{hint}

) : null}
{children}
) } type KeywordInputProps = { value: string onChange: (value: string) => void placeholder?: string disabled?: boolean } function KeywordInput({ value, onChange, placeholder, disabled }: KeywordInputProps) { const [inputValue, setInputValue] = useState('') // 将逗号分隔的字符串转换为数组 const keywords = value ? value.split(',').map((k) => k.trim()).filter(Boolean) : [] const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault() const trimmed = inputValue.trim() if (trimmed && !keywords.includes(trimmed)) { const newKeywords = [...keywords, trimmed] onChange(newKeywords.join(',')) setInputValue('') } } } const removeKeyword = (keywordToRemove: string) => { const newKeywords = keywords.filter((k) => k !== keywordToRemove) onChange(newKeywords.join(',')) } return (
setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder={placeholder} disabled={disabled} className="h-9 text-xs" /> {keywords.length > 0 && (
{keywords.map((keyword) => ( {keyword} {!disabled && ( )} ))}
)}
) } export function CrawlerConfigPanel() { const { t } = useTranslation('config') const config = useCrawlerStore((state) => state.config) const updateConfig = useCrawlerStore((state) => state.updateConfig) const status = useCrawlerStore((state) => state.status) const { data: platforms } = usePlatforms() const { data: options } = useConfigOptions() const { mutate: startCrawler, isPending: isStarting } = useStartCrawler() const { mutate: stopCrawler, isPending: isStopping } = useStopCrawler() const isDisabled = status === 'running' || status === 'stopping' const isRunning = status === 'running' const isBusy = isStarting || isStopping || status === 'stopping' const handleStart = () => { startCrawler(config) } const handleStop = () => { stopCrawler() } return (
{/* Row 1: Three Config Columns */}
{/* Column 1: Target & Mode Section */}
updateConfig({ start_page: parseInt(e.target.value) || 1 })} disabled={isDisabled} className="h-9 text-xs" />
{/* 根据爬虫类型显示不同的输入框 */} {config.crawler_type === 'search' && ( updateConfig({ keywords })} disabled={isDisabled} /> )} {config.crawler_type === 'detail' && (