import type { ReactNode } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import type { AutomationScript, ScriptRecordingResult, ScriptSaveInput } from '../../../lib/script-types'; import { CheckIcon, CloseIcon, PlayIcon, StopIcon, } from './icons'; type ScriptDraft = { name: string; description: string; channel: string; code: string; enabled: boolean; }; type CreateScriptDialogProps = { open: boolean; saving: boolean; onClose: () => void; onSubmit: (input: ScriptSaveInput) => Promise; }; type EditScriptDialogProps = { open: boolean; script: AutomationScript | null; saving: boolean; recordingStatus: 'idle' | 'recording' | 'stopped'; onClose: () => void; onSubmit: (input: ScriptSaveInput) => Promise; onStartRecording: (url: string) => Promise; onStopRecording: () => Promise; onFeedback: (message: string, tone?: 'info' | 'success' | 'error') => void; }; type ConfirmDeleteDialogProps = { open: boolean; scriptName?: string; deleting: boolean; onClose: () => void; onConfirm: () => Promise; }; const CHANNEL_URLS = [ 'https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1', 'https://me.meituan.com/ebooking/merchant/product#/index', 'https://life.douyin.com/p/travel-ari/hotel/price_amount_state?groupid=1816249020842116', ]; const DEFAULT_SCRIPT_TEMPLATE = `import { chromium } from 'playwright'; import { preparePage, safeDisconnectBrowser } from './common/tabs.js'; (async () => { const browser = await chromium.connectOverCDP('http://127.0.0.1:9222'); const { page } = await preparePage(browser, { targetUrl: 'about:blank', }); // Your automation code here await safeDisconnectBrowser(browser); process.exit(0); })(); `; function buildDefaultScript(channel: string) { return DEFAULT_SCRIPT_TEMPLATE.replace('about:blank', channel.trim() || 'about:blank'); } function ModalFrame({ open, title, subtitle, widthClassName, children, onClose, }: { open: boolean; title: string; subtitle: string; widthClassName: string; children: ReactNode; onClose: () => void; }) { useEffect(() => { if (!open) return undefined; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [open, onClose]); if (!open) return null; return (
event.stopPropagation()} >

{title}

{subtitle}

{children}
); } function FieldLabel({ children }: { children: ReactNode }) { return ( ); } function baseFieldClassName(multiline = false) { return [ 'w-full rounded-[14px] border border-transparent bg-[#eceae1] px-4 text-[13px] text-[#171717] outline-none transition-colors', 'placeholder:text-[#171717]/45 focus:border-[#2B7FFF]/50 dark:bg-[#222225] dark:text-[#f3f4f6] dark:placeholder:text-gray-500', multiline ? 'min-h-[108px] py-3 leading-6' : 'h-[46px]', ].join(' '); } function Switch({ checked, onChange, disabled, }: { checked: boolean; onChange: (value: boolean) => void; disabled?: boolean; }) { return ( ); } export function CreateScriptDialog({ open, saving, onClose, onSubmit, }: CreateScriptDialogProps) { const [draft, setDraft] = useState({ name: '', description: '', channel: '', code: '', enabled: true, }); useEffect(() => { if (!open) { setDraft({ name: '', description: '', channel: '', code: '', enabled: true, }); } }, [open]); async function handleSubmit() { if (!draft.name.trim()) return; await onSubmit({ name: draft.name.trim(), description: draft.description.trim(), channel: draft.channel.trim(), code: buildDefaultScript(draft.channel), enabled: true, }); } return (
脚本名称 setDraft((current) => ({ ...current, name: event.target.value }))} placeholder="例如:飞猪房态采集" className={baseFieldClassName()} />
脚本描述