Skip to content

Commit

Permalink
Merge pull request #20 from ildecimo/develop
Browse files Browse the repository at this point in the history
dev -> main
  • Loading branch information
maxdyy committed Aug 13, 2023
2 parents 6c4946a + e6fb2b2 commit 4490b21
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 151 deletions.
Binary file removed public/images/vertex-ai.jpeg
Binary file not shown.
Binary file added public/images/vertex-ai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions src/app/api/disapprove-review/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextResponse, type NextRequest } from 'next/server';
import { authorize } from '~/lib/authorize';
import * as db from '~/lib/db';
import { disapproveReview } from '~/server/bigcommerce-api';

export async function POST(req: NextRequest) {
const authorized = authorize();

if (!authorized) {
return new NextResponse('Unauthorized', { status: 401 });
}

const accessToken = await db.getStoreToken(authorized.storeHash);

if (!accessToken) {
return new NextResponse(
'Access token not found. Try to re-install the app.',
{ status: 401 }
);
}

const reqBody = (await req.json()) as { productId: number; reviewId: number };

const review = await disapproveReview({
...reqBody,
accessToken,
storeHash: authorized.storeHash,
});

return NextResponse.json(review);
}
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Source_Sans_3 } from 'next/font/google';
import { type Metadata } from 'next/types';
import StyledComponentsRegistry from '~/lib/registry';
import { TailwindIndicator } from '~/components/TailwindIndicator';
import ThemeProvider from '~/components/ThemeProvider';
import StyledComponentsRegistry from '~/lib/registry';
import '~/styles/main.css';

const sourceSans = Source_Sans_3({
subsets: ['latin'],
weight: ['300', '400', '600', '700', '800'],
});

export const metadata: Metadata = { title: 'Product description generator' };
export const metadata: Metadata = { title: 'Review Pulse - ildecimo BigAI' };

