Used main branch as base branch in the last time

This commit is contained in:
Carlos Mesquita
2024-07-25 16:59:15 +01:00
parent 10a3243756
commit 877d2f359f
17 changed files with 9051 additions and 1463 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
src/constants/test_firebase.json
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies

7594
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,7 @@
"react-toastify": "^9.1.2",
"react-xarrows": "^2.0.2",
"read-excel-file": "^5.7.1",
"short-unique-id": "^5.0.2",
"short-unique-id": "5.0.2",
"stripe": "^13.10.0",
"swr": "^2.1.3",
"tailwind-scrollbar-hide": "^1.1.7",
@@ -77,7 +77,8 @@
"use-file-picker": "^2.1.0",
"uuid": "^9.0.0",
"wavesurfer.js": "^6.6.4",
"zustand": "^4.3.6"
"zustand": "^4.3.6",
"react-tooltip": "^5.27.1"
},
"devDependencies": {
"@types/blob-stream": "^0.1.33",
@@ -95,7 +96,6 @@
"autoprefixer": "^10.4.13",
"husky": "^8.0.3",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"types/": "paypal/react-paypal-js"
"tailwindcss": "^3.2.4"
}
}

1
public/mat-icon-info.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -0,0 +1,193 @@
import Image from "next/image";
import clsx from "clsx";
import RadialProgressBar from "./RadialProgressBar";
import { AIDetectionAttributes } from "@/interfaces/exam";
import { Tooltip } from 'react-tooltip';
import SegmentedProgressBar from "./SegmentedProgressBar";
// Colors and texts scrapped from gpt's zero react bundle
const AIDetection: React.FC<AIDetectionAttributes> = ({ predicted_class, confidence_category, class_probabilities, sentences }) => {
const probabilityTooltipContent = `
GTP's Zero deep learning model predicts the <br/>
probability this text has been entirely <br/>
generated by AI. For instance, a 40% AI <br/>
probability does not indicate that the text<br/>
contains 40% AI-written content. Rather, it<br/>
indicates the text is more likely to be partially<br/>
human written than be entirely AI-written.
`;
const confidenceTooltipContent = `
Confidence scores are a safeguard to better<br/>
understand AI identification results. GTP Zero<br/>
trained it's deep learning model on a diverse<br/>
dataset of millions of human and AI-written<br/>
documents. Green scores indicate that you can scan<br/>
with confidence that the model has classified<br/>
many similar documents with high accuracy.<br/>
Red scores indicate that this text is dissimilar<br/>
to the ones in their training set, which can impact<br/>
the model's accuracy, and to proceed with caution.
`;
const confidenceKeywords = ["moderately", "highly", "confident", "uncertain"];
var confidence = {
low: {
ai: "GPT Zero is uncertain about this text. If GPT Zero had to classify it, it would be considered",
human: "GPT Zero is uncertain about this text. If GPT Zero had to classify it, it would likely be considered",
mixed: "GPT Zero is uncertain about this text. If GPT Zero had to classify it, it would likely be a"
},
medium: {
ai: "GPT Zero is moderately confident this text was",
human: "GPT Zero is moderately confident this text is entirely",
mixed: "GPT Zero is moderately confident this text is a"
},
high: {
ai: "GPT Zero is highly confident this text was",
human: "GPT Zero is highly confident this text is entirely",
mixed: "GPT Zero is highly confident this text is a"
}
}
var classPrediction = {
ai: {
background: "bg-ai-detection-result-ai-bg",
color: "text-ai-detection-result-ai",
text: "ai generated"
},
mixed: {
background: "bg-ai-detection-result-mixed-bg",
color: "text-ai-detection-result-mixed",
text: "mix of ai and human"
},
human: {
background: "bg-ai-detection-result-human-bg",
color: "text-ai-detection-result-human",
text: "human"
}
}
const segments = [
{ percentage: Math.round(class_probabilities["human"] * 100), subtitle: 'human', color: "ai-detection-result-human" },
{ percentage: Math.round(class_probabilities["mixed"] * 100), subtitle: 'mixed', color: "ai-detection-result-mixed" },
{ percentage: Math.round(class_probabilities["ai"] * 100), subtitle: 'ai', color: "ai-detection-result-ai" }
];
const styleConfidenceText = (text: string): [string, string[]] => {
const keywords: string[] = [];
const styledText = text.split(" ").map(word => {
if (confidenceKeywords.includes(word)) {
keywords.push(word);
return `<span style="font-weight: 500; text-decoration: underline;">${word}</span>`;
}
return word
}).join(" ");
return [styledText, keywords];
};
const confidenceText = confidence[confidence_category][predicted_class];
const [styledText, keywords] = styleConfidenceText(confidenceText);
const tooltipStyle = {
"backgroundColor": "rgb(255, 255, 255)",
"color": "#8992B1",
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
borderRadius: '0.125rem'
}
const highestProbability = Math.max(class_probabilities["ai"], class_probabilities["human"], class_probabilities["mixed"]);
const spanTextColor = highestProbability === class_probabilities["ai"]
? "#f4bf4f"
: highestProbability === class_probabilities["human"]
? "#50c08a"
: "#93aafb";
let spanClassName = highestProbability === class_probabilities["ai"]
? "text-ai-detection-result-ai"
: highestProbability === class_probabilities["human"]
? "text-ai-detection-result-human"
: "text-ai-detection-result-mixed";
spanClassName = `${spanClassName} font-bold text-lg`
const percentage = Math.round(highestProbability * 100)
const hasHighlightedForAI = sentences.some(item => item.highlight_sentence_for_ai);
return (
<>
<Tooltip id="probability-tooltip" className="z-50 bg-white shadow-md rounded-sm" style={tooltipStyle} />
<Tooltip id="confidence-tooltip" className="z-50 bg-white shadow-md rounded-sm" style={tooltipStyle} />
<div className="flex flex-col bg-white p-6 rounded-lg shadow-lg gap-16">
<h1 className="text-lg font-semibold">GPT Zero AI Detection Results</h1>
<div className="flex flex-row -md:flex-col -lg:gap-0 -xl:gap-10 gap-20 items-stretch -md:items-center">
<div className="flex -md:w-5/6 w-1/2 justify-center">
<div className="flex flex-col border rounded-xl">
<h1 className="border-b p-6 font-medium">Text Classification</h1>
<div className="flex flex-row gap-8 items-center p-6">
<RadialProgressBar
percentage={percentage}
text={predicted_class}
color={spanTextColor}
spanClassName={spanClassName}
/>
<div className="flex flex-col gap-1 text-sm">
<div className="flex flex-row items-center">
<span className="mr-2 text-ai-detection-result-ai-text font-semibold text-xl">
{`${Math.round(class_probabilities["ai"] * 100)}%`}
</span>
<span className="text-sm -md:text-xs text-ai-detection-text">Probability AI generated</span>
<a data-tooltip-id="probability-tooltip" data-tooltip-html={probabilityTooltipContent} className='ml-1 flex items-center justify-center'>
<Image src="/mat-icon-info.svg" width={24} height={24} alt="Probability Tooltip" />
</a>
</div>
<div className="flex flex-row items-center gap-1">
<div className={clsx(
"rounded-full w-3 h-3",
confidence_category == 'low' ?
"bg-ai-detection-confidence-low border border-ai-detection-confidence-border" : "bg-ai-detection-confidence-low-transparent"
)}></div>
<div className={clsx(
"rounded-full w-3 h-3",
confidence_category == 'medium' ?
"bg-ai-detection-confidence-medium border border-ai-detection-confidence-border" : "bg-ai-detection-confidence-medium-transparent"
)}></div>
<div className={clsx(
"rounded-full w-3 h-3 mr-2",
confidence_category == 'high' ?
"bg-ai-detection-confidence-high border border-ai-detection-confidence-border" : "bg-ai-detection-confidence-high-transparent"
)}></div>
<span className="text-sm -md:text-xs text-ai-detection-text">{keywords.join(' ')}</span>
<a data-tooltip-id="confidence-tooltip" data-tooltip-html={confidenceTooltipContent} className='ml-1 flex items-center justify-center'>
<Image src="/mat-icon-info.svg" width={24} height={24} alt="Probability Tooltip" />
</a>
</div>
</div>
</div>
</div>
</div>
<div className="flex flex-col border rounded-xl -md:w-5/6 w-2/6">
<h1 className="border-b p-6 font-medium">Probability Breakdown</h1>
<div className="flex items-center w-full h-full">
<SegmentedProgressBar segments={segments} className="w-full px-8 -md:py-8 text-ai-detection-text" />
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-row items-center">
<div dangerouslySetInnerHTML={{ __html: styledText }} className="mr-2"></div>
<div className={clsx(
"flex items-center justify-center p-2 rounded",
classPrediction[predicted_class]['color'],
classPrediction[predicted_class]['background']
)}>
<span className="text-sm">{classPrediction[predicted_class]['text']}</span>
</div>
</div>
{(hasHighlightedForAI && <div>
Sentences that are likely written by AI are <span className="font-semibold bg-ai-detection-highlight">highlighted</span>.
</div>)}
</div>
</div >
<div>
{sentences.map((item, index) => (
<span
key={index}
className={item.highlight_sentence_for_ai ? 'bg-ai-detection-highlight' : ''}
>
{item.sentence}{' '}
</span>
))}
</div>
</>
)
}
export default AIDetection;

