feat: add scrape screenshot support
This commit is contained in:
@@ -16,10 +16,10 @@ import {
|
||||
CardContent,
|
||||
Tabs,
|
||||
Tab,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
FormControl,
|
||||
FormLabel
|
||||
Select,
|
||||
MenuItem,
|
||||
InputLabel
|
||||
} from '@mui/material';
|
||||
import { ArrowBack, PlayCircleOutline, Article, Code, Description } from '@mui/icons-material';
|
||||
import { useGlobalInfoStore } from '../../../context/globalInfo';
|
||||
@@ -376,7 +376,7 @@ const RobotCreate: React.FC = () => {
|
||||
/>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" mb={3}>
|
||||
Turn websites into LLM-ready Markdown & clean HTML for AI apps.
|
||||
Turn websites into LLM-ready Markdown, clean HTML, or screenshots for AI apps.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ width: '100%', maxWidth: 700, mb: 2 }}>
|
||||
@@ -399,40 +399,52 @@ const RobotCreate: React.FC = () => {
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<FormControl component="fieldset" sx={{ width: '100%', textAlign: 'left' }}>
|
||||
<p>Output Format (Select at least one)</p>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={outputFormats.includes('markdown')}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setOutputFormats([...outputFormats, 'markdown']);
|
||||
} else {
|
||||
setOutputFormats(outputFormats.filter(f => f !== 'markdown'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Markdown"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={outputFormats.includes('html')}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setOutputFormats([...outputFormats, 'html']);
|
||||
} else {
|
||||
setOutputFormats(outputFormats.filter(f => f !== 'html'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="HTML"
|
||||
/>
|
||||
</FormControl>
|
||||
<Box sx={{ width: '100%', display: 'flex', justifyContent: 'flex-start' }}>
|
||||
<FormControl sx={{ mb: 2, width: '300px' }}>
|
||||
<InputLabel id="output-formats-label">Output Formats *</InputLabel>
|
||||
<Select
|
||||
labelId="output-formats-label"
|
||||
id="output-formats"
|
||||
multiple
|
||||
value={outputFormats}
|
||||
label="Output Formats *"
|
||||
onChange={(e) => {
|
||||
const value = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value;
|
||||
setOutputFormats(value);
|
||||
}}
|
||||
renderValue={(selected) => {
|
||||
if (selected.length === 0) {
|
||||
return <em style={{ color: '#999' }}>Select formats</em>;
|
||||
}
|
||||
return `${selected.length} format${selected.length > 1 ? 's' : ''} selected`;
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: 300,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem value="markdown">
|
||||
<Checkbox checked={outputFormats.includes('markdown')} />
|
||||
Markdown
|
||||
</MenuItem>
|
||||
<MenuItem value="html">
|
||||
<Checkbox checked={outputFormats.includes('html')} />
|
||||
HTML
|
||||
</MenuItem>
|
||||
<MenuItem value="screenshot-visible">
|
||||
<Checkbox checked={outputFormats.includes('screenshot-visible')} />
|
||||
Screenshot - Visible Viewport
|
||||
</MenuItem>
|
||||
<MenuItem value="screenshot-fullpage">
|
||||
<Checkbox checked={outputFormats.includes('screenshot-fullpage')} />
|
||||
Screenshot - Full Page
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
@@ -461,7 +473,7 @@ const RobotCreate: React.FC = () => {
|
||||
notify('success', `${scrapeRobotName} created successfully!`);
|
||||
navigate('/robots');
|
||||
} else {
|
||||
notify('error', 'Failed to create markdown robot');
|
||||
notify('error', 'Failed to create scrape robot');
|
||||
}
|
||||
}}
|
||||
disabled={!url.trim() || !scrapeRobotName.trim() || outputFormats.length === 0 || isLoading}
|
||||
|
||||
@@ -26,7 +26,7 @@ interface RobotMeta {
|
||||
params: any[];
|
||||
type?: 'extract' | 'scrape';
|
||||
url?: string;
|
||||
formats?: ('markdown' | 'html')[];
|
||||
formats?: ('markdown' | 'html' | 'screenshot-visible' | 'screenshot-fullpage')[];
|
||||
}
|
||||
|
||||
interface RobotWorkflow {
|
||||
|
||||
@@ -26,7 +26,7 @@ interface RobotMeta {
|
||||
params: any[];
|
||||
type?: 'extract' | 'scrape';
|
||||
url?: string;
|
||||
formats?: ('markdown' | 'html')[];
|
||||
formats?: ('markdown' | 'html' | 'screenshot-visible' | 'screenshot-fullpage')[];
|
||||
}
|
||||
|
||||
interface RobotWorkflow {
|
||||
|
||||
@@ -18,7 +18,7 @@ interface RobotMeta {
|
||||
params: any[];
|
||||
type?: 'extract' | 'scrape';
|
||||
url?: string;
|
||||
formats?: ('markdown' | 'html')[];
|
||||
formats?: ('markdown' | 'html' | 'screenshot-visible' | 'screenshot-fullpage')[];
|
||||
}
|
||||
|
||||
interface RobotWorkflow {
|
||||
|
||||
@@ -135,16 +135,19 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
const rawKeys = Object.keys(row.binaryOutput);
|
||||
|
||||
const isLegacyPattern = rawKeys.every(key => /^item-\d+-\d+$/.test(key));
|
||||
|
||||
|
||||
let normalizedScreenshotKeys: string[];
|
||||
|
||||
if (isLegacyPattern) {
|
||||
// Legacy unnamed screenshots → Screenshot 1, Screenshot 2...
|
||||
normalizedScreenshotKeys = rawKeys.map((_, index) => `Screenshot ${index + 1}`);
|
||||
} else {
|
||||
// Same rule as captured lists: if name missing or generic, auto-label
|
||||
normalizedScreenshotKeys = rawKeys.map((key, index) => {
|
||||
if (!key || key.toLowerCase().includes("screenshot")) {
|
||||
if (key === 'screenshot-visible') {
|
||||
return 'Screenshot (Visible)';
|
||||
} else if (key === 'screenshot-fullpage') {
|
||||
return 'Screenshot (Full Page)';
|
||||
} else if (!key || key.toLowerCase().includes("screenshot")) {
|
||||
return `Screenshot ${index + 1}`;
|
||||
}
|
||||
return key;
|
||||
@@ -739,6 +742,67 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{hasScreenshots && (
|
||||
<Accordion defaultExpanded sx={{ mb: 2 }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography variant='h6'>
|
||||
{t('run_content.captured_screenshot.title', 'Captured Screenshots')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{screenshotKeys.length > 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
{screenshotKeys.map((key, idx) => (
|
||||
<Box
|
||||
key={key}
|
||||
onClick={() => setCurrentScreenshotIndex(idx)}
|
||||
sx={{
|
||||
px: 3,
|
||||
py: 1,
|
||||
cursor: 'pointer',
|
||||
backgroundColor:
|
||||
currentScreenshotIndex === idx
|
||||
? (theme) => theme.palette.mode === 'dark'
|
||||
? '#121111ff'
|
||||
: '#e9ecef'
|
||||
: 'transparent',
|
||||
borderBottom: currentScreenshotIndex === idx ? '3px solid #FF00C3' : 'none',
|
||||
color: (theme) => theme.palette.mode === 'dark' ? '#fff' : '#000',
|
||||
}}
|
||||
>
|
||||
{key}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 1 }}>
|
||||
{screenshotKeys.length > 0 && (
|
||||
<img
|
||||
src={row.binaryOutput[screenshotKeyMap[screenshotKeys[currentScreenshotIndex]]]}
|
||||
alt={`Screenshot ${screenshotKeys[currentScreenshotIndex]}`}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Extract robot output
|
||||
|
||||
@@ -29,7 +29,7 @@ interface RobotMeta {
|
||||
params: any[];
|
||||
type?: 'extract' | 'scrape';
|
||||
url?: string;
|
||||
formats?: ('markdown' | 'html')[];
|
||||
formats?: ('markdown' | 'html' | 'screenshot-visible' | 'screenshot-fullpage')[];
|
||||
}
|
||||
|
||||
interface RobotWorkflow {
|
||||
|
||||
Reference in New Issue
Block a user