feat: add auto robots template
This commit is contained in:
442
src/components/robot/AutoRobotDetailModal.tsx
Normal file
442
src/components/robot/AutoRobotDetailModal.tsx
Normal file
@@ -0,0 +1,442 @@
|
||||
import React, { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Divider,
|
||||
Chip,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from '@mui/material';
|
||||
import { GenericModal } from '../ui/GenericModal';
|
||||
|
||||
interface AutoRobotData {
|
||||
id: string;
|
||||
name: string;
|
||||
category?: string;
|
||||
description?: string;
|
||||
access?: string;
|
||||
sample?: any[];
|
||||
logo?: string;
|
||||
configOptions?: {
|
||||
parameters?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
type: 'dropdown' | 'search' | 'url' | 'limit' | 'username' | 'path-segment';
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
queryParam: string;
|
||||
options?: Array<{ value: string; label: string; }>;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
interface AutoRobotDetailModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
robot: AutoRobotData | null;
|
||||
onUseRobot: (robot: AutoRobotData, config?: { parameters?: { [key: string]: string } }) => void;
|
||||
}
|
||||
|
||||
export const AutoRobotDetailModal: FC<AutoRobotDetailModalProps> = ({ open, onClose, robot, onUseRobot }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!robot) return null;
|
||||
|
||||
const sampleData = robot.sample || [];
|
||||
|
||||
const columnHeaders = sampleData.length > 0
|
||||
? Object.keys(sampleData[0])
|
||||
: [];
|
||||
|
||||
const needsConfiguration = robot.configOptions?.parameters && robot.configOptions.parameters.length > 0;
|
||||
const parameters = robot.configOptions?.parameters || [];
|
||||
|
||||
const handleUseRobot = () => {
|
||||
onUseRobot(robot);
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericModal
|
||||
isOpen={open}
|
||||
onClose={onClose}
|
||||
modalStyle={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '800px',
|
||||
maxWidth: '90vw',
|
||||
height: 'auto',
|
||||
maxHeight: '85vh',
|
||||
padding: '28px 32px',
|
||||
overflow: 'auto',
|
||||
backgroundColor: 'background.paper',
|
||||
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
|
||||
borderRadius: '12px',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: 'rgba(128, 128, 128, 0.3) transparent'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, mb: 3 }}>
|
||||
{robot.logo && (
|
||||
<Box
|
||||
component="img"
|
||||
src={robot.logo}
|
||||
alt={`${robot.name} logo`}
|
||||
sx={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
objectFit: 'contain',
|
||||
flexShrink: 0,
|
||||
mt: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: 'text.primary',
|
||||
fontSize: '1.75rem',
|
||||
lineHeight: 1.2,
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
{robot.name}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={robot.category || t('recordingtable.no_category', 'Uncategorized')}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
height: '28px',
|
||||
fontSize: '0.8rem',
|
||||
borderRadius: '14px',
|
||||
fontWeight: 500
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mb: 4,
|
||||
backgroundColor: 'action.hover',
|
||||
borderRadius: '8px',
|
||||
p: 3
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
mb: 1,
|
||||
fontWeight: 600,
|
||||
color: 'text.primary',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{t('robot.description', 'Description')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
color: 'text.secondary',
|
||||
lineHeight: 1.6
|
||||
}}
|
||||
>
|
||||
{robot.description || t('recordingtable.no_description', 'No description available')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{
|
||||
my: 3,
|
||||
borderColor: 'divider'
|
||||
}} />
|
||||
|
||||
{needsConfiguration && (
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
mb: 2,
|
||||
fontWeight: 600,
|
||||
color: 'text.primary'
|
||||
}}
|
||||
>
|
||||
{t('robot.config.configuration', 'Configuration')}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
mb: 2,
|
||||
color: 'text.secondary',
|
||||
fontStyle: 'italic'
|
||||
}}
|
||||
>
|
||||
{t('robot.config.info_message', 'The following fields will be required when you use this robot:')}
|
||||
</Typography>
|
||||
|
||||
<Stack spacing={3}>
|
||||
{parameters.map((param) => {
|
||||
if (param.type === 'dropdown') {
|
||||
return (
|
||||
<FormControl
|
||||
key={param.id}
|
||||
fullWidth
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<InputLabel>{param.label}{param.required ? ' *' : ''}</InputLabel>
|
||||
<Select
|
||||
value=""
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
readOnly
|
||||
>
|
||||
{param.options?.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === 'search') {
|
||||
return (
|
||||
<TextField
|
||||
key={param.id}
|
||||
fullWidth
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
placeholder={param.placeholder}
|
||||
value=""
|
||||
InputProps={{ readOnly: true }}
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === 'url') {
|
||||
return (
|
||||
<TextField
|
||||
key={param.id}
|
||||
fullWidth
|
||||
type="url"
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
placeholder={param.placeholder || 'https://example.com'}
|
||||
value=""
|
||||
InputProps={{ readOnly: true }}
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === 'limit') {
|
||||
return (
|
||||
<TextField
|
||||
key={param.id}
|
||||
fullWidth
|
||||
type="number"
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
placeholder={param.placeholder || '100'}
|
||||
value=""
|
||||
InputProps={{ readOnly: true }}
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === 'username') {
|
||||
return (
|
||||
<TextField
|
||||
key={param.id}
|
||||
fullWidth
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
placeholder={param.placeholder || 'Enter username'}
|
||||
value=""
|
||||
InputProps={{ readOnly: true }}
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === 'path-segment') {
|
||||
return (
|
||||
<TextField
|
||||
key={param.id}
|
||||
fullWidth
|
||||
label={`${param.label}${param.required ? ' *' : ''}`}
|
||||
placeholder={param.placeholder || 'Enter path segment value'}
|
||||
value=""
|
||||
InputProps={{ readOnly: true }}
|
||||
sx={{
|
||||
opacity: 0.7,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{needsConfiguration && (
|
||||
<Divider sx={{
|
||||
my: 3,
|
||||
borderColor: 'divider'
|
||||
}} />
|
||||
)}
|
||||
|
||||
{sampleData.length > 0 && (
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
mb: 2,
|
||||
fontWeight: 600,
|
||||
color: 'text.primary',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{t('robot.sample_output', 'Sample Output')}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
borderRadius: '4px',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<Table size="medium" sx={{ width: '100%' }}>
|
||||
<TableHead>
|
||||
<TableRow sx={{ backgroundColor: '#242424' }}>
|
||||
{columnHeaders.map((header, index) => (
|
||||
<TableCell
|
||||
key={index}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: 'white',
|
||||
borderBottom: 'none',
|
||||
py: 2,
|
||||
px: 2,
|
||||
whiteSpace: 'normal',
|
||||
minWidth: '100px'
|
||||
}}
|
||||
>
|
||||
{header}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{sampleData.map((row, rowIndex) => (
|
||||
<TableRow
|
||||
key={rowIndex}
|
||||
sx={{
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
'&:last-child': {
|
||||
borderBottom: 'none'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{columnHeaders.map((header, cellIndex) => (
|
||||
<TableCell
|
||||
key={`${rowIndex}-${cellIndex}`}
|
||||
sx={{
|
||||
py: 2.5,
|
||||
px: 2,
|
||||
color: 'text.secondary',
|
||||
fontWeight: 'inherit',
|
||||
borderBottom: 'none',
|
||||
whiteSpace: 'normal',
|
||||
minWidth: '100px'
|
||||
}}
|
||||
>
|
||||
{row[header] !== null ? (typeof row[header] === 'object' ? JSON.stringify(row[header]) : String(row[header])) : '-'}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 2,
|
||||
mt: 5,
|
||||
mb: 1
|
||||
}}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
color: '#ff00c3 !important',
|
||||
borderColor: '#ff00c3 !important',
|
||||
backgroundColor: 'white !important',
|
||||
}} >
|
||||
{t('common.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUseRobot}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
px: 3,
|
||||
py: 1,
|
||||
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)',
|
||||
textTransform: 'none',
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{t('robot.add_to_my_robots', 'Use this robot')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</GenericModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoRobotDetailModal;
|
||||
406
src/components/robot/AutoRobots.tsx
Normal file
406
src/components/robot/AutoRobots.tsx
Normal file
@@ -0,0 +1,406 @@
|
||||
import React, { memo, useState, useMemo, useEffect, FC } from "react";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Card,
|
||||
CardContent,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
Divider,
|
||||
Chip,
|
||||
Pagination,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { Search as SearchIcon } from "@mui/icons-material";
|
||||
import { GenericModal } from "../ui/GenericModal";
|
||||
import { AutoRobotDetailModal } from "./AutoRobotDetailModal";
|
||||
import { AUTO_ROBOTS } from "../../constants/autoRobots";
|
||||
|
||||
interface Data {
|
||||
id: string;
|
||||
name: string;
|
||||
category?: string;
|
||||
description?: string;
|
||||
access?: string;
|
||||
sample?: any[];
|
||||
logo?: string;
|
||||
configOptions?: {
|
||||
parameters?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
type: 'dropdown' | 'search' | 'url' | 'limit' | 'username' | 'path-segment';
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
queryParam: string;
|
||||
options?: Array<{ value: string; label: string; }>;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
interface RecordingCardProps {
|
||||
row: Data;
|
||||
onUseRobot: (robot: Data) => void;
|
||||
}
|
||||
|
||||
const RecordingCard: FC<RecordingCardProps> = memo(({ row, onUseRobot }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isPremium = row.access === 'premium';
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
|
||||
},
|
||||
borderRadius: 2,
|
||||
overflow: 'visible',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{isPremium && (
|
||||
<Chip
|
||||
label="Premium"
|
||||
size="small"
|
||||
color="secondary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
backgroundColor: '#ff00c3',
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
height: '24px',
|
||||
fontSize: '0.7rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CardContent
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, mb: 2 }}>
|
||||
{row.logo && (
|
||||
<Box
|
||||
component="img"
|
||||
src={row.logo}
|
||||
alt={`${row.name} logo`}
|
||||
sx={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
objectFit: 'contain',
|
||||
flexShrink: 0,
|
||||
mt: 0.25,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 0.5,
|
||||
color: theme => theme.palette.text.primary,
|
||||
lineHeight: 1.2,
|
||||
height: '2.4em',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
{row.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
height: '4.5em',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
marginTop: '18px',
|
||||
fontSize: '16px',
|
||||
lineHeight: 1.5
|
||||
}}
|
||||
>
|
||||
{row.description || t('recordingtable.no_description')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => onUseRobot(row)}
|
||||
fullWidth
|
||||
sx={{
|
||||
borderRadius: 1.5,
|
||||
py: 1.2
|
||||
}}
|
||||
>
|
||||
{t('robot.use_this_robot', 'Use this robot')}
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
export const AutoRobots: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [rowsPerPage] = useState<number>(6);
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('');
|
||||
const [selectedRobot, setSelectedRobot] = useState<Data | null>(null);
|
||||
const [detailModalOpen, setDetailModalOpen] = useState<boolean>(false);
|
||||
const [cloudModalOpen, setCloudModalOpen] = useState<boolean>(false);
|
||||
|
||||
const rows = AUTO_ROBOTS;
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const uniqueCategories = [...new Set(rows
|
||||
.map((row: any) => row.category)
|
||||
.filter(Boolean)
|
||||
)];
|
||||
return uniqueCategories;
|
||||
}, [rows]);
|
||||
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
const debouncedSearchTerm = useDebounce<string>(searchTerm, 300);
|
||||
|
||||
const filteredRows = useMemo<Data[]>(() => {
|
||||
let filtered = rows;
|
||||
|
||||
if (selectedCategory) {
|
||||
filtered = filtered.filter(row => row.category === selectedCategory);
|
||||
}
|
||||
|
||||
const searchLower = debouncedSearchTerm.toLowerCase();
|
||||
if (debouncedSearchTerm) {
|
||||
filtered = filtered.filter(row => row.name.toLowerCase().includes(searchLower));
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [rows, debouncedSearchTerm, selectedCategory]);
|
||||
|
||||
const handlePageChange = (_event: React.ChangeEvent<unknown>, value: number): void => {
|
||||
setPage(value);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const paginatedRows = useMemo<Data[]>(() => {
|
||||
const startIndex = (page - 1) * rowsPerPage;
|
||||
return filteredRows.slice(startIndex, startIndex + rowsPerPage);
|
||||
}, [filteredRows, page, rowsPerPage]);
|
||||
|
||||
const totalPages = Math.ceil(filteredRows.length / rowsPerPage);
|
||||
|
||||
const handleUseRobotClick = (robot: Data): void => {
|
||||
setSelectedRobot(robot);
|
||||
setDetailModalOpen(true);
|
||||
};
|
||||
|
||||
const handleAddToMyRobots = (robot: Data, config?: { parameters?: { [key: string]: string } }): void => {
|
||||
setDetailModalOpen(false);
|
||||
setCloudModalOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "30px" }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
mb: 3
|
||||
}}>
|
||||
<Typography variant="h6">
|
||||
{t('mainmenu.prebuilt_robots', 'Auto Robots')}
|
||||
</Typography>
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder={t('recordingtable.search', 'Search')}
|
||||
value={searchTerm}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{ width: '250px' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
mb: 3,
|
||||
flexWrap: 'wrap'
|
||||
}}>
|
||||
<Chip
|
||||
label="All"
|
||||
onClick={() => setSelectedCategory('')}
|
||||
color={selectedCategory === '' ? 'primary' : 'default'}
|
||||
sx={{
|
||||
'&:hover': { backgroundColor: theme => selectedCategory === '' ? theme.palette.primary.main : theme.palette.action.hover }
|
||||
}}
|
||||
/>
|
||||
{categories.map((category) => (
|
||||
<Chip
|
||||
key={category}
|
||||
label={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
color={selectedCategory === category ? 'primary' : 'default'}
|
||||
sx={{
|
||||
'&:hover': { backgroundColor: theme => selectedCategory === category ? theme.palette.primary.main : theme.palette.action.hover }
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{paginatedRows.map((row) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={row.id}>
|
||||
<RecordingCard
|
||||
row={row}
|
||||
onUseRobot={handleUseRobotClick}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{filteredRows.length === 0 ? (
|
||||
<Box sx={{
|
||||
textAlign: 'center',
|
||||
py: 8,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 1,
|
||||
mt: 3
|
||||
}}>
|
||||
<Typography color="text.secondary">
|
||||
{searchTerm
|
||||
? t('recordingtable.no_results', 'No results found')
|
||||
: t('recordingtable.no_recordings', 'No robots available')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
mt: 4,
|
||||
mb: 2
|
||||
}}>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={page}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
size="large"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<AutoRobotDetailModal
|
||||
open={detailModalOpen}
|
||||
onClose={() => setDetailModalOpen(false)}
|
||||
robot={selectedRobot}
|
||||
onUseRobot={handleAddToMyRobots}
|
||||
/>
|
||||
|
||||
<GenericModal
|
||||
isOpen={cloudModalOpen}
|
||||
onClose={() => setCloudModalOpen(false)}
|
||||
modalStyle={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '500px',
|
||||
maxWidth: '90vw',
|
||||
padding: '32px',
|
||||
backgroundColor: 'background.paper',
|
||||
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
|
||||
borderRadius: '12px',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="h5" fontWeight={600} mb={2}>
|
||||
Available on Maxun Cloud
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" mb={3}>
|
||||
Auto Robots are available exclusively on Maxun Cloud. Sign up for free to access pre-built automation templates and start extracting data instantly.
|
||||
</Typography>
|
||||
<Box display="flex" gap={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
onClick={() => window.open('https://app.maxun.dev/prebuilt-robots', '_blank', 'noopener,noreferrer')}
|
||||
>
|
||||
Go to Maxun Cloud
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
fullWidth
|
||||
onClick={() => setCloudModalOpen(false)}
|
||||
sx={{
|
||||
color: '#ff00c3 !important',
|
||||
borderColor: '#ff00c3 !important',
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</GenericModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoRobots;
|
||||
522
src/constants/autoRobots.ts
Normal file
522
src/constants/autoRobots.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
export const AUTO_ROBOTS = [
|
||||
{
|
||||
id: "mock-robot-1",
|
||||
name: "Extract stories from Medium user profile",
|
||||
description: "Scraping Medium profiles for stories, reveals what content truly connects with readers. Example: https://thedankoe.medium.com",
|
||||
category: "News",
|
||||
logo: "https://img.logo.dev/medium.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Likes": "277",
|
||||
"Comments": "6",
|
||||
"Story Date": "Aug 26",
|
||||
"Story Title": "HUMAN 3.0 — A Map To Reach The Top 1%",
|
||||
"Story Description": "Read online free here."
|
||||
},
|
||||
{
|
||||
"Likes": "351",
|
||||
"Comments": "6",
|
||||
"Story Date": "Jul 31",
|
||||
"Story Title": "You don't need a niche, you need a point of view",
|
||||
"Story Description": "How to survive in the AI generated internet"
|
||||
},
|
||||
{
|
||||
"Likes": "412",
|
||||
"Comments": "11",
|
||||
"Story Date": "Jul 22",
|
||||
"Story Title": "You have about 36 months to make it",
|
||||
"Story Description": "why everyone is racing to get rich"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "mock-robot-2",
|
||||
name: "Extract Apps From Pipedream",
|
||||
description: "Browse available Pipedream apps to tap into automation tools.",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/pipedream.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"App": "HTTP / Webhook",
|
||||
"Description": "Get a unique URL where you can send HTTP or webhook requests"
|
||||
},
|
||||
{
|
||||
"App": "Node",
|
||||
"Description": "Anything you can do with Node.js, you can do in a Pipedream workflow. This includes using most of npm's 400,000+ packages."
|
||||
},
|
||||
{
|
||||
"App": "Python",
|
||||
"Description": "Anything you can do in Python can be done in a Pipedream Workflow. This includes using any of the 350,000+ PyPi packages."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "mock-robot-3",
|
||||
name: "Extract URLs from Sitemap URL Set",
|
||||
description: "Extract URLs from XML sitemaps—special .xml files that websites publish to list all their public pages and update frequencies. Example: https://www.nike.com/sitemap-us-help.xml",
|
||||
category: "Tech",
|
||||
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSDdTRSGj8VljQR4AOE2QewgUJvqxDkxI7GkW5CnwMxuwdgwHCpiV1whxI&s=10",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"loc": "https://www.nike.com/help/a/promo-code-terms",
|
||||
"lastmod": "2019-02-06"
|
||||
},
|
||||
{
|
||||
"loc": "https://www.nike.com/help/a/nfl-jersey",
|
||||
"lastmod": "2019-02-06"
|
||||
},
|
||||
{
|
||||
"loc": "https://www.nike.com/help/a/free-shipping",
|
||||
"lastmod": "2019-02-06"
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "website_url",
|
||||
type: "url" as const,
|
||||
label: "Website URL",
|
||||
required: true,
|
||||
queryParam: "url",
|
||||
placeholder: "https://www.nike.com/sitemap-us-help.xml"
|
||||
},
|
||||
{
|
||||
id: "result_limit",
|
||||
type: "limit" as const,
|
||||
label: "Number of entries",
|
||||
required: true,
|
||||
queryParam: "limit",
|
||||
placeholder: "100"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-4",
|
||||
name: "Extract trending repositories from Github",
|
||||
description: "Spot trending GitHub repos to follow what developers are building.",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/github.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"URL": "https://github.com/microsoft/markitdown",
|
||||
"Name": "microsoft / markitdown",
|
||||
"Forks": "2,608",
|
||||
"Stars": "52,816",
|
||||
"Language": "Python",
|
||||
"Description": "Python tool for converting files and office documents to Markdown.",
|
||||
"Stars Today": "822 stars today"
|
||||
},
|
||||
{
|
||||
"URL": "https://github.com/hydralauncher/hydra",
|
||||
"Name": "hydralauncher / hydra",
|
||||
"Forks": "3,533",
|
||||
"Stars": "12,547",
|
||||
"Language": "TypeScript",
|
||||
"Description": "Hydra is a game launcher with its own embedded bittorrent client",
|
||||
"Stars Today": "112 stars today"
|
||||
},
|
||||
{
|
||||
"URL": "https://github.com/pocketbase/pocketbase",
|
||||
"Name": "pocketbase / pocketbase",
|
||||
"Forks": "2,257",
|
||||
"Stars": "45,993",
|
||||
"Language": "Go",
|
||||
"Description": "Open Source realtime backend in 1 file",
|
||||
"Stars Today": "391 stars today"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "mock-robot-5",
|
||||
name: "Extract companies from trustpilot search results",
|
||||
description: "Search for any information regarding companies and extract data received from the search results. Example: https://www.trustpilot.com/search?query=Car%20wash%20near%20me",
|
||||
category: "Companies",
|
||||
logo: "https://img.logo.dev/trustpilot.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Name": "IMO Car Wash",
|
||||
"Site": "www.imocarwash.com",
|
||||
"Rating": "2.5",
|
||||
"Address": "35 - 37 Amersham Hill, High Wycombe, United Kingdom",
|
||||
"Total Reviews": "2,894"
|
||||
},
|
||||
{
|
||||
"Name": "Tint World",
|
||||
"Site": "www.tintworld.com",
|
||||
"Rating": "3.5",
|
||||
"Address": "United States",
|
||||
"Total Reviews": "820"
|
||||
},
|
||||
{
|
||||
"Name": "Shinearmor",
|
||||
"Site": "shinearmor.com",
|
||||
"Rating": "4.2",
|
||||
"Address": "Philips Hwy 6100, Jacksonville, United States",
|
||||
"Total Reviews": "311"
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "search_query",
|
||||
type: "search" as const,
|
||||
label: "Search Query",
|
||||
required: true,
|
||||
queryParam: "query",
|
||||
placeholder: "Enter business type or name (e.g., Car dealer near me, Pizza restaurant NYC, etc.)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-6",
|
||||
name: "Extract extension reviews from Chrome Web Store",
|
||||
description: "Gather Chrome extension reviews and ratings to analyze user feedback and popularity trends.",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/chromewebstore.google.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Date": "Jul 9, 2025",
|
||||
"Review": "Very useful and easy to use tool. Highly recommended!",
|
||||
"Reviewer Name": "Jean-Philippe Demoulin"
|
||||
},
|
||||
{
|
||||
"Date": "Jul 8, 2025",
|
||||
"Review": "Been using this tool for some time now and the new update is a game changer!",
|
||||
"Reviewer Name": "Michael Libman"
|
||||
},
|
||||
{
|
||||
"Date": "Jul 8, 2025",
|
||||
"Review": "It's a great tool and the customer service is the best!",
|
||||
"Reviewer Name": "Telma V"
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "website_url",
|
||||
type: "url" as const,
|
||||
label: "Extension reviews URL",
|
||||
required: true,
|
||||
queryParam: "url",
|
||||
placeholder: "https://chromewebstore.google.com/detail/extension-id/reviews"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-7",
|
||||
name: "Extract Newest Products From FutureTools.io Based On Category",
|
||||
description: "Explore the latest tools from FutureTools.io by category. Example: https://www.futuretools.io/?search=youtube",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/futuretools.io?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"URL": "https://www.futuretools.io/tools/virlo",
|
||||
"Name": "Virlo",
|
||||
"Upvotes": "7",
|
||||
"Category": "Social Media",
|
||||
"Description": "A platform to analyze trends and insights to optimize short-form content for viral potential on social media."
|
||||
},
|
||||
{
|
||||
"URL": "https://www.futuretools.io/tools/noteey",
|
||||
"Name": "Noteey",
|
||||
"Upvotes": "3",
|
||||
"Category": "Productivity",
|
||||
"Description": "A tool to organize, annotate, and connect visual notes offline."
|
||||
},
|
||||
{
|
||||
"URL": "https://www.futuretools.io/tools/papira",
|
||||
"Name": "Papira",
|
||||
"Upvotes": "2",
|
||||
"Category": "Copywriting",
|
||||
"Description": "A tool to create documents using personalized AI writing commands."
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "search_query",
|
||||
type: "search" as const,
|
||||
label: "Search Query",
|
||||
required: true,
|
||||
queryParam: "search",
|
||||
placeholder: "Enter tool category (e.g., youtube, music etc.)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-8",
|
||||
name: "Extract Google Trends based on region",
|
||||
description: "Extracting Google Trends by region uncovers what topics are gaining traction in specific areas.",
|
||||
category: "News",
|
||||
logo: "https://img.logo.dev/google.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Rise": "1,000%",
|
||||
"Trend": "AI developments",
|
||||
"Started": "23 hours ago",
|
||||
"Search Volume": "500K+"
|
||||
},
|
||||
{
|
||||
"Rise": "1,000%",
|
||||
"Trend": "Tech innovations",
|
||||
"Started": "19 hours ago",
|
||||
"Search Volume": "100K+"
|
||||
},
|
||||
{
|
||||
"Rise": "1,000%",
|
||||
"Trend": "Market trends",
|
||||
"Started": "24 hours ago",
|
||||
"Search Volume": "500K+"
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "geo",
|
||||
type: "dropdown" as const,
|
||||
label: "Region",
|
||||
options: [
|
||||
{ label: "India", value: "IN" },
|
||||
{ label: "United States", value: "US" },
|
||||
{ label: "United Kingdom", value: "GB" },
|
||||
{ label: "Canada", value: "CA" },
|
||||
{ label: "Australia", value: "AU" },
|
||||
{ label: "Germany", value: "DE" },
|
||||
{ label: "France", value: "FR" },
|
||||
{ label: "Japan", value: "JP" },
|
||||
{ label: "Brazil", value: "BR" },
|
||||
{ label: "Mexico", value: "MX" }
|
||||
],
|
||||
required: true,
|
||||
queryParam: "geo"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-9",
|
||||
name: "Extract HTML code and full screenshot from a webpage",
|
||||
description: "Extract HTML source code with matching visual screenshots.",
|
||||
category: "Tech",
|
||||
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSDdTRSGj8VljQR4AOE2QewgUJvqxDkxI7GkW5CnwMxuwdgwHCpiV1whxI&s=10",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Label": "html",
|
||||
"Value": "<html lang=\"en\"><head><title>Example Page</title>..."
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "website_url",
|
||||
type: "url" as const,
|
||||
label: "Website URL",
|
||||
required: true,
|
||||
queryParam: "url",
|
||||
placeholder: "https://www.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-10",
|
||||
name: "Extract jobs from Craigslist based on location and industry",
|
||||
description: "Get job listings from Craigslist filtered by region and sector.",
|
||||
category: "Jobs",
|
||||
logo: "https://img.logo.dev/craigslist.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Job": "Nurses Should Apply",
|
||||
"Location": "Sacramento",
|
||||
"Date Posted": "22/04",
|
||||
"Description": "Commission & Bonuses Only"
|
||||
},
|
||||
{
|
||||
"Job": "Entry Level Full-Time Bookkeeper Needed",
|
||||
"Location": "Arden Area",
|
||||
"Date Posted": "19/04",
|
||||
"Description": "Pay Depending on Experience"
|
||||
},
|
||||
{
|
||||
"Job": "Entry Level Full-Time Bookkeeper Needed",
|
||||
"Location": "Arden Area",
|
||||
"Date Posted": "15/04",
|
||||
"Description": "Pay Depending on Experience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "mock-robot-11",
|
||||
name: "Extract popular movies by genre from IMDb",
|
||||
description: "Find trending IMDb movies sorted by genre for entertainment insights.",
|
||||
category: "Entertainment",
|
||||
logo: "https://img.logo.dev/imdb.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Year": "2025",
|
||||
"Title": "Frankenstein",
|
||||
"Votes": "(76K)",
|
||||
"Rating": "7.6",
|
||||
"Duration": "2h 29m",
|
||||
"Description": "A brilliant but egotistical scientist brings a creature to life in a monstrous experiment."
|
||||
},
|
||||
{
|
||||
"Year": "2025",
|
||||
"Title": "Weapons",
|
||||
"Votes": "(236K)",
|
||||
"Rating": "7.5",
|
||||
"Duration": "2h 8m",
|
||||
"Description": "A community questions who is behind mysterious disappearances."
|
||||
},
|
||||
{
|
||||
"Year": "2025",
|
||||
"Title": "The Black Phone 2",
|
||||
"Votes": "(28K)",
|
||||
"Rating": "6.3",
|
||||
"Duration": "1h 54m",
|
||||
"Description": "A teen struggles with life after captivity while his sister receives disturbing calls."
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "title_type",
|
||||
type: "dropdown" as const,
|
||||
label: "Movie Type",
|
||||
options: [
|
||||
{ label: "Movie", value: "feature" },
|
||||
{ label: "TV Series", value: "tv_series" },
|
||||
{ label: "Short", value: "short" },
|
||||
{ label: "Documentary", value: "documentary" }
|
||||
],
|
||||
required: true,
|
||||
queryParam: "title_type"
|
||||
},
|
||||
{
|
||||
id: "genres",
|
||||
type: "dropdown" as const,
|
||||
label: "Genre",
|
||||
options: [
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Thriller", value: "thriller" },
|
||||
{ label: "Sci-Fi", value: "sci_fi" }
|
||||
],
|
||||
required: true,
|
||||
queryParam: "genres"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-12",
|
||||
name: "Extract complete text and full screenshot from a webpage",
|
||||
description: "Capture webpage text content alongside visual screenshots in one go.",
|
||||
category: "Tech",
|
||||
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSDdTRSGj8VljQR4AOE2QewgUJvqxDkxI7GkW5CnwMxuwdgwHCpiV1whxI&s=10",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Label": "text",
|
||||
"Value": "Welcome to the website! Discover new products and services..."
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "website_url",
|
||||
type: "url" as const,
|
||||
label: "Website URL",
|
||||
required: true,
|
||||
queryParam: "url",
|
||||
placeholder: "https://www.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mock-robot-13",
|
||||
name: "Extract YCombinator Companies",
|
||||
description: "Pull data on startups backed by Y Combinator to track innovation.",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/ycombinator.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Name": "Airbnb",
|
||||
"Location": "San Francisco, CA, USA",
|
||||
"YC Season": "W09",
|
||||
"Category": "CONSUMER",
|
||||
"Description": "Book accommodations around the world."
|
||||
},
|
||||
{
|
||||
"Name": "Amplitude",
|
||||
"Location": "San Francisco, CA, USA",
|
||||
"YC Season": "W12",
|
||||
"Category": "B2B",
|
||||
"Description": "Digital Analytics Platform"
|
||||
},
|
||||
{
|
||||
"Name": "Coinbase",
|
||||
"Location": "San Francisco, CA, USA",
|
||||
"YC Season": "S12",
|
||||
"Category": "FINTECH",
|
||||
"Description": "Buy, sell, and manage cryptocurrencies."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "mock-robot-14",
|
||||
name: "Extract products from Product Hunt based on category",
|
||||
description: "Discover top Product Hunt launches tailored to specific categories.",
|
||||
category: "Tech",
|
||||
logo: "https://img.logo.dev/producthunt.com?token=live_6a1a28fd-6420-4492-aeb0-b297461d9de2&size=100&retina=true",
|
||||
access: "free",
|
||||
sample: [
|
||||
{
|
||||
"Name": "OpenAI",
|
||||
"Description": "The most powerful platform for building AI products.",
|
||||
"Reviews": "4.6 (78 reviews)"
|
||||
},
|
||||
{
|
||||
"Name": "Claude by Anthropic",
|
||||
"Description": "AI research company building reliable AI systems.",
|
||||
"Reviews": "4.8 (117 reviews)"
|
||||
},
|
||||
{
|
||||
"Name": "ChatGPT by OpenAI",
|
||||
"Description": "Get instant answers and creative inspiration.",
|
||||
"Reviews": "4.7 (768 reviews)"
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
parameters: [
|
||||
{
|
||||
id: "category",
|
||||
type: "path-segment" as const,
|
||||
label: "Category",
|
||||
required: true,
|
||||
queryParam: "category",
|
||||
placeholder: "e.g., health-fitness"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -6,6 +6,7 @@ import { Recordings } from "../components/robot/Recordings";
|
||||
import { Runs } from "../components/run/Runs";
|
||||
import ProxyForm from '../components/proxy/ProxyForm';
|
||||
import ApiKey from '../components/api/ApiKey';
|
||||
import { AutoRobots } from '../components/robot/AutoRobots';
|
||||
import { useGlobalInfoStore, useCacheInvalidation } from "../context/globalInfo";
|
||||
import { createAndRunRecording, createRunForStoredRecording, CreateRunResponseWithQueue, interpretStoredRecording, notifyAboutAbort, scheduleStoredRecording } from "../api/storage";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
@@ -317,6 +318,8 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps)
|
||||
return <ProxyForm />;
|
||||
case 'apikey':
|
||||
return <ApiKey />;
|
||||
case 'prebuilt-robots':
|
||||
return <AutoRobots />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ export const PageWrapper = () => {
|
||||
<Route path="/" element={<Navigate to="/robots" replace />} />
|
||||
<Route path="/robots/create" element={<RobotCreate />} />
|
||||
<Route path="/robots/*" element={<MainPage handleEditRecording={handleEditRecording} initialContent="robots" />} />
|
||||
<Route path="/prebuilt-robots" element={<MainPage handleEditRecording={handleEditRecording} initialContent="prebuilt-robots" />} />
|
||||
<Route path="/runs/*" element={<MainPage handleEditRecording={handleEditRecording} initialContent="runs" />} />
|
||||
<Route path="/proxy" element={<MainPage handleEditRecording={handleEditRecording} initialContent="proxy" />} />
|
||||
<Route path="/apikey" element={<MainPage handleEditRecording={handleEditRecording} initialContent="apikey" />} />
|
||||
|
||||
Reference in New Issue
Block a user