View File

@@ -1,138 +0,0 @@
import { DurationUnit } from "@/interfaces/paypal";
import {
CreateOrderActions,
CreateOrderData,
OnApproveActions,
OnApproveData,
OnCancelledActions,
OrderResponseBody,
} from "@paypal/paypal-js";
import {
PayPalButtons,
PayPalScriptProvider,
usePayPalScriptReducer,
} from "@paypal/react-paypal-js";
import axios from "axios";
import { useState, useEffect } from "react";
import { toast } from "react-toastify";
interface Props {
clientID: string;
currency: string;
price: number;
duration: number;
duration_unit: DurationUnit;
loadScript?: boolean;
setIsLoading: (isLoading: boolean) => void;
onSuccess: (duration: number, duration_unit: DurationUnit) => void;
trackingId?: string;
}
export default function PayPalPayment({
clientID,
price,
currency,
duration,
duration_unit,
loadScript,
setIsLoading,
onSuccess,
trackingId,
}: Props) {
const createOrder = async (
data: CreateOrderData,
actions: CreateOrderActions
): Promise<string> => {
if (!trackingId) {
throw new Error("trackingId is not set");
}
setIsLoading(true);
return axios
.post<OrderResponseBody>("/api/paypal", {
currencyCode: currency,
price,
trackingId,
})
.then((response) => response.data)
.then((data) => {
setIsLoading(false);
return data.id;
})
.catch((err) => {
setIsLoading(false);
return err;
});
};
const onApprove = async (data: OnApproveData, actions: OnApproveActions) => {
if (!trackingId) {
throw new Error("trackingId is not set");
}
axios
.post<{ ok: boolean; reason?: string }>("/api/paypal/approve", {
id: data.orderID,
duration,
duration_unit,
trackingId,
})
.then((request) => {
if (request.status !== 200) {
toast.error("Something went wrong, please try again later");
return;
}
toast.success("Your account has been credited more time!");
return onSuccess(duration, duration_unit);
})
.catch((err) => {
console.error(err);
toast.error("Something went wrong, please try again later");
});
};
const onError = async (data: Record<string, unknown>) => {
setIsLoading(false);
};
const onCancel = async (
data: Record<string, unknown>,
actions: OnCancelledActions
) => {
setIsLoading(false);
};
if (trackingId) {
return loadScript ? (
<PayPalScriptProvider
options={{
clientId: clientID,
currency,
intent: "capture",
commit: true,
}}
>
<PayPalButtons
className="w-full"
style={{ layout: "vertical" }}
createOrder={createOrder}
onApprove={onApprove}
onCancel={onCancel}
onError={onError}
/>
</PayPalScriptProvider>
) : (
<PayPalButtons
className="w-full"
style={{ layout: "vertical" }}
createOrder={createOrder}
onApprove={onApprove}
onCancel={onCancel}
onError={onError}
/>
);
}
return null;
}

