ai 추가

This commit is contained in:
익희 김 2024-12-06 21:56:30 +09:00
parent 1fa6f72233
commit b1f0627611
3 changed files with 116 additions and 21 deletions

View File

@ -7,7 +7,7 @@ import { Box, Container, Stack, Grid } from '@mui/material';
// components // components
import RelatedProductsCarousel from 'src/components/_main/product/relatedProducts'; import RelatedProductsCarousel from 'src/components/_main/product/relatedProducts';
import ProductDetailTabs from 'src/components/_main/product/tabs'; import ProductDetailTabs from 'src/components/_main/product/tabs';
import ProductAdditionalInfo from 'src/components/_main/product/additionalInfo'; // import ProductAdditionalInfo from 'src/components/_main/product/additionalInfo';
import ProductDetailsCarousel from 'src/components/carousels/customPaginationSilder'; import ProductDetailsCarousel from 'src/components/carousels/customPaginationSilder';
import ProductDetailsSumary from 'src/components/_main/product/summary'; import ProductDetailsSumary from 'src/components/_main/product/summary';
import HeaderBreadcrumbs from 'src/components/headerBreadcrumbs'; import HeaderBreadcrumbs from 'src/components/headerBreadcrumbs';
@ -81,13 +81,13 @@ export default async function ProductDetail({ params: { slug } }) {
/> />
</Grid> </Grid>
</Grid> </Grid>
<ProductAdditionalInfo /> {/* <ProductAdditionalInfo /> */}
<Suspense fallback={<></>}> <Suspense fallback={<></>}>
<ProductDetailTabs {/* <ProductDetailTabs
product={{ description: data.description, _id: data._id }} product={{ description: data.description, _id: data._id }}
totalRating={totalRating} totalRating={totalRating}
totalReviews={totalReviews} totalReviews={totalReviews}
/> /> */}
</Suspense> </Suspense>
<Suspense fallback={<></>}> <Suspense fallback={<></>}>
<RelatedProductsCarousel id={data._id} category={category?.slug} /> <RelatedProductsCarousel id={data._id} category={category?.slug} />

View File

@ -244,7 +244,7 @@ export default function ProductDetailsSumary({ ...props }) {
({totalRating?.toFixed(1)}) ({totalRating?.toFixed(1)})
</Typography> </Typography>
</Stack> </Stack>
<Stack direction="row" alignItems="center" spacing={1} color="text.secondary"> {/* <Stack direction="row" alignItems="center" spacing={1} color="text.secondary">
<TbMessage size={18} /> <TbMessage size={18} />
<Typography variant="subtitle2" color="text.secondary"> <Typography variant="subtitle2" color="text.secondary">
{product?.reviews.length}{' '} {product?.reviews.length}{' '}
@ -256,7 +256,7 @@ export default function ProductDetailsSumary({ ...props }) {
<Typography variant="subtitle2" color="text.secondary"> <Typography variant="subtitle2" color="text.secondary">
{product?.sold} sold {product?.sold} sold
</Typography> </Typography>
</Stack> </Stack> */}
</Stack> </Stack>
<Stack direction="row" alignItems="center" spacing={1} mt={1.5}> <Stack direction="row" alignItems="center" spacing={1} mt={1.5}>
<Typography variant="subtitle1">Brand:</Typography> <Typography variant="subtitle1">Brand:</Typography>
@ -294,15 +294,15 @@ export default function ProductDetailsSumary({ ...props }) {
</Grid> </Grid>
<Grid item xs={12} md={5}> <Grid item xs={12} md={5}>
<Card sx={{ p: 2, position: 'sticky', top: 156 }}> <Card sx={{ p: 2, position: 'sticky', top: 156 }}>
<Typography variant="h4" className="text-price"> {/* <Typography variant="h4" className="text-price">
{!isLoading && isLoaded && fCurrency(cCurrency(product?.priceSale))} &nbsp; {!isLoading && isLoaded && fCurrency(cCurrency(product?.priceSale))} &nbsp;
{product?.price <= product?.priceSale ? null : ( {product?.price <= product?.priceSale ? null : (
<Typography component="span" className="old-price" color="text.secondary"> <Typography component="span" className="old-price" color="text.secondary">
{!isLoading && isLoaded && fCurrency(cCurrency(product?.price))} {!isLoading && isLoaded && fCurrency(cCurrency(product?.price))}
</Typography> </Typography>
)} )}
</Typography> </Typography> */}
<Stack direction="row" alignItems="center" spacing={1} className="incrementer-wrapper" my={2}> {/* <Stack direction="row" alignItems="center" spacing={1} className="incrementer-wrapper" my={2}>
{isLoading ? ( {isLoading ? (
<Box sx={{ float: 'right' }}> <Box sx={{ float: 'right' }}>
<Skeleton variant="rounded" width={120} height={40} /> <Skeleton variant="rounded" width={120} height={40} />
@ -327,10 +327,10 @@ export default function ProductDetailsSumary({ ...props }) {
> >
{product?.available > 0 ? `${product?.available} Items` : <span>Out of stock</span>} {product?.available > 0 ? `${product?.available} Items` : <span>Out of stock</span>}
</Typography> </Typography>
</Stack> </Stack> */}
<Stack spacing={1} className="contained-buttons" mb={2}> <Stack spacing={1} className="contained-buttons" mb={2}>
<Button {/* <Button
fullWidth fullWidth
disabled={isMaxQuantity || isLoading || product?.available < 1} disabled={isMaxQuantity || isLoading || product?.available < 1}
// size={isMobile ? 'medium' : 'large'} // size={isMobile ? 'medium' : 'large'}
@ -383,9 +383,9 @@ export default function ProductDetailsSumary({ ...props }) {
> >
Add to Wishlist Add to Wishlist
</LoadingButton> </LoadingButton>
)} )} */}
{compareProducts?.filter((v) => v._id === product._id).length > 0 ? ( {/* {compareProducts?.filter((v) => v._id === product._id).length > 0 ? (
<Button <Button
startIcon={<GoGitCompare />} startIcon={<GoGitCompare />}
fullWidth fullWidth
@ -407,7 +407,7 @@ export default function ProductDetailsSumary({ ...props }) {
> >
Add to Compare Add to Compare
</Button> </Button>
)} )} */}
<Stack direction="row" spacing={0.5} justifyContent={'center'}> <Stack direction="row" spacing={0.5} justifyContent={'center'}>
<Tooltip title="Copy Prooduct URL"> <Tooltip title="Copy Prooduct URL">
@ -423,14 +423,14 @@ export default function ProductDetailsSumary({ ...props }) {
</Tooltip> </Tooltip>
{isClient && ( {isClient && (
<> <>
<Tooltip title="Share on WhatsApp"> <Tooltip title="문의하기">
<WhatsappShareButton url={window?.location.href || ''}> <WhatsappShareButton url={window?.location.href || ''}>
<IconButton sx={{ color: '#42BC50' }} aria-label="whatsapp"> <IconButton sx={{ color: '#42BC50' }} aria-label="whatsapp">
<IoLogoWhatsapp size={24} /> <IoLogoWhatsapp size={24} />
</IconButton> </IconButton>
</WhatsappShareButton> </WhatsappShareButton>
</Tooltip> </Tooltip>
<Tooltip title="Share on Facebook"> {/* <Tooltip title="Share on Facebook">
<FacebookShareButton url={window?.location.href || ''}> <FacebookShareButton url={window?.location.href || ''}>
<IconButton sx={{ color: '#1373EC' }} aria-label="facebook"> <IconButton sx={{ color: '#1373EC' }} aria-label="facebook">
<FaFacebook size={24} /> <FaFacebook size={24} />
@ -450,13 +450,13 @@ export default function ProductDetailsSumary({ ...props }) {
<FaLinkedin size={24} /> <FaLinkedin size={24} />
</IconButton> </IconButton>
</LinkedinShareButton> </LinkedinShareButton>
</Tooltip> </Tooltip> */}
</> </>
)} )}
</Stack> </Stack>
</Stack> </Stack>
<Divider /> {/* <Divider />
{shippingData.map((item, index) => ( {shippingData.map((item, index) => (
<Stack <Stack
key={index} key={index}
@ -473,7 +473,7 @@ export default function ProductDetailsSumary({ ...props }) {
{item.name} {item.name}
</Typography> </Typography>
</Stack> </Stack>
))} ))} */}
</Card> </Card>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useState, useRef } from 'react';
// mui // mui
import { alpha, styled } from '@mui/material/styles'; import { alpha, styled } from '@mui/material/styles';
import { Box, List, Stack, Paper, Button, ListItem, Typography, Skeleton, IconButton } from '@mui/material'; import { Box, List, Stack, Paper, Button, ListItem, Typography, Skeleton, IconButton, Modal } from '@mui/material';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
// components // components
@ -43,11 +43,79 @@ UploadMultiFile.propTypes = {
export default function UploadMultiFile({ ...props }) { export default function UploadMultiFile({ ...props }) {
const { error, files, onRemove, blob, isEdit, onRemoveAll, loading, sx, ...other } = props; const { error, files, onRemove, blob, isEdit, onRemoveAll, loading, sx, ...other } = props;
const [modalOpen, setModalOpen] = useState(false);
const [image, setImage] = useState('');
const [predictions, setPredictions] = useState([]); // Roboflow
const canvasRef = useRef(null); //
const hasFile = files.length > 0; const hasFile = files.length > 0;
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
...other ...other
}); });
// Roboflow API
const detectWithRoboflow = async () => {
if (!image) return;
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
const img = new Image();
img.src = image;
img.onload = async () => {
//
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Blob
const response = await fetch(image);
const blob = await response.blob();
// API
const formData = new FormData();
formData.append('file', blob);
const apiEndpoint = `https://detect.roboflow.com/picup/1?api_key=s9OJq0UPljSqkPsJY6xP`;
try {
const apiResponse = await fetch(apiEndpoint, {
method: 'POST',
body: formData
});
const result = await apiResponse.json();
console.log('Roboflow Predictions:', result);
if (result.predictions) {
setPredictions(result.predictions);
//
result.predictions.forEach((prediction) => {
const { x, y, width, height, class: label, confidence } = prediction;
//
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, width, height);
//
ctx.fillStyle = 'red';
ctx.font = '16px Arial';
ctx.fillText(`${label} (${(confidence * 100).toFixed(2)}%)`, x, y > 10 ? y - 5 : 10);
});
}
} catch (error) {
console.error('Error detecting objects:', error);
}
};
};
// AI
const objectDetect = () => {
detectWithRoboflow();
};
return ( return (
<Box sx={{ width: '100%', ...sx }}> <Box sx={{ width: '100%', ...sx }}>
<DropZoneStyle <DropZoneStyle
@ -127,6 +195,10 @@ export default function UploadMultiFile({ ...props }) {
<Paper <Paper
variant="outlined" variant="outlined"
component="img" component="img"
onClick={() => {
setModalOpen(true);
setImage(file.url);
}}
src={!file.blob ? file.url : file.blob} src={!file.blob ? file.url : file.blob}
sx={{ sx={{
width: '100%', width: '100%',
@ -140,6 +212,29 @@ export default function UploadMultiFile({ ...props }) {
</React.Fragment> </React.Fragment>
))} ))}
</List> </List>
<Modal open={modalOpen} sx={{ position: 'relative' }}>
<Stack
direction="col"
sx={{ background: '#fff', position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }}
>
<IconButton
sx={{ position: 'absolute;', right: 0, top: 0 }}
onClick={() => {
setModalOpen(false);
setImage('');
}}
>
<CloseRoundedIcon fontSize="small" />
</IconButton>
<Button
sx={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translate(-50%, -30px)' }}
onClick={() => objectDetect()}
>
AI 상품 검출
</Button>
<canvas ref={canvasRef} style={{ width: '100%' }} />
</Stack>
</Modal>
{hasFile && ( {hasFile && (
<Stack direction="row" justifyContent="flex-end"> <Stack direction="row" justifyContent="flex-end">