export default function RootLayout({
children,
Expand All @@ -25,6 +26,7 @@ export default function RootLayout({
<StyledComponentsRegistry>
<ThemeProvider>
<main className={sourceSans.className}>{children}</main>
<TailwindIndicator />
</ThemeProvider>
</StyledComponentsRegistry>
</body>
Expand Down
26 changes: 15 additions & 11 deletions src/app/product/[productId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import {
} from '~/server/bigcommerce-api';

import { ProductReviewList } from '~/components/ProductReviewList';
import { getReviewAnalysesByProductId } from '~/lib/db';

interface PageProps {
params: { productId: string };
}

export default async function Page(props: PageProps) {
const { productId } = props.params;

const authorized = authorize();

if (!authorized) {
Expand All @@ -27,19 +26,24 @@ export default async function Page(props: PageProps) {
throw new Error('Access token not found. Try to re-install the app.');
}

const id = Number(productId);

const product = await fetchProductWithAttributes(
id,
accessToken,
authorized.storeHash
);
const productId = Number(props.params.productId);

const reviews = await fetchReviews(id, accessToken, authorized.storeHash);
const [product, reviews, reviewAnalyses] = await Promise.all([
fetchProductWithAttributes(productId, accessToken, authorized.storeHash),
fetchReviews(productId, accessToken, authorized.storeHash),
getReviewAnalysesByProductId({
productId,
storeHash: authorized.storeHash,
}),
]);

return (
<div>
<ProductReviewList product={product} reviews={reviews} />
<ProductReviewList
product={product}
reviews={reviews}
reviewAnalyses={reviewAnalyses ?? []}
/>
</div>
);
}
43 changes: 28 additions & 15 deletions src/app/product/[productId]/review/[reviewId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import {
} from '~/server/bigcommerce-api';

import { ReviewDetail } from '~/components/ReviewDetail';
import {
analyzeIssuesCategory,
analyzeReview,
} from '~/server/google-ai/analyze-review';
import { analyzeReview } from '~/server/google-ai/analyze-review';

interface PageProps {
params: { reviewId: string; productId: string };
Expand Down Expand Up @@ -48,22 +45,38 @@ export default async function Page(props: PageProps) {

const customerReviews = reviews.filter((r) => r.email === review.email);

const sentimentAnalysis = await analyzeReview({
rating: review.rating,
text: review.text,
title: review.title,
let sentimentAnalysis = await db.getReviewAnalysis({
productId,
reviewId,
storeHash: authorized.storeHash,
});

const issuesCategories = await analyzeIssuesCategory({
rating: review.rating,
text: review.text,
title: review.title,
});
if (!sentimentAnalysis) {
const freshAnalysis = await analyzeReview({
rating: review.rating,
text: review.text,
title: review.title,
});

if (freshAnalysis && typeof freshAnalysis !== 'string') {
sentimentAnalysis = freshAnalysis;

await db.setReviewAnalysis({
analysis: freshAnalysis,
productId,
reviewId,
storeHash: authorized.storeHash,
});
}
}

return (
<ReviewDetail
sentimentAnalysis={sentimentAnalysis}
issuesCategories={issuesCategories}
sentimentAnalysis={
!sentimentAnalysis || typeof sentimentAnalysis === 'string'
? undefined
: sentimentAnalysis
}
customerOrders={customerOrders}
customerReviews={customerReviews}
product={product}
Expand Down
18 changes: 6 additions & 12 deletions src/components/AIChatBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,22 @@ interface AIChatBubbleProps {

export const AIChatBubble = ({ message, hideAvatar }: AIChatBubbleProps) => {
return (
<div className="mb-4 flex max-w-[450px] items-center">
<div className="flex max-w-[450px] items-center">
{!hideAvatar && (
<div className="mr-4 flex flex-none flex-col items-center space-y-1">
<Image
src="/images/vertex-ai.jpeg"
alt="AI Avatar"
width={32}
height={32}
className="rounded-full"
/>
<p className="block text-xs">Vertex AI</p>
<Image src="/images/vertex-ai.png" alt="" width={32} height={32} />
<p className="block text-xs text-gray-600">Vertex AI</p>
</div>
)}
<div
className={`relative mb-2 flex-1 rounded-lg bg-gray-100/80 p-2 text-lg ${
className={`relative flex-1 rounded-lg bg-gray-100 px-3 py-2 text-lg ${
hideAvatar ? 'ml-[60px]' : ''
}`}
>
<div>{message}</div>
<div className="text-gray-800">{message}</div>

{!hideAvatar && (
<div className="absolute left-0 top-1/2 h-2 w-2 -translate-x-1/2 rotate-45 transform bg-gray-100/80"></div>
<div className="absolute left-0 top-1/2 h-3 w-3 -translate-x-1/2 rotate-45 transform bg-gray-100"></div>
)}
</div>
</div>
Expand Down
22 changes: 11 additions & 11 deletions src/components/IssueBadges.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Badge } from '@bigcommerce/big-design';

interface IssuesBadgesProps {
issuesCategoriesArray: string[];
issuesCategoriesArray?: string[];
}

export const IssuesBadges = ({ issuesCategoriesArray }: IssuesBadgesProps) => {
export const IssuesBadges = ({
issuesCategoriesArray = [],
}: IssuesBadgesProps) => {
return (
<div className="flex flex-wrap items-center">
<span className="mr-2">Issues found:</span>

{issuesCategoriesArray.map((issue) => (
<Badge
key={issue}
label={issue}
variant={issue === 'no issues' ? 'success' : 'danger'}
>
{issue}
</Badge>
))}
{issuesCategoriesArray.length > 0 ? (
issuesCategoriesArray.map((issue) => (
<Badge key={issue} label={issue} variant="warning" />
))
) : (
<Badge label="No issues" variant="success" />
)}
</div>
);
};
41 changes: 41 additions & 0 deletions src/components/ProductReviewList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';

import { type Product, type Review } from 'types';

import { Table } from '@bigcommerce/big-design';
Expand All @@ -11,16 +12,20 @@ import { NextLink } from '~/components/NextLink';
import { ReviewStatusBadge } from '~/components/ReviewStatusBadge';
import { StarRating } from '~/components/StarRating';

import { ScoreCircle } from '~/components/ScoreCircle';
import { type ReviewAnalysesByProductIdResponse } from '~/lib/db';
import { convertToDateString } from '~/utils/utils';

interface ProductReviewListProps {
product: Product;
reviews: Review[];
reviewAnalyses: ReviewAnalysesByProductIdResponse;
}

export const ProductReviewList = ({
product,
reviews,
reviewAnalyses,
}: ProductReviewListProps) => {
const averageRating = useMemo(() => {
return (
Expand All @@ -39,6 +44,15 @@ export const ProductReviewList = ({
);
}, [approvedReviews]);

const averageSentiment = useMemo(
() =>
Math.floor(
reviewAnalyses.reduce((acc, analysis) => acc + analysis.data.score, 0) /
reviewAnalyses.length
),
[reviewAnalyses]
);

return (
<div>
<div>
Expand Down Expand Up @@ -94,11 +108,38 @@ export const ProductReviewList = ({
</div>
</>
}
topRightContent={
<ScoreCircle
score={averageSentiment}
tooltip="Average product sentiment"
/>
}
/>
</div>

<Table
columns={[
{
header: 'Score',
hash: 'score',
render: (review) => {
const score = reviewAnalyses?.find(
(r) => r.id === `${review.id}`
)?.data?.score;

return (
<ScoreCircle
score={score}
tooltip={
typeof score === 'number'
? 'Sentiment score'
: 'Unanalyzed review'
}
tooltipPlacement="right"
/>
);
},
},
{
header: 'Rating',
hash: 'rating',
Expand Down
Loading

0 comments on commit 4490b21

Please sign in to comment.