View File

@@ -0,0 +1,64 @@
import clsx from 'clsx';
import React from 'react';
interface RadialProgressBarProps {
percentage: number;
text: string;
color: string;
spanClassName?: string;
size?: number;
strokeWidth?: number;
strokeOpacity?: number;
}
// https://gist.github.com/eYinka/873be69fae3ef27b103681b8a9f5e379 Omarmarei's answer
const RadialProgressBar: React.FC<RadialProgressBarProps> = ({
percentage,
text,
color,
spanClassName = "",
size = 100,
strokeWidth = 10,
strokeOpacity = 0.5
}) => {
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (percentage / 100) * circumference;
return (
<div className='relative flex items-center justify-center' style={{ width: size, height: size}}>
<svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`
}
className="circular-progress-bar"
>
<circle className="circle-bg" stroke="#e6e6e6" strokeWidth={strokeWidth}
fill="none"
cx={size / 2}
cy={size / 2}
r={radius}
strokeOpacity={strokeOpacity}
/>
<circle
className="circle-progress"
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
fill="none"
cx={size / 2}
cy={size / 2}
r={radius}
strokeDasharray={circumference}
strokeDashoffset={offset}
transform={`rotate(-90 ${size / 2} ${size / 2})`}
strokeOpacity={strokeOpacity}
/>
</svg>
<span className={clsx('absolute', spanClassName)}>{text}</span>
</div>
);
};
export default RadialProgressBar;

View File

@@ -0,0 +1,48 @@
import React from 'react';
import clsx from 'clsx';
interface Segment {
percentage: number;
subtitle: string;
color: string;
}
interface SegmentedProgressBarProps {
segments: Segment[];
height?: number;
className?: string;
}
const SegmentedProgressBar: React.FC<SegmentedProgressBarProps> = ({ segments, height=15, className="" }) => {
return (
<div className={className}>
<div className="relative flex rounded-full overflow-hidden bg-gray-200" style={{height: `${height}px`}}>
{segments.map((segment, index) => (
<div
key={index}
className={clsx(
'h-full opacity-50',
'transition-all duration-500 ease-out',
`bg-${segment.color}`,
{
'rounded-l-full': index === 0,
'rounded-r-full': index === segments.length - 1,
'rounded-none': index !== 0 && index !== segments.length - 1
}
)}
style={{width: `${segment.percentage}%`}}
></div>
))}
</div>
<div className="mt-2 flex text-sm justify-between">
{segments.map((segment, index) => (
<div
key={index}
className="flex flex-col text-center w-fit"
>
<span className={clsx('font-semibold',`text-${segment.color}`)}>{segment.subtitle}</span>
<span>{`${segment.percentage}%`}</span>
</div>
))}
</div>
</div>
);
};
export default SegmentedProgressBar;

View File

@@ -7,11 +7,17 @@ import {Dialog, Tab, Transition} from "@headlessui/react";
import {writingReverseMarking} from "@/utils/score";
import clsx from "clsx";
import ReactDiffViewer, {DiffMethod} from "react-diff-viewer";
import useUser from "@/hooks/useUser";
import AIDetection from "../AIDetection";
export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [showDiff, setShowDiff] = useState(false);
const { user } = useUser();
const aiEval = userSolutions && userSolutions.length > 0 ? userSolutions[0].evaluation?.ai_detection : undefined;
return (
<>
{attachment && (
@@ -159,6 +165,19 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
}>
Recommended Answer
</Tab>
{aiEval && user?.type !== "student" && (
<Tab
className={({ selected }) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-writing",
)
}>
AI Use
</Tab>
)}
</Tab.List>
<Tab.Panels>
<Tab.Panel className="w-full bg-ielts-writing/10 h-fit rounded-xl p-6 flex flex-col gap-4">
@@ -169,6 +188,11 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
{userSolutions[0].evaluation!.perfect_answer.replaceAll(/\s{2,}/g, "\n\n").replaceAll("\\n", "\n")}
</span>
</Tab.Panel>
{aiEval && user?.type !== "student" && (
<Tab.Panel className="w-full bg-ielts-writing/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<AIDetection {...aiEval} />
</Tab.Panel>
)}
</Tab.Panels>
</Tab.Group>
) : (

View File

@@ -1,7 +1,6 @@
import Button from "@/components/Low/Button";
import ProgressBar from "@/components/Low/ProgressBar";
import InviteCard from "@/components/Medium/InviteCard";
import PayPalPayment from "@/components/PayPalPayment";
import ProfileSummary from "@/components/ProfileSummary";
import useAssignments from "@/hooks/useAssignments";
import useInvites from "@/hooks/useInvites";

View File

@@ -24,6 +24,8 @@ import {LevelScore} from "@/constants/ielts";
import {getLevelScore} from "@/utils/score";
import {capitalize} from "lodash";
import Modal from "@/components/Modal";
import { UserSolution } from "@/interfaces/exam";
import ai_usage from "@/utils/ai.detection";
interface Score {
module: Module;
@@ -40,15 +42,18 @@ interface Props {
timeSpent?: number;
inactivity?: number;
};
solutions: UserSolution[];
isLoading: boolean;
onViewResults: (moduleIndex?: number) => void;
}
export default function Finish({user, scores, modules, information, isLoading, onViewResults}: Props) {
export default function Finish({user, scores, modules, information, solutions, isLoading, onViewResults}: Props) {
const [selectedModule, setSelectedModule] = useState(modules[0]);
const [selectedScore, setSelectedScore] = useState<Score>(scores.find((x) => x.module === modules[0])!);
const [isExtraInformationOpen, setIsExtraInformationOpen] = useState(false);
const aiUsage = Math.round(ai_usage(solutions) * 100);
const exams = useExamStore((state) => state.exams);
useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]);
@@ -125,7 +130,7 @@ export default function Finish({user, scores, modules, information, isLoading, o
minTimer={exams.find((x) => x.module === selectedModule)!.minTimer}
disableTimer
/>
<div className="flex gap-4 self-start">
<div className="flex gap-4 self-start w-full">
{modules.includes("reading") && (
<div
onClick={() => setSelectedModule("reading")}
@@ -149,6 +154,7 @@ export default function Finish({user, scores, modules, information, isLoading, o
</div>
)}
{modules.includes("writing") && (
<div className="flex w-full justify-between items-center">
<div
onClick={() => setSelectedModule("writing")}
className={clsx(
@@ -158,6 +164,18 @@ export default function Finish({user, scores, modules, information, isLoading, o
<BsPen className="h-6 w-6" />
<span className="font-semibold">Writing</span>
</div>
{aiUsage >= 50 && user.type !== "student" && (
<div className={clsx(
"flex items-center justify-center border px-3 h-full rounded",
{
'bg-orange-100 border-orange-400 text-orange-700': aiUsage < 80,
'bg-red-100 border-red-400 text-red-700': aiUsage >= 80,
}
)}>
<span className="text-xs">AI Usage</span>
</div>
)}
</div>
)}
{modules.includes("speaking") && (
<div

View File

@@ -152,11 +152,29 @@ export interface WritingExercise {
userSolutions: {
id: string;
solution: string;
evaluation?: CommonEvaluation;
evaluation?: WritingEvaluation;
}[];
topic?: string;
}
export interface AIDetectionAttributes {
predicted_class: 'ai' | 'mixed' | 'human';
confidence_category: 'high' | 'medium' | 'low';
class_probabilities: {
ai: number;
human: number;
mixed: number;
},
sentences: {
sentence: string;
highlight_sentence_for_ai: boolean
}[]
}
export interface WritingEvaluation extends CommonEvaluation {
ai_detection?: AIDetectionAttributes
}
export interface SpeakingExercise {
id: string;
type: "speaking";

View File

@@ -453,6 +453,7 @@ export default function ExamPage({page}: Props) {
isLoading={isEvaluationLoading}
user={user!}
modules={selectedModules}
solutions={userSolutions}
information={{
timeSpent,
inactivity: totalInactivity,

View File

@@ -26,6 +26,9 @@ import useAssignments from "@/hooks/useAssignments";
import {uuidv4} from "@firebase/util";
import {usePDFDownload} from "@/hooks/usePDFDownload";
import useRecordStore from "@/stores/recordStore";
import ai_usage from "@/utils/ai.detection";
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user;
@@ -196,6 +199,8 @@ export default function History({user}: {user: User}) {
const assignment = assignments.find((a) => a.id === assignmentID);
const isDisabled = dateStats.some((x) => x.isDisabled);
const aiUsage = Math.round(ai_usage(dateStats) * 100);
const aggregatedLevels = aggregatedScores.map((x) => ({
module: x.module,
level: calculateBandScore(x.correct, x.total, x.module, user.focus),
@@ -254,12 +259,25 @@ export default function History({user}: {user: User}) {
)}
</div>
</div>
<div className="flex flex-row gap-2">
<span className={textColor}>
Level{" "}
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
</span>
{renderPdfIcon(session, textColor, textColor)}
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">
<span className={textColor}>
Level{" "}
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
</span>
{renderPdfIcon(session, textColor, textColor)}
</div>
{aiUsage >= 50 && user.type !== "student" && (
<div className={clsx(
"ml-auto border px-1 rounded w-fit mr-1",
{
'bg-orange-100 border-orange-400 text-orange-700': aiUsage < 80,
'bg-red-100 border-red-400 text-red-700': aiUsage >= 80,
}
)}>
<span className="text-xs">AI Usage</span>
</div>
)}
</div>
</div>

11
src/utils/ai.detection.ts Normal file
View File

@@ -0,0 +1,11 @@
import { UserSolution } from "@/interfaces/exam";
export default function ai_usage(solutions: UserSolution[]): number {
return solutions.reduce((max, solution) => {
if (solution.type == "writing") {
const aiUse = solution.solutions[0].evaluation?.ai_detection?.class_probabilities["ai"];
return aiUse !== undefined ? Math.max(max, aiUse) : max;
} else {
return 0;
}
}, 0);
}

View File

@@ -1,6 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,tsx,ts,js,jsx}"],
safelist: [
{
pattern: /bg-ai-detection-result-(ai|mixed|human)/,
}
],
theme: {
extend: {
colors: {
@@ -31,6 +36,21 @@ module.exports = {
speaking: {DEFAULT: "#EF5DA8", light: "#FEF6FA", transparent: "rgba(75, 192, 192, 0.5)"},
level: {DEFAULT: "#414288", light: "#C8C8E4", transparent: "rgba(65, 66, 136, 0.5)"},
},
"ai-detection": {
result: {
ai: {DEFAULT: "#f4bf4f", text: "#f0bc4f", bg: "#fff8e8"},
mixed: {DEFAULT:"#93aafb", bg: "rgba(147, 170, 251, 0.3)"},
human: {DEFAULT:"#50c08a", bg: "#e9f9ed"}
},
confidence: {
high: {DEFAULT: "#84d1ac", transparent: "#daf0e3"},
medium: {DEFAULT: "#f7ec88", transparent: "#fcf8d8"},
low: {DEFAULT: "#ffc1c1", transparent: "#ffebe9"},
border: "#888888"
},
highlight: "#ffefb7",
text: "#8992B1"
}
},
screens: {
"-sm": {max: "639px"},

2337
yarn.lock

File diff suppressed because it is too large Load Diff