Commit a96ce8d7 authored by IT20013950 Lakshani N.V.M.'s avatar IT20013950 Lakshani N.V.M.

Merge branch 'IT20013950-Lakshani' into 'master'

Knn completion with complete frontend

See merge request !7
parents 335a4719 46882fdb
git-colab-terminal.ipynb
**/__pycache__/
.vscode/
.creds/
/models
/datasets
/wandb
/evaluations
\ No newline at end of file
Inputs ->
feature names -> List of features used in the TFIDF vectoriser. Used to give words in the final output
threshold T -> Threshold to consider an output as counterfactual
classifier_fn (C) -> classifier prediction probability function in the random forest classifier
max_iter -> Maximum number of iterations run before termination if a CF is not found
max_time -> Maximum time that the algorithm run before termination if a CF is not found
Output ->
list of words to remove or to change to reverse the model output.
Process ->
input -> Instance W -> document to classify. Has m words
c = initial predicted class
p = probability of the predicted class
r = revert or not. Set to zero if predicted class is positive.
n_explanations = 0
explanations = {}
combinations_to_expand = {}
prob_combinations_to_expand = {}
shap_combinations_to_expand = {}
W = [] indices of features
R = [] indices of replacement features of feature w_i if such replcement exsists.
for i = 1 to m:
p_n = C(w_i) # Instance with w_i removed or changed to r_i
if (p_n < T):
explanations = explanations U w_i
else:
combinations_to_expand = combinations_to_expand U w_i
prob_combinations_to_expand = prob_combinations_to_expand U w_i
end if
end for
iteration = 1
start time
while True:
if iteration > max_iter OR time > max_time:
end while
combi = word combinations to remove where change in prediction score towards reverse class is maximal
new_combi_set = expanded combinations of combi without the exisiting combinations in explanations
for combo in new_combi_set do:
p_n = C(w_i)
if (p_n < T):
explanations = explanations U w_i
else:
combinations_to_expand = combinations_to_expand U w_i
prob_combinations_to_expand = prob_combinations_to_expand U w_i
shap_combinations_to_expand = shap_combinations_to_expand U shap_vals(w_i)
end if
end for
iteration ++
increment time
end while
replcement antonyms are generated from the wordnet library
Gives faster results than removal as it pushes the results towards the reverse class
Need to choose a proper antonym as antonyms maybe chosen to push towards the current class.
This is prevented by using SHAP values to choose antonyms
Used paper - Text Counterfactuals via Latent Optimization and Shapley-Guided Search
\ No newline at end of file
Inputs ->
feature names -> List of features used in the TFIDF vectoriser. Used to give words in the final output
threshold T -> Threshold to consider an output as counterfactual
classifier_fn (C) -> classifier prediction probability function in the random forest classifier
max_iter -> Maximum number of iterations run before termination if a CF is not found
max_time -> Maximum time that the algorithm run before termination if a CF is not found
Output ->
list of words to remove to reverse the model output.
Process ->
input -> Instance W -> document to classify. Has m words
c = initial predicted class
p = probability of the predicted class
r = revert or not. Set to zero if predicted class is positive.
n_explanations = 0
explanations = {}
combinations_to_expand = {}
prob_combinations_to_expand = {}
shap_combinations_to_expand = {}
shap_vals = {}_n shapley values of each feature with reference point taken as zero vector
W = [] indices of features sorted in the descending order of shap values
for i = 1 to m:
p_n = C(w_i) -> Instance with the feature w_i removed
if (p_n < T):
explanations = explanations U w_i
else:
combinations_to_expand = combinations_to_expand U w_i
prob_combinations_to_expand = prob_combinations_to_expand U w_i
shap_combinations_to_expand = shap_combinations_to_expand U shap_vals(w_i)
end if
end for
iteration = 1
start time
while True:
if iteration > max_iter OR time > max_time:
end while
combi = word combinations to remove where shap_combinations_to_expand is maximal
new_combi_set = expanded combinations of combi withiut the exisiting combinations in explanations
for combo in new_combi_set do:
p_n = C(w_i)
if (p_n < T):
explanations = explanations U w_i
end while
else:
combinations_to_expand = combinations_to_expand U w_i
prob_combinations_to_expand = prob_combinations_to_expand U w_i
shap_combinations_to_expand = shap_combinations_to_expand U shap_vals(w_i)
end if
end for
iteration ++
increment time
end while
Does not always converge ->
Even though shap values individually give measures for each feature better than score change,
for a set of features, algebraic sum of shap values is not a good measure.
But for changes with less number of words like 1-4 words:
using shap values give faster results
observation - Also gives better results when converting negative results to positive results when using shap values
Can use feature_importance_ of Random forest instead of shapely values. But need to check if the feature contributes to positive or negative change in the current instance.
x1 -> (2.98)
x2 -> 2.98 - 0.6
x3 -> 2.98 + 1.3
x4 -> 2.98 + 2.0
Current plan - Random forest
Get shapley values of all features for the current model. -> Reduces the randomness of removing features.
(Text Counterfactuals via Latent Optimization and Shapley-Guided Search) THis paper gives replacements instead of removing the feature.
Get the features contributing to each tree and the whole model for the Random forest -> Reduces the affecting feature number.
Order the features by the shapley value.
Change the value of leaf nodes and try to find cunterfactuals.
Works only for the Random Forest.
Current Implementation - Random Forest
Get shapley values of all features for the current model. -> Reduces the randomness of removing features
Get shapely values of features.
Expand and prune the required instance to generate counterfactuals.
Expand and prune order of the counterfactuals is sorted according to the shapely value of each feature.
Run the prediction algorithm of the RF model to check if a desirable counterfactual is generated.
If a desirable length counterfactual is generated, output the counterfactual in a text format.
To be Developed ->
Get the desicion path of each tree in the random forest.
Find the features affecting the prediction.
Initially consider only the features with high shapely values and are in the set of features in decision trees.
Check for the speed of operation.
NEW
Previously -> used shap values (Accurate. But takes time to calculate.)
Therefore, Use feature_importance_ in random forest model.
These are calculated when training the model. -> Takes relatively less time when compared to shap. No time taken to calculate values.
Issues -> 2) Not instance specific
1) Does not give direction of class change
solved -> Get feature_importance_
take instance
remove feature importances not related to tte current instance -> Reduces memory consuption + takes less time to calculate.
Take each feature -> Check the affect of removing that feature -> Assign a class change sign to the feature_importance_.
Current Implementation - Logistic Regression
Get shapley values of all features for the current model. -> Reduces the randomness of removing features
Get shapely values of features.
Expand and prune the required instance to generate counterfactuals.
Expand and prune order of the counterfactuals is sorted according to the shapely value of each feature.
Run the prediction algorithm of the LR model to check if a desirable counterfactual is generated.
If a desirable length counterfactual is generated, output the counterfactual in a text format.
To be Developed
Get the feature importance of the current model using weights of features.
Check for replacements of features which maximises the change in prediction probability. -> Calculated using shap values.
Extension to https://arxiv.org/pdf/1906.09293.pdf -> by adding replacements instead of removals to maximise the change
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
name: imdb
source_url: https://sliit-xai.s3.ap-south-1.amazonaws.com/datasets/imdb.zip
paths:
data: imdb.csv
split:
test: 0.1
train: 0.8
val: 0.1
labels:
- negative
- positive
extras:
input_encoder_path: tfidf.pkl
min_df: 30
name: snli_1.0_contra
paths:
test: snli_1.0_contra_test.csv
train: snli_1.0_contra_train.csv
val: snli_1.0_contra_val.csv
source_url: https://sliit-xai.s3.ap-south-1.amazonaws.com/datasets/snli_1.0_contra.zip
model_name: t5-small
max_token_len: 64
name: analysis-models
source_url: https://sliit-xai.s3.ap-south-1.amazonaws.com/models/analysis-models.zip
paths:
tfidf: tfidf.pkl
knn: knn.pkl
lr: lr.pkl
rf: rf.pkl
svm: svm.pkl
models:
knn: knn.pkl
lr: lr.pkl
rf: rf.pkl
svm: svm.pkl
encoders:
input_encoder_name: tfidf
output_encoder_name: lut
output_labels:
- negative
- positive
name: t5-cf-generator
paths:
model: model.pt
model_config: t5-small
source_url: https://sliit-xai.s3.ap-south-1.amazonaws.com/models/t5-cf-generator.zip
name: wf-cf-generator
flip_prob: 0.5
flipping_tags:
- VB
- VBD
- VBG
- VBN
- VBP
- VBZ
sample_prob_decay_factor: 0.2
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
This diff is collapsed.
{
"name": "xai-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@aws-sdk/client-lambda": "^3.359.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
"@mui/lab": "^5.0.0-alpha.134",
"@mui/material": "^5.13.6",
"next": "13.4.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.45.4"
}
}
precision recall f1-score support
0 0.82 0.78 0.80 2529
1 0.79 0.82 0.81 2470
accuracy 0.80 4999
macro avg 0.80 0.80 0.80 4999
weighted avg 0.80 0.80 0.80 4999
---- Classification report for LR ----
precision recall f1-score support
0 0.90 0.89 0.90 2529
1 0.89 0.90 0.89 2470
accuracy 0.89 4999
macro avg 0.89 0.89 0.89 4999
weighted avg 0.89 0.89 0.89 4999
precision recall f1-score support
0 0.86 0.85 0.86 2529
1 0.85 0.86 0.85 2470
accuracy 0.85 4999
macro avg 0.85 0.85 0.85 4999
weighted avg 0.85 0.85 0.85 4999
precision recall f1-score support
0 0.90 0.90 0.90 2529
1 0.90 0.89 0.90 2470
accuracy 0.90 4999
macro avg 0.90 0.90 0.90 4999
weighted avg 0.90 0.90 0.90 4999
import { AWS_ACCESS_KEY, AWS_REGION, AWS_SECRET_KEY } from "@/constants";
import { LambdaClient } from "@aws-sdk/client-lambda";
const lambdaClient = new LambdaClient({
region: AWS_REGION,
credentials: {
accessKeyId: AWS_ACCESS_KEY,
secretAccessKey: AWS_SECRET_KEY,
},
});
export { lambdaClient };
import * as React from "react";
import TextareaAutosize from "@mui/base/TextareaAutosize";
import { styled } from "@mui/system";
export default React.forwardRef((props, ref) => {
const blue = {
100: "#DAECFF",
200: "#b6daff",
400: "#3399FF",
500: "#007FFF",
600: "#0072E5",
900: "#003A75",
};
const grey = {
50: "#f6f8fa",
100: "#eaeef2",
200: "#d0d7de",
300: "#afb8c1",
400: "#8c959f",
500: "#6e7781",
600: "#57606a",
700: "#424a53",
800: "#32383f",
900: "#24292f",
};
const StyledTextarea = styled(TextareaAutosize)(
({ theme }) => `
width: 100%;
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
padding: 12px;
border-radius: 12px 12px 0 12px;
color: ${theme.palette.mode === "dark" ? grey[300] : grey[900]};
background: ${theme.palette.mode === "dark" ? grey[900] : "#fff"};
border: 1px solid ${theme.palette.mode === "dark" ? grey[700] : grey[200]};
box-shadow: 0px 2px 24px ${
theme.palette.mode === "dark" ? blue[900] : blue[100]
};
&:hover {
border-color: ${blue[400]};
}
&:focus {
border-color: ${blue[400]};
box-shadow: 0 0 0 3px ${
theme.palette.mode === "dark" ? blue[600] : blue[200]
};
}
// firefox
&:focus-visible {
outline: 0;
}
`
);
return <StyledTextarea {...props} ref={ref} />;
});
import commonStyles from "@/styles/commonStyles";
import { Box, Button } from "@mui/material";
const FormButtons = ({ reset, close }) => {
return (
<Box sx={commonStyles.btnContainer}>
<Button variant="outlined" type="submit">
Add
</Button>
<Button variant="outlined" onClick={reset}>
Reset
</Button>
<Button variant="outlined" onClick={close}>
Close
</Button>
</Box>
);
};
export default FormButtons;
import commonStyles from "@/styles/commonStyles";
import { Box, IconButton, Paper } from "@mui/material";
import styles from "./styles";
import { Close } from "@mui/icons-material";
const ModalContainer = ({ children, show, close }) => {
return (
<Box sx={commonStyles.backdrop(show)} onClick={close}>
<Paper sx={styles.modal} onClick={(e) => e.stopPropagation()}>
<Box sx={styles.headRibbon}>
<IconButton sx={styles.cross} onClick={close}>
<Close />
</IconButton>
</Box>
<Box sx={styles.body}>{children}</Box>
</Paper>
</Box>
);
};
export default ModalContainer;
import commonStyles from "@/styles/commonStyles";
export default {
modal: {
minWidth: "640px",
},
headRibbon: {
display: "flex",
justifyContent: "flex-end",
},
cross: {
...commonStyles.iconBtn,
margin: "7px",
padding: "2px",
},
body: {
padding: "20px",
paddingTop: "0px",
},
};
export const AWS_ACCESS_KEY = process.env["NEXT_PUBLIC_AWS_ACCESS_KEY"];
export const AWS_SECRET_KEY = process.env["NEXT_PUBLIC_AWS_SECRET_KEY"];
export const AWS_REGION = process.env["NEXT_PUBLIC_AWS_REGION"];
export const AWS_LAMBDA_NAME = process.env["NEXT_PUBLIC_AWS_XAI_LAMBDA"];
export const MODEL_NAME_KNN = "knn";
export const MODEL_NAME_SVM = "svm";
export const MODEL_NAME_LR = "lr";
export const MODEL_NAME_RF = "rf";
export const STATUS_CODE_MAP = {
200: "OK",
400: "Bad Request",
500: "Server Error",
};
import commonStyles from "@/styles/commonStyles";
import { Box, IconButton, TextField, Typography } from "@mui/material";
import Textarea from "@/components/Textarea";
import { useContext, useEffect, useRef, useState } from "react";
import { startTestCase } from "@/functions/api";
import { LoadingButton } from "@mui/lab";
import Configurations from "./configurations/Configurations";
import { ModalContext } from "@/providers/modalProvider/ModalProvider";
import { MODEL_NAME_KNN, MODEL_NAME_SVM } from "@/constants";
import { Close } from "@mui/icons-material";
import styles from "./styles";
const Analysis = ({ model }) => {
const { setNotification } = useContext(ModalContext);
const textareaRef = useRef();
const variationsRef = useRef();
const [loading, setLoading] = useState(false);
const [configurations, setConfigurations] = useState([]);
const [report, setReport] = useState();
const evaluationHandler = () => {
const prompt = textareaRef.current && textareaRef.current.value;
const variations = variationsRef.current
? parseInt(variationsRef.current.value)
: null;
if (prompt !== "" && model && variations !== 0) {
setLoading(true);
startTestCase({
model_name: model,
prompt,
variations,
configurations,
})
.then(setReport)
.catch(setNotification)
.finally(() => {
setLoading(false);
});
}
};
useEffect(() => {
setConfigurations([]);
setReport();
}, [model]);
return (
<Box sx={commonStyles.sectionContainer}>
<Typography variant="h3">Analysis</Typography>
<Textarea
ref={textareaRef}
placeholder="Prompt"
sx={commonStyles.text}
/>
{(model === MODEL_NAME_SVM || model === MODEL_NAME_KNN) && (
<TextField
inputRef={variationsRef}
sx={commonStyles.text}
label="Variations"
size="small"
type="number"
defaultValue={2}
/>
)}
<Typography variant="h4">Test Cases</Typography>
<Configurations
model={model}
configurations={configurations}
setConfigurations={setConfigurations}
/>
<LoadingButton
loading={loading}
sx={commonStyles.btn}
onClick={evaluationHandler}
>
Analyze
</LoadingButton>
{report && (
<>
<Typography variant="h4">Report</Typography>
<Box sx={commonStyles.outputContainer()}>
<IconButton
sx={styles.close}
onClick={() => setReport()}
>
<Close />
</IconButton>
<Typography variant="body1" sx={commonStyles.codeBlock}>
{report}
</Typography>
</Box>
</>
)}
</Box>
);
};
export default Analysis;
import { Box, IconButton, Typography } from "@mui/material";
import styles from "./configForm/styles";
import { Add } from "@mui/icons-material";
import { useState } from "react";
import ConfigForm from "./configForm/ConfigForm";
import ConfigTable from "./configTable/ConfigTable";
import ModalContainer from "@/components/modalContainer/ModalContainer";
const Configurations = ({ model, configurations, setConfigurations }) => {
const [displayModal, setDisplayModal] = useState(false);
const addConfig = (newConfig) => {
setConfigurations([...configurations, newConfig]);
setDisplayModal(false);
};
return (
<Box sx={styles.root}>
{configurations.length !== 0 && (
<ConfigTable
configurations={configurations}
setConfigurations={setConfigurations}
model={model}
/>
)}
<IconButton
onClick={() => setDisplayModal(true)}
sx={styles.addRow}
>
<Add />
</IconButton>
<ModalContainer
show={displayModal}
close={() => setDisplayModal(false)}
>
<Typography variant="h4">Add Test Case</Typography>
<ConfigForm
model={model}
addConfig={addConfig}
close={() => setDisplayModal(false)}
displayModal={displayModal}
/>
</ModalContainer>
</Box>
);
};
export default Configurations;
import { useForm } from "react-hook-form";
import { useEffect } from "react";
import {
MODEL_NAME_KNN,
MODEL_NAME_LR,
MODEL_NAME_RF,
MODEL_NAME_SVM,
} from "@/constants";
import SVMKNN, { defaultValues as SVMKNNDefaultValues } from "./SVMKNN";
import RFLR, { LRDefaultValues, RFDefaultValues } from "./RFLR";
import { Box } from "@mui/material";
const ConfigForm = ({ model, addConfig, close, displayModal }) => {
let defaultValues = SVMKNNDefaultValues;
if (model === MODEL_NAME_RF) defaultValues = RFDefaultValues;
else if (model === MODEL_NAME_LR) defaultValues = LRDefaultValues;
const {
register,
handleSubmit,
control,
formState: { errors },
reset,
} = useForm({
defaultValues,
});
useEffect(() => {
if (displayModal) reset();
}, [displayModal]);
if (model === MODEL_NAME_KNN || model === MODEL_NAME_SVM) {
return (
<SVMKNN
handleSubmit={handleSubmit}
addConfig={addConfig}
register={register}
control={control}
errors={errors}
reset={reset}
close={close}
/>
);
} else if (model === MODEL_NAME_RF || model === MODEL_NAME_LR) {
return (
<RFLR
handleSubmit={handleSubmit}
addConfig={addConfig}
register={register}
errors={errors}
reset={reset}
close={close}
/>
);
} else {
return <Box>unknown</Box>;
}
};
export default ConfigForm;
import commonStyles from "@/styles/commonStyles";
import { TextField } from "@mui/material";
import styles from "./styles";
import FormButtons from "@/components/formButtons/FormButtons";
export const RFDefaultValues = {
threshold_classifier: 0.493399999999838,
max_iter: 50,
time_maximum: 120,
};
export const LRDefaultValues = {
threshold_classifier: 0.491799999999785,
max_iter: 50,
time_maximum: 120,
};
const RFLR = ({ handleSubmit, addConfig, register, errors, reset, close }) => {
const onSubmit = (config) => {
config.threshold_classifier = parseFloat(config.threshold_classifier);
config.max_iter = parseInt(config.max_iter);
config.time_maximum = parseInt(config.time_maximum);
addConfig(config);
};
return (
<form onSubmit={handleSubmit(onSubmit)} style={styles.modalRoot}>
<TextField
sx={commonStyles.text}
label="Name"
type="text"
{...register("name", { required: "Name is required" })}
helperText={errors.name && errors.name.message}
error={errors.name ? true : false}
/>
<TextField
sx={commonStyles.text}
label="Classification Threshold"
type="number"
inputProps={{ step: 0.000000000000001 }}
{...register("threshold_classifier", {
required: "Classification Threshold is required",
min: 0,
})}
helperText={
errors.threshold_classifier &&
errors.threshold_classifier.message
}
error={errors.threshold_classifier ? true : false}
/>
<TextField
sx={commonStyles.text}
label="Maximum Iterations"
type="number"
{...register("max_iter", {
required: "Maximum Iterations is required",
})}
helperText={errors.max_iter && errors.max_iter.message}
error={errors.max_iter ? true : false}
/>
<TextField
sx={commonStyles.text}
label="Maximum Time"
type="number"
{...register("time_maximum", {
required: "Maximum Time is required",
})}
helperText={errors.time_maximum && errors.time_maximum.message}
error={errors.time_maximum ? true : false}
/>
<FormButtons reset={reset} close={close} />
</form>
);
};
export default RFLR;
import commonStyles from "@/styles/commonStyles";
import styles from "./styles";
import { Autocomplete, TextField, Typography } from "@mui/material";
import { Controller } from "react-hook-form";
import { tags } from "../flippingTags";
import FormButtons from "@/components/formButtons/FormButtons";
export const defaultValues = {
sample_prob_decay_factor: 0.2,
flip_prob: 0.5,
};
const SVMKNN = ({
handleSubmit,
addConfig,
register,
errors,
control,
close,
reset,
}) => {
const onAddConfig = (newConfig) => {
if (newConfig.flipping_tags)
newConfig.flipping_tags = JSON.parse(newConfig.flipping_tags);
const { name, ...generator_config } = newConfig;
generator_config["flip_prob"] = parseFloat(
generator_config["flip_prob"]
);
generator_config["sample_prob_decay_factor"] = parseFloat(
generator_config["sample_prob_decay_factor"]
);
const formattedConfig = { name, generator_config };
addConfig(formattedConfig);
};
return (
<form onSubmit={handleSubmit(onAddConfig)} style={styles.modalRoot}>
<TextField
sx={commonStyles.text}
label="Name"
type="text"
{...register("name", { required: "Name is required" })}
helperText={errors.name && errors.name.message}
error={errors.name ? true : false}
/>
<Typography variant="h5">Generator Configurations</Typography>
<TextField
sx={commonStyles.text}
label="Sampling Probability Decay Factor"
type="number"
inputProps={{ step: 0.000000000000001 }}
{...register("sample_prob_decay_factor", {
required: "Sampling Probability Decay Factor is required",
min: 0,
})}
helperText={
errors.sample_prob_decay_factor &&
errors.sample_prob_decay_factor.message
}
error={errors.sample_prob_decay_factor ? true : false}
/>
<TextField
sx={commonStyles.text}
label="Flipping Probability"
type="number"
inputProps={{ step: 0.000000000000001 }}
{...register("flip_prob", {
required: "Flipping Probability is required",
min: 0,
max: 1,
})}
helperText={errors.flip_prob && errors.flip_prob.message}
error={errors.flip_prob ? true : false}
/>
<Controller
name="flipping_tags"
control={control}
rules={{ required: "Flipping Tags are required" }}
render={({ field }) => (
<Autocomplete
{...field}
onChange={(e, val) => {
field.onChange({
target: { value: JSON.stringify(val) },
});
}}
value={field.value ? JSON.parse(field.value) : []}
multiple
options={tags}
renderInput={(params) => (
<TextField
{...params}
label="Flipping Tags"
error={!!errors.flipping_tags}
helperText={errors.flipping_tags?.message}
/>
)}
/>
)}
/>
<FormButtons reset={reset} close={close} />
</form>
);
};
export default SVMKNN;
import { alpha } from "@mui/material";
export default {
root: {
width: "100%",
},
addRow: {
border: (theme) =>
`1px dashed ${alpha(theme.palette.text.primary, 0.5)}`,
textAlign: "center",
width: "100%",
borderRadius: "10px",
},
modalRoot: {
display: "flex",
flexDirection: "column",
},
};
import {
MODEL_NAME_KNN,
MODEL_NAME_LR,
MODEL_NAME_RF,
MODEL_NAME_SVM,
} from "@/constants";
import { Table } from "@mui/material";
import SVMKNN from "./SVMKNN";
import RFLR from "./RFLR";
const ConfigTable = ({ configurations, setConfigurations, model }) => {
const handleDelete = (i) => {
const newConfigs = [...configurations];
newConfigs.splice(i, 1);
setConfigurations(newConfigs);
};
if (model === MODEL_NAME_KNN || model === MODEL_NAME_SVM) {
return (
<SVMKNN
configurations={configurations}
handleDelete={handleDelete}
/>
);
} else if (model === MODEL_NAME_RF || model === MODEL_NAME_LR) {
return (
<RFLR configurations={configurations} handleDelete={handleDelete} />
);
} else {
return <Table></Table>;
}
};
export default ConfigTable;
import { Close } from "@mui/icons-material";
import {
IconButton,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@mui/material";
const RFLR = ({ configurations, handleDelete }) => {
return (
<Table>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Classification Threshold</TableCell>
<TableCell>Maximum Iterations</TableCell>
<TableCell>Maximum Time</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{configurations.map((config, key) => (
<TableRow key={key}>
<TableCell>{config.name}</TableCell>
<TableCell>{config.threshold_classifier}</TableCell>
<TableCell>{config.max_iter}</TableCell>
<TableCell>{config.time_maximum}</TableCell>
<TableCell>
<IconButton
color="error"
onClick={() => handleDelete(key)}
>
<Close />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};
export default RFLR;
import { Close } from "@mui/icons-material";
import {
IconButton,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@mui/material";
const SVMKNN = ({ configurations, handleDelete }) => {
return (
<Table>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Sampling Probability Decay Factor</TableCell>
<TableCell>Flipping Probability</TableCell>
<TableCell>Flipping Tags</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{configurations.map((config, key) => (
<TableRow key={key}>
<TableCell>{config.name}</TableCell>
<TableCell>
{config.generator_config?.sample_prob_decay_factor}
</TableCell>
<TableCell>
{config.generator_config?.flip_prob}
</TableCell>
<TableCell>
{config.generator_config?.flipping_tags.join(", ")}
</TableCell>
<TableCell>
<IconButton
color="error"
onClick={() => handleDelete(key)}
>
<Close />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};
export default SVMKNN;
export const tagHelp = `CC: conjunction, coordinating
& 'n and both but either et for less minus neither nor or plus so
therefore times v. versus vs. whether yet
CD: numeral, cardinal
mid-1890 nine-thirty forty-two one-tenth ten million 0.5 one forty-
seven 1987 twenty '79 zero two 78-degrees eighty-four IX '60s .025
fifteen 271,124 dozen quintillion DM2,000 ...
DT: determiner
all an another any both del each either every half la many much nary
neither no some such that the them these this those
EX: existential there
there
FW: foreign word
gemeinschaft hund ich jeux habeas Haementeria Herr K'ang-si vous
lutihaw alai je jour objets salutaris fille quibusdam pas trop Monte
terram fiche oui corporis ...
IN: preposition or conjunction, subordinating
astride among uppon whether out inside pro despite on by throughout
below within for towards near behind atop around if like until below
next into if beside ...
JJ: adjective or numeral, ordinal
third ill-mannered pre-war regrettable oiled calamitous first separable
ectoplasmic battery-powered participatory fourth still-to-be-named
multilingual multi-disciplinary ...
JJR: adjective, comparative
bleaker braver breezier briefer brighter brisker broader bumper busier
calmer cheaper choosier cleaner clearer closer colder commoner costlier
cozier creamier crunchier cuter ...
JJS: adjective, superlative
calmest cheapest choicest classiest cleanest clearest closest commonest
corniest costliest crassest creepiest crudest cutest darkest deadliest
dearest deepest densest dinkiest ...
LS: list item marker
A A. B B. C C. D E F First G H I J K One SP-44001 SP-44002 SP-44005
SP-44007 Second Third Three Two * a b c d first five four one six three
two
MD: modal auxiliary
can cannot could couldn't dare may might must need ought shall should
shouldn't will would
NN: noun, common, singular or mass
common-carrier cabbage knuckle-duster Casino afghan shed thermostat
investment slide humour falloff slick wind hyena override subhumanity
machinist ...
NNP: noun, proper, singular
Motown Venneboerger Czestochwa Ranzer Conchita Trumplane Christos
Oceanside Escobar Kreisler Sawyer Cougar Yvette Ervin ODI Darryl CTCA
Shannon A.K.C. Meltex Liverpool ...
NNPS: noun, proper, plural
Americans Americas Amharas Amityvilles Amusements Anarcho-Syndicalists
Andalusians Andes Andruses Angels Animals Anthony Antilles Antiques
Apache Apaches Apocrypha ...
NNS: noun, common, plural
undergraduates scotches bric-a-brac products bodyguards facets coasts
divestitures storehouses designs clubs fragrances averages
subjectivists apprehensions muses factory-jobs ...
PDT: pre-determiner
all both half many quite such sure this
POS: genitive marker
' 's
PRP: pronoun, personal
hers herself him himself hisself it itself me myself one oneself ours
ourselves ownself self she thee theirs them themselves they thou thy us
PRP$: pronoun, possessive
her his mine my our ours their thy your
RB: adverb
occasionally unabatingly maddeningly adventurously professedly
stirringly prominently technologically magisterially predominately
swiftly fiscally pitilessly ...
RBR: adverb, comparative
further gloomier grander graver greater grimmer harder harsher
healthier heavier higher however larger later leaner lengthier less-
perfectly lesser lonelier longer louder lower more ...
RBS: adverb, superlative
best biggest bluntest earliest farthest first furthest hardest
heartiest highest largest least less most nearest second tightest worst
RP: particle
aboard about across along apart around aside at away back before behind
by crop down ever fast for forth from go high i.e. in into just later
low more off on open out over per pie raising start teeth that through
under unto up up-pp upon whole with you
SYM: symbol
% & ' '' ''. ) ). * + ,. < = > @ A[fj] U.S U.S.S.R * ** ***
TO: "to" as preposition or infinitive marker
to
UH: interjection
Goodbye Goody Gosh Wow Jeepers Jee-sus Hubba Hey Kee-reist Oops amen
huh howdy uh dammit whammo shucks heck anyways whodunnit honey golly
man baby diddle hush sonuvabitch ...
VB: verb, base form
ask assemble assess assign assume atone attention avoid bake balkanize
bank begin behold believe bend benefit bevel beware bless boil bomb
boost brace break bring broil brush build ...
VBD: verb, past tense
dipped pleaded swiped regummed soaked tidied convened halted registered
cushioned exacted snubbed strode aimed adopted belied figgered
speculated wore appreciated contemplated ...
VBG: verb, present participle or gerund
telegraphing stirring focusing angering judging stalling lactating
hankerin' alleging veering capping approaching traveling besieging
encrypting interrupting erasing wincing ...
VBN: verb, past participle
multihulled dilapidated aerosolized chaired languished panelized used
experimented flourished imitated reunifed factored condensed sheared
unsettled primed dubbed desired ...
VBP: verb, present tense, not 3rd person singular
predominate wrap resort sue twist spill cure lengthen brush terminate
appear tend stray glisten obtain comprise detest tease attract
emphasize mold postpone sever return wag ...
VBZ: verb, present tense, 3rd person singular
bases reconstructs marks mixes displeases seals carps weaves snatches
slumps stretches authorizes smolders pictures emerges stockpiles
seduces fizzes uses bolsters slaps speaks pleads ...
WDT: WH-determiner
that what whatever which whichever
WP: WH-pronoun
that what whatever whatsoever which who whom whosoever
WP$: WH-pronoun, possessive
whose
WRB: Wh-adverb
how however whence whenever where whereby whereever wherein whereof why`;
export const tags = [
"CC",
"CD",
"DT",
"EX",
"FW",
"IN",
"JJ",
"JJR",
"JJS",
"LS",
"MD",
"NN",
"NNP",
"NNPS",
"NNS",
"PDT",
"POS",
"PRP",
"PRP$",
"RB",
"RBR",
"RBS",
"RP",
"SYM",
"TO",
"UH",
"VB",
"VBD",
"VBG",
"VBN",
"VBP",
"VBZ",
"WDT",
"WP",
"WP$",
"WRB",
];
export default {
close: {
position: "absolute",
right: 0,
top: 0,
},
};
import Textarea from "@/components/Textarea";
import { Box, Typography } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { ThumbDownOffAlt, ThumbUpOffAlt } from "@mui/icons-material";
import styles from "./styles";
import commonStyles from "@/styles/commonStyles";
import { useContext, useRef, useState } from "react";
import { getSentiment } from "@/functions/api";
import { ModalContext } from "@/providers/modalProvider/ModalProvider";
const Evaluation = ({ model }) => {
const { setNotification } = useContext(ModalContext);
const textareaRef = useRef();
const [loading, setLoading] = useState(false);
const [prompt, setPrompt] = useState("");
const [result, setResult] = useState();
const evaluationHandler = () => {
const prompt = textareaRef.current.value;
if (prompt !== "" && model) {
setLoading(true);
setPrompt(prompt);
setResult();
getSentiment(model, prompt)
.then((sentiment) => setResult(sentiment))
.catch(setNotification)
.finally(() => {
setLoading(false);
});
}
};
const status = result && result.prediction === "positive";
return (
<Box sx={commonStyles.sectionContainer}>
<Typography variant="h3">Prompt Evaluation</Typography>
<Textarea
ref={textareaRef}
placeholder="Prompt"
sx={commonStyles.text}
/>
<LoadingButton
loading={loading}
sx={commonStyles.btn}
onClick={evaluationHandler}
>
Evaluate
</LoadingButton>
{prompt !== "" && (
<Box sx={commonStyles.outputContainer(status)}>
<Typography>{prompt}</Typography>
{status !== undefined && (
<Box sx={styles.iconWrapper}>
{status ? <ThumbUpOffAlt /> : <ThumbDownOffAlt />}
<Typography>{result.score}</Typography>
</Box>
)}
</Box>
)}
</Box>
);
};
export default Evaluation;
export default {
iconWrapper: {
width: "100%",
textAlign: "center",
},
};
import {
MODEL_NAME_KNN,
MODEL_NAME_LR,
MODEL_NAME_RF,
MODEL_NAME_SVM,
} from "@/constants";
import commonStyles from "@/styles/commonStyles";
import { Autocomplete, Box, TextField } from "@mui/material";
const ModelSelection = ({ setModel }) => {
const models = [
{ label: "K Nearest Neighbour Model", value: MODEL_NAME_KNN },
{ label: "Logistic Regression Model", value: MODEL_NAME_LR },
{ label: "Random Forest Model", value: MODEL_NAME_RF },
{ label: "Support Vector Machine Model", value: MODEL_NAME_SVM },
];
return (
<Box sx={{ width: "300px" }}>
<Autocomplete
disablePortal
onChange={(e, obj) => (obj ? setModel(obj.value) : setModel())}
options={models}
sx={commonStyles.autocomplete}
isOptionEqualToValue={(obj) => obj.label}
size="small"
renderInput={(params) => (
<TextField {...params} label="Models" />
)}
/>
</Box>
);
};
export default ModelSelection;
import { Box, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import commonStyles from "@/styles/commonStyles";
const Stats = ({ model }) => {
const [text, setText] = useState("");
useEffect(() => {
const updateText = async () => {
const response = await fetch(
`/evaluations/${model}/evaluation.txt`
);
if (response.status == 200) {
const text = await response.text();
setText(text);
}
};
updateText();
}, [model]);
return (
<Box sx={commonStyles.sectionContainer}>
<Typography variant="h3">Test Set Performance</Typography>
<Typography variant="h4">Report</Typography>
<Typography variant="body1" sx={commonStyles.codeBlock}>
{text}
</Typography>
<Typography variant="h4">Visualizations</Typography>
<img src={`/evaluations/${model}/evaluation.jpg`} />
</Box>
);
};
export default Stats;
import { lambdaClient } from "@/clients/aws";
import { AWS_LAMBDA_NAME, STATUS_CODE_MAP } from "@/constants";
import { InvokeCommand } from "@aws-sdk/client-lambda";
export const getSentiment = async (model_name, prompt) => {
const payload = {
task: "evaluation",
payload: { model_name, texts: [prompt] },
};
const command = new InvokeCommand({
FunctionName: AWS_LAMBDA_NAME,
Payload: JSON.stringify(payload),
});
const { Payload } = await lambdaClient.send(command);
const result = Buffer.from(Payload).toString();
const parsedResult = JSON.parse(result);
if (parsedResult.status === 200) {
const response = {
score: parsedResult.body.scores[0],
prediction: parsedResult.body.predictions[0],
};
return response;
} else {
console.error(parsedResult.body);
const bubble = STATUS_CODE_MAP[parsedResult.status];
throw bubble;
}
};
export const startTestCase = async ({
model_name,
prompt,
variations,
configurations,
}) => {
const payload = {
task: "analysis",
payload: { model_name, prompt, variations, configurations },
};
const command = new InvokeCommand({
FunctionName: AWS_LAMBDA_NAME,
Payload: JSON.stringify(payload),
});
const { Payload } = await lambdaClient.send(command);
const result = Buffer.from(Payload).toString();
const parsedResult = JSON.parse(result);
if (parsedResult.status === 200) {
return parsedResult.body;
} else {
console.error(parsedResult.body);
const bubble = STATUS_CODE_MAP[parsedResult.status];
throw bubble;
}
};
export const deepMerge = (...objects) => {
const isObject = (obj) => obj && typeof obj === "object";
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach((key) => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
} else {
prev[key] = oVal;
}
});
return prev;
}, {});
};
import ModalProvider from "@/providers/modalProvider/ModalProvider";
import "@/styles/globals.css";
import { darkTheme, lightTheme } from "@/styles/themes";
import { ThemeProvider } from "@mui/material";
import { useEffect, useState } from "react";
export default function App({ Component, pageProps }) {
const [theme, setTheme] = useState(lightTheme);
useEffect(() => {
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
setTheme(darkTheme);
}
}, []);
return (
<ThemeProvider theme={theme}>
<ModalProvider>
<Component {...pageProps} />
</ModalProvider>
</ThemeProvider>
);
}
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
import Head from "next/head";
import { Box } from "@mui/material";
import pageStyles from "@/styles/pageStyles/index";
import Evaluation from "@/containers/evaluation/Evaluation";
import { useState } from "react";
import Stats from "@/containers/stats/Stats";
import ModelSelection from "@/containers/modelSelection/ModelSelection";
import Analysis from "@/containers/analysis/Analysis";
import { MODEL_NAME_LR } from "@/constants";
export default function Home() {
const [model, setModel] = useState();
return (
<>
<Head>
<title>XAI</title>
<meta
name="description"
content="An implementation of explainable AI algorithms"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<Box sx={pageStyles.main}>
<ModelSelection setModel={setModel} />
{model && (
<>
<Evaluation model={model} />
{/* <Stats model={model} /> */}
<Analysis model={model} />
</>
)}
</Box>
</>
);
}
import ModalContainer from "@/components/modalContainer/ModalContainer";
import { Box, Button, IconButton, Typography, useTheme } from "@mui/material";
import { createContext, useState } from "react";
import styles from "./styles";
import { Close, ErrorOutline } from "@mui/icons-material";
export const ModalContext = createContext();
const ModalProvider = ({ children }) => {
const theme = useTheme();
const [modalContent, setModalContent] = useState();
const [notificationText, setNotificationText] = useState();
const [notificationActive, setNotificationActive] = useState(false);
const displayModal = ({
heading,
body,
state,
closeBtnText,
closeBtnProps,
closeBtnExtraActions,
extraBtnTexts,
extraBtnActions,
}) => {
let color = undefined;
let headIcon = undefined;
if (state === "error") {
color = theme.palette.error.main;
headIcon = (
<ErrorOutline
color={"error"}
fontSize="inherit"
sx={styles.headIcon}
/>
);
}
const extraBtns = [];
if (extraBtnTexts) {
for (let i = 0; i < extraBtnTexts.length; i++) {
const text = extraBtnTexts[i];
const action = extraBtnActions[i];
extraBtns.push({ text, action });
}
}
let [sx, rest] = [undefined, undefined];
if (closeBtnProps) {
const { sxN, ...restN } = closeBtnProps;
sx = sxN;
rest = restN;
}
setModalContent({
headIcon,
heading,
body,
color,
closeBtnText,
closeBtnProps: rest,
closeBtnSx: sx,
closeBtnExtraActions,
extraBtns,
});
};
const setNotification = (text) => {
setNotificationText(text);
setNotificationActive(true);
setTimeout(() => {
setNotificationActive(false);
}, 3000);
};
const closeModal = () => {
modalContent.closeBtnExtraActions &&
modalContent.closeBtnExtraActions();
setModalContent();
};
return (
<ModalContext.Provider
value={{ displayModal, setNotification, closeModal }}
>
<Box sx={styles.root}>
{children}
{modalContent && (
<ModalContainer show={true} close={closeModal}>
<Typography
sx={styles.heading(modalContent.color)}
variant="h3"
>
{modalContent.headIcon} {modalContent.heading}
</Typography>
{typeof modalContent.body === "string" ? (
<Typography sx={styles.body} variant="body1">
{modalContent.body}
</Typography>
) : (
modalContent.body
)}
<Box sx={styles.btnContainer}>
{modalContent.extraBtns.map((btn) => (
<Button
key={btn.text}
sx={styles.btn}
variant="outlined"
onClick={(e) => {
btn.action(e);
closeModal();
}}
>
{btn.text}
</Button>
))}
<Button
sx={{
...styles.btn,
...modalContent.closeBtnSx,
}}
{...modalContent.closeBtnProps}
variant="outlined"
onClick={closeModal}
>
{modalContent.closeBtnText
? modalContent.closeBtnText
: "Close"}
</Button>
</Box>
</ModalContainer>
)}
<Box sx={styles.notification(notificationActive)}>
<Typography variant="body1">{notificationText}</Typography>
<IconButton
sx={styles.cross}
onClick={() => setNotificationActive(false)}
>
<Close />
</IconButton>
</Box>
</Box>
</ModalContext.Provider>
);
};
export default ModalProvider;
import common from "@/styles/commonStyles";
import { alpha } from "@mui/material";
export default {
root: {
position: "relative",
overflow: "hidden",
},
heading: (color) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: color && alpha(color, 0.2),
}),
body: {
padding: "20px 0px",
},
headIcon: {
margin: "0px 0.7rem",
},
btnContainer: {
display: "flex",
justifyContent: "center",
},
btn: {
margin: "0px 5px",
},
notification: (active) => ({
display: "flex",
alignItems: "center",
position: "absolute",
bottom: active ? "0px" : "-70px",
left: "0px",
backgroundColor: (theme) => theme.palette.background.paper,
padding: "10px 20px",
paddingRight: "5px",
margin: "5px",
borderRadius: "5px",
transition: "0.3s",
zIndex: 1,
boxShadow: (theme) => theme.shadows[10],
}),
cross: {
...common.iconBtn,
margin: "7px",
padding: "2px",
},
};
import { alpha } from "@mui/material";
export default {
btn: {
margin: (theme) => theme.spacing(2),
},
btnContainer: {
textAlign: "center",
".MuiButton-root": {
margin: (theme) => theme.spacing(1),
},
},
autocomplete: {
margin: (theme) => `${theme.spacing(2)} 0`,
width: "100%",
},
text: {
margin: (theme) => `${theme.spacing(2)} 0`,
},
codeBlock: {
fontFamily: "monospace",
whiteSpace: "pre",
overflowX: "scroll",
},
sectionContainer: {
display: "flex",
flexDirection: "column",
alignItems: "center",
border: (theme) => `1px solid ${theme.palette.text.primary}`,
padding: "30px",
margin: "10px",
width: "100%",
borderRadius: "10px",
".MuiTypography-h3": {
margin: "15px 0px",
width: "100%",
},
".MuiTypography-h4": {
margin: "10px 0px",
width: "100%",
},
},
outputContainer: (ok) => ({
position: "relative",
border: (theme) =>
`1px solid ${
ok === undefined
? theme.palette.text.icon
: ok
? theme.palette.success.main
: theme.palette.error.main
}`,
width: "100%",
padding: (theme) => theme.spacing(2),
borderRadius: (theme) => theme.spacing(1),
color: (theme) =>
ok === undefined
? theme.palette.text.icon
: ok
? theme.palette.success.main
: theme.palette.error.main,
}),
backdrop: (active) => ({
display: active ? "flex" : "none",
justifyContent: "center",
alignItems: "center",
position: "fixed",
width: "100vw",
height: "100vh",
left: 0,
top: 0,
backgroundColor: (theme) =>
alpha(theme.palette.background.default, 0.5),
zIndex: 1,
}),
};
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#1e72a333 0deg,
#3890e933 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(
rgba(1, 175, 255, 0.4),
rgba(1, 65, 255, 0)
);
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 175, 255, 0.4)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
This diff is collapsed.
export default {
typography: {
h3: {
fontSize: "2rem",
},
h4: {
fontSize: "1.5rem",
},
},
};
This diff is collapsed.
import { createTheme } from "@mui/material";
import darkThemeConfig from "./darkThemeConfig";
import lightThemeConfig from "./lightThemeConfig";
export const darkTheme = createTheme(darkThemeConfig);
export const lightTheme = createTheme(lightThemeConfig);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment