Spaces:
Sleeping
Sleeping
Commit
·
b9604a1
0
Parent(s):
Initial commit for TREAT project
Browse files- README.md +198 -0
- app/__init__.py +21 -0
- app/routes.py +24 -0
- app/run.py +12 -0
- gradio_app.py +8 -0
- model/model.py +223 -0
- requirements.txt +5 -0
- static/css/style.css +222 -0
- static/images/android-chrome-192x192.png +0 -0
- static/images/apple-touch-icon.png +0 -0
- static/images/favicon-16x16.png +0 -0
- static/images/favicon-32x32.png +0 -0
- static/images/favicon.ico +0 -0
- static/images/readme-images/Request-Access-Page.png +0 -0
- static/images/site.webmanifest +14 -0
- static/js/app.js +36 -0
- templates/index.html +39 -0
- utils/utils.py +38 -0
README.md
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+

|
2 |
+
|
3 |
+
<h1 align="center">
|
4 |
+
Trigger Recognition for Enjoyable and Appropriate Television
|
5 |
+
</h1>
|
6 |
+
|
7 |
+
<p align="center">
|
8 |
+
<img src="https://img.shields.io/static/v1?label=Kuberwastaken&message=TREAT&color=blue&logo=github" alt="Kuberwastaken - TREAT">
|
9 |
+
<img src="https://img.shields.io/badge/version-2.0-blue" alt="Version 2.0">
|
10 |
+
<img src="https://img.shields.io/badge/License-Apache_2.0-blue" alt="License Apache 2.0">
|
11 |
+
</p>
|
12 |
+
|
13 |
+
I was tired of getting grossed out watching unexpected scenes in movies and TV and losing my appetite, that's why I created TREAT.
|
14 |
+
|
15 |
+
The goal of this project is to empower viewers by forewarning them about potential triggers in the content they watch, making the viewing experience more enjoyable, inclusive, and appropriate for everyone.
|
16 |
+
|
17 |
+
TREAT is a web application that uses natural language processing to analyze movie and TV show scripts, identifying potential triggers to help viewers make informed choices.
|
18 |
+
|
19 |
+
## Installation Instructions
|
20 |
+
### Prerequisites
|
21 |
+
- Star the Repository to Show Your Support.
|
22 |
+
- Clone the Repository to Your Local Machine:
|
23 |
+
|
24 |
+
```bash
|
25 |
+
git clone https://github.com/Kuberwastaken/TREAT.git
|
26 |
+
```
|
27 |
+
|
28 |
+
### Hugging Face Login Instructions for Llama-3.2-1B Model
|
29 |
+
To use the [Llama-3.2-1B model](https://huggingface.co/meta-llama/Llama-3.2-1B), which provides a 35% increase in accuracy and efficiency over the [previous model](https://github.com/Kuberwastaken/TREAT-CS50), you must request access for it, as it is a gated model.
|
30 |
+
|
31 |
+

|
32 |
+
|
33 |
+
1. **Login to Hugging Face in Your Environment:**
|
34 |
+
|
35 |
+
Run the following command in your terminal:
|
36 |
+
|
37 |
+
```bash
|
38 |
+
huggingface-cli login
|
39 |
+
```
|
40 |
+
|
41 |
+
Enter your Hugging Face access token when prompted.
|
42 |
+
|
43 |
+
2. **Download the Llama-3.2-1B Model:**
|
44 |
+
|
45 |
+
The model will be downloaded automatically when running the script analysis for the first time, provided you have received access.
|
46 |
+
|
47 |
+
### Environment Setup
|
48 |
+
To set up the development environment, you will need to create a virtual environment and install the necessary dependencies.
|
49 |
+
|
50 |
+
1. Create a Virtual Environment:
|
51 |
+
|
52 |
+
```bash
|
53 |
+
python3 -m venv treat-env
|
54 |
+
```
|
55 |
+
|
56 |
+
2. Activate the Virtual Environment:
|
57 |
+
|
58 |
+
```bash
|
59 |
+
source treat-env/bin/activate # On Unix or MacOS
|
60 |
+
treat-env\Scriptsctivate # On Windows
|
61 |
+
```
|
62 |
+
|
63 |
+
3. Install Dependencies:
|
64 |
+
|
65 |
+
Navigate to the project directory and run:
|
66 |
+
|
67 |
+
```bash
|
68 |
+
pip install -r requirements.txt
|
69 |
+
```
|
70 |
+
|
71 |
+
## Project Usage
|
72 |
+
1. **Start the Flask Server:**
|
73 |
+
|
74 |
+
```bash
|
75 |
+
python run.py
|
76 |
+
```
|
77 |
+
|
78 |
+
2. **Open Your Browser:**
|
79 |
+
|
80 |
+
Navigate to `http://127.0.0.1:5000` to access the TREAT web interface.
|
81 |
+
|
82 |
+
3. **Analyze Scripts:**
|
83 |
+
|
84 |
+
You can manually enter a script in the provided text area and click "Analyze Script."
|
85 |
+
|
86 |
+
## File Descriptions
|
87 |
+
- **app.py:** The main Flask application file that handles routing.
|
88 |
+
|
89 |
+
- **app/routes.py:** Contains the Flask routes for handling script uploads.
|
90 |
+
|
91 |
+
- **app/model.py:** Includes the script analysis functions using the Llama-3.2-1B model.
|
92 |
+
|
93 |
+
- **templates/index.html:** The main HTML file for the web interface.
|
94 |
+
|
95 |
+
- **static/css/style.css:** Custom CSS for styling the web interface.
|
96 |
+
|
97 |
+
- **static/js/app.js:** JavaScript for handling client-side interactions.
|
98 |
+
|
99 |
+
## Types of Triggers Detected
|
100 |
+
The TREAT application focuses on identifying a variety of potential triggers in scripts, including but not limited to:
|
101 |
+
|
102 |
+
- **Violence:** Scenes of physical aggression or harm.
|
103 |
+
|
104 |
+
- **Self-Harm:** Depictions of self-inflicted injury.
|
105 |
+
|
106 |
+
- **Death:** Depictions of death or dying characters.
|
107 |
+
|
108 |
+
- **Sexual Content:** Any depiction or mention of sexual activity, intimacy, or behavior.
|
109 |
+
|
110 |
+
- **Sexual Abuse:** Instances of sexual violence or exploitation.
|
111 |
+
|
112 |
+
- **Gun Use:** Depictions of firearms and their usage.
|
113 |
+
|
114 |
+
- **Gore:** Graphic depiction of injury, blood, or dismemberment.
|
115 |
+
|
116 |
+
- **Vomit:** Depictions of vomiting or nausea-inducing content.
|
117 |
+
|
118 |
+
- **Mental Health Issues:** Depictions of mental health struggles, including anxiety, depression, or disorders.
|
119 |
+
|
120 |
+
- **Animal Cruelty:** Depictions of harm or abuse towards animals.
|
121 |
+
|
122 |
+
These categories help address a very real-world problem by forewarning viewers about potentially distressing content, enhancing their viewing experience.
|
123 |
+
|
124 |
+
Adding new categories is as simple as specifying a new category under model.py and utils.py
|
125 |
+
|
126 |
+
## Design Choices
|
127 |
+
|
128 |
+
- **Inspiration:** I aimed for a simple and intuitive user experience, focusing on simplicity and ease of use. This decision stemmed from the need to create a tool that is easy to navigate for all users, regardless of background or age.
|
129 |
+
|
130 |
+
- **Theme and Color Scheme:** The chosen theme and color scheme create a visually appealing and engaging environment. The chocolate and sweets theme is intended to stick to the TREAT theme and make the experience enjoyable and pleasant.
|
131 |
+
|
132 |
+
- **Script Analysis:** The Llama-3.2-1B model by Meta was chosen for its increased accuracy (about 35% better) compared to the prior FLAN-T5 version. The decision was based on its ability to provide precise trigger recognition while being open source. As a new and advanced model, it enhances the script analysis capabilities significantly.
|
133 |
+
|
134 |
+
## How to Edit Sensitivity Settings or Prompts
|
135 |
+
|
136 |
+
To adjust the sensitivity or modify the prompts used during script analysis, you can edit the `model.py` file located under the `treat/app` directory. This file contains various parameters and settings that control how triggers are identified and how the model processes the script. Here’s how you can adjust key settings:
|
137 |
+
|
138 |
+
### Key Parameters to Edit:
|
139 |
+
|
140 |
+
|
141 |
+
- **max_new_tokens**: Controls the maximum number of tokens (words or characters) the model will generate in response to a prompt. A higher number can lead to more detailed results but may also increase processing time.
|
142 |
+
|
143 |
+
- **temperature**: Controls the randomness of the model's responses. A value of 1.0 means standard behavior, while lower values (e.g., 0.2) make the model's output more deterministic and focused. Higher values (e.g., 1.5) allow for more creative or varied responses.
|
144 |
+
|
145 |
+
- **top_p** (Nucleus Sampling): Controls how many of the top predicted tokens are considered during text generation. A value of 0.9 means the model will only consider the top 90% of predictions, cutting off the least likely options. A lower value can make the output more coherent but less creative.
|
146 |
+
|
147 |
+
To adjust these, look for `max_new_tokens`, `temperature`, and `top_p` in the `model.py` file and set them to your desired values. For example:
|
148 |
+
```python
|
149 |
+
max_new_tokens = 10 # Set the maximum number of tokens
|
150 |
+
temperature = 0.7 # A moderate level for balanced responses
|
151 |
+
top_p = 0.9 # Adjust the diversity of the output
|
152 |
+
```
|
153 |
+
|
154 |
+
### Chunk Size and Overlap:
|
155 |
+
To handle long scripts effectively, the text is divided into chunks before being analyzed. Adjusting the chunk size and overlap helps control how much of the script is processed at once and ensures that the model doesn't miss context between chunks.
|
156 |
+
|
157 |
+
- **chunk_size**: This parameter defines the length of each chunk in tokens. A larger chunk size can help the model capture more context, but may result in higher memory usage and processing time. A smaller chunk size may process faster but could lose context.
|
158 |
+
|
159 |
+
- **overlap**: This parameter defines the number of overlapping tokens between consecutive chunks. Adding overlap ensures that context from the end of one chunk is carried over to the next chunk, preventing the model from losing important information.
|
160 |
+
|
161 |
+
To adjust these, look for `chunk_size` and `overlap` in `model.py` and set them to your desired values:
|
162 |
+
```python
|
163 |
+
chunk_size = 1000 # Set the length of each chunk (tokens)
|
164 |
+
overlap = 200 # Set the number of overlapping tokens between chunks
|
165 |
+
```
|
166 |
+
|
167 |
+
### Adjusting Prompts:
|
168 |
+
To modify the types of triggers detected by the model, you can edit the prompts under the `trigger_categories` section in `model.py`. This section allows you to adjust how the model recognizes various types of content in the script. Simply modify the prompts to suit your needs.
|
169 |
+
|
170 |
+
### Summary of Editable Parameters:
|
171 |
+
- **max_new_tokens, temperature, top_p**: Control the length, randomness, and diversity of the model's output.
|
172 |
+
- **chunk_size, overlap**: Control how the script is divided into chunks and how context is maintained between chunks.
|
173 |
+
- **trigger_categories**: Adjust the prompts to change how triggers are identified in the script.
|
174 |
+
|
175 |
+
|
176 |
+
## To-Do List
|
177 |
+
- Integration with an API to Directly Search Scripts by Name of Movies/Shows
|
178 |
+
|
179 |
+
- Introduce multiple themes to allow users to customize the appearance of the application according to their preferences
|
180 |
+
|
181 |
+
- Increasing speed and efficiency
|
182 |
+
|
183 |
+
- Potentially host this online
|
184 |
+
|
185 |
+
- Make the application mobile-friendly
|
186 |
+
|
187 |
+
## Open Source Contribution
|
188 |
+
This repository is completely open source and free to contribute. I intend to keep this project alive and evolve it into a tool that's extremely usable for all. Contributions are welcome and highly encouraged to add new features, improve the user interface, or enhance the script analysis.
|
189 |
+
|
190 |
+
## Acknowledgements
|
191 |
+
I would like to thank:
|
192 |
+
|
193 |
+
- Meta AI: For developing and allowing me access to the Llama-3.2-1B model, a very critical component of this project.
|
194 |
+
|
195 |
+
- Parasite (2019): For that unexpected jumpscare that ruined my appetite and ultimately inspired this project.
|
196 |
+
|
197 |
+
## License
|
198 |
+
This project is licensed under the [Apache 2.0 License](https://github.com/Kuberwastaken/TREAT/blob/main/LICENSE).
|
app/__init__.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask
|
2 |
+
from flask_cors import CORS
|
3 |
+
import os
|
4 |
+
|
5 |
+
# Get the absolute path for the template and static folders
|
6 |
+
base_dir = os.path.abspath(os.path.dirname(__file__))
|
7 |
+
template_dir = os.path.join(base_dir, "../../templates") # Path to the templates folder
|
8 |
+
static_dir = os.path.join(base_dir, "../../static") # Path to the static folder
|
9 |
+
|
10 |
+
# Create an instance of the Flask class with the specified template and static folders
|
11 |
+
app = Flask(
|
12 |
+
__name__,
|
13 |
+
template_folder=template_dir,
|
14 |
+
static_folder=static_dir
|
15 |
+
)
|
16 |
+
|
17 |
+
# Enable Cross-Origin Resource Sharing (CORS) for the app
|
18 |
+
CORS(app)
|
19 |
+
|
20 |
+
# Import routes after initializing the Flask app to avoid circular import issues
|
21 |
+
from app import routes
|
app/routes.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, request, jsonify, render_template
|
2 |
+
from app import app
|
3 |
+
from app.model import analyze_script
|
4 |
+
|
5 |
+
# Define the home route which renders the index.html template
|
6 |
+
@app.route('/')
|
7 |
+
def home():
|
8 |
+
return render_template('index.html')
|
9 |
+
|
10 |
+
# Define the upload route to handle POST requests for script analysis
|
11 |
+
@app.route('/upload', methods=['POST'])
|
12 |
+
def upload_script():
|
13 |
+
try:
|
14 |
+
# Get the JSON data from the request
|
15 |
+
data = request.get_json()
|
16 |
+
# Extract the text content from the JSON data
|
17 |
+
content = data.get('text', '')
|
18 |
+
# Analyze the script for triggers
|
19 |
+
triggers = analyze_script(content)
|
20 |
+
# Return the triggers in a JSON response
|
21 |
+
return jsonify({"triggers": triggers})
|
22 |
+
except Exception as e:
|
23 |
+
# Handle any exceptions and return an error message
|
24 |
+
return jsonify({"error": str(e)}), 500
|
app/run.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from os.path import dirname, abspath
|
3 |
+
|
4 |
+
# Add the directory of the 'treat' folder to the system path
|
5 |
+
sys.path.append(abspath(dirname(__file__)) + "/treat")
|
6 |
+
|
7 |
+
# Import the Flask app from the app module
|
8 |
+
from app import app
|
9 |
+
|
10 |
+
if __name__ == '__main__':
|
11 |
+
# Run the Flask app with debug mode enabled
|
12 |
+
app.run(debug=True)
|
gradio_app.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from model.model import get_detailed_analysis
|
3 |
+
|
4 |
+
def analyze_script(script):
|
5 |
+
return get_detailed_analysis(script)
|
6 |
+
|
7 |
+
iface = gr.Interface(fn=analyze_script, inputs="text", outputs="json")
|
8 |
+
iface.launch()
|
model/model.py
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
2 |
+
import torch
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
+
def analyze_script(script):
|
6 |
+
# Starting the script analysis
|
7 |
+
print("\n=== Starting Analysis ===")
|
8 |
+
print(f"Time: {datetime.now()}") # Outputting the current timestamp
|
9 |
+
print("Loading model and tokenizer...")
|
10 |
+
|
11 |
+
try:
|
12 |
+
# Load the tokenizer and model, selecting the appropriate device (CPU or CUDA)
|
13 |
+
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B", use_fast=True)
|
14 |
+
device = "cuda" if torch.cuda.is_available() else "cpu" # Use CUDA if available, else use CPU
|
15 |
+
print(f"Using device: {device}")
|
16 |
+
|
17 |
+
model = AutoModelForCausalLM.from_pretrained(
|
18 |
+
"meta-llama/Llama-3.2-1B",
|
19 |
+
torch_dtype=torch.float16 if device == "cuda" else torch.float32, # Use 16-bit precision for CUDA, 32-bit for CPU
|
20 |
+
device_map="auto" # Automatically map model to available device
|
21 |
+
)
|
22 |
+
print("Model loaded successfully")
|
23 |
+
|
24 |
+
# Define trigger categories with their descriptions
|
25 |
+
trigger_categories = {
|
26 |
+
"Violence": {
|
27 |
+
"mapped_name": "Violence",
|
28 |
+
"description": (
|
29 |
+
"Any act involving physical force or aggression intended to cause harm, injury, or death to a person, animal, or object. "
|
30 |
+
"Includes direct physical confrontations (e.g., fights, beatings, or assaults), implied violence (e.g., very graphical threats or descriptions of injuries), "
|
31 |
+
"or large-scale events like wars, riots, or violent protests."
|
32 |
+
)
|
33 |
+
},
|
34 |
+
"Death": {
|
35 |
+
"mapped_name": "Death References",
|
36 |
+
"description": (
|
37 |
+
"Any mention, implication, or depiction of the loss of life, including direct deaths of characters, including mentions of deceased individuals, "
|
38 |
+
"or abstract references to mortality (e.g., 'facing the end' or 'gone forever'). This also covers depictions of funerals, mourning, "
|
39 |
+
"grieving, or any dialogue that centers around death, do not take metaphors into context that don't actually lead to death."
|
40 |
+
)
|
41 |
+
},
|
42 |
+
"Substance Use": {
|
43 |
+
"mapped_name": "Substance Use",
|
44 |
+
"description": (
|
45 |
+
"Any explicit or implied reference to the consumption, misuse, or abuse of drugs, alcohol, or other intoxicating substances. "
|
46 |
+
"Includes scenes of drinking, smoking, or drug use, whether recreational or addictive. May also cover references to withdrawal symptoms, "
|
47 |
+
"rehabilitation, or substance-related paraphernalia (e.g., needles, bottles, pipes)."
|
48 |
+
)
|
49 |
+
},
|
50 |
+
"Gore": {
|
51 |
+
"mapped_name": "Gore",
|
52 |
+
"description": (
|
53 |
+
"Extremely detailed and graphic depictions of highly severe physical injuries, mutilation, or extreme bodily harm, often accompanied by descriptions of heavy blood, exposed organs, "
|
54 |
+
"or dismemberment. This includes war scenes with severe casualties, horror scenarios involving grotesque creatures, or medical procedures depicted with excessive detail."
|
55 |
+
"only answer yes if you're completely certain."
|
56 |
+
)
|
57 |
+
},
|
58 |
+
"Vomit": {
|
59 |
+
"mapped_name": "Vomit",
|
60 |
+
"description": (
|
61 |
+
"Any explicit reference to vomiting, whether directly described, implied, or depicted. This includes detailed sounds, visual descriptions, mentions of nausea explicitly leading to vomiting, or any aftermath involving vomit."
|
62 |
+
"Respond 'yes' only if the scene unambiguously and clearly involves vomiting, with no room for doubt."
|
63 |
+
)
|
64 |
+
},
|
65 |
+
"Sexual Content": {
|
66 |
+
"mapped_name": "Sexual Content",
|
67 |
+
"description": (
|
68 |
+
"Any depiction or mention of sexual activity, intimacy, or sexual behavior, ranging from implied scenes to explicit descriptions. "
|
69 |
+
"This includes romantic encounters, physical descriptions of characters in a sexual context, sexual dialogue, or references to sexual themes (e.g., harassment, innuendos)."
|
70 |
+
)
|
71 |
+
},
|
72 |
+
"Sexual Abuse": {
|
73 |
+
"mapped_name": "Sexual Abuse",
|
74 |
+
"description": (
|
75 |
+
"Any form of non-consensual sexual act, behavior, or interaction, involving coercion, manipulation, or physical force. "
|
76 |
+
"This includes incidents of sexual assault, molestation, exploitation, harassment, and any acts where an individual is subjected to sexual acts against their will or without their consent. "
|
77 |
+
"It also covers discussions or depictions of the aftermath of such abuse, such as trauma, emotional distress, legal proceedings, or therapy. "
|
78 |
+
"References to inappropriate sexual advances, groping, or any other form of sexual misconduct are also included, as well as the psychological and emotional impact on survivors. "
|
79 |
+
"Scenes where individuals are placed in sexually compromising situations, even if not directly acted upon, may also fall under this category."
|
80 |
+
"only answer yes if you're completely certain of it's presence."
|
81 |
+
)
|
82 |
+
},
|
83 |
+
"Self-Harm": {
|
84 |
+
"mapped_name": "Self-Harm",
|
85 |
+
"description": (
|
86 |
+
"Any mention or depiction of behaviors where an individual intentionally causes harm to themselves. This includes cutting, burning, or other forms of physical injury, "
|
87 |
+
"as well as suicidal ideation, suicide attempts, or discussions of self-destructive thoughts and actions. References to scars, bruises, or other lasting signs of self-harm are also included."
|
88 |
+
"only answer yes if you're completely certain."
|
89 |
+
)
|
90 |
+
},
|
91 |
+
"Gun Use": {
|
92 |
+
"mapped_name": "Gun Use",
|
93 |
+
"description": (
|
94 |
+
"Any explicit or implied mention of firearms being handled, fired, or used in a threatening manner. This includes scenes of gun violence, references to shootings, "
|
95 |
+
"gun-related accidents, or the presence of firearms in a tense or dangerous context (e.g., holstered weapons during an argument)."
|
96 |
+
)
|
97 |
+
},
|
98 |
+
"Animal Cruelty": {
|
99 |
+
"mapped_name": "Animal Cruelty",
|
100 |
+
"description": (
|
101 |
+
"Any act of harm or abuse toward animals, whether intentional or accidental. This includes physical abuse (e.g., hitting, injuring, or killing animals), "
|
102 |
+
"mental or emotional mistreatment (e.g., starvation, isolation), and scenes where animals are subjected to pain or suffering for human entertainment or experimentation."
|
103 |
+
"Respond 'yes' only if the scene unambiguously and clearly involves Animal Cruelty, with no room for doubt"
|
104 |
+
)
|
105 |
+
},
|
106 |
+
"Mental Health Issues": {
|
107 |
+
"mapped_name": "Mental Health Issues",
|
108 |
+
"description": (
|
109 |
+
"Any reference to mental health struggles, disorders, or psychological distress. This includes mentions of depression, anxiety, PTSD, bipolar disorder, schizophrenia, "
|
110 |
+
"or other conditions. Scenes depicting destructive coping mechanisms are also included."
|
111 |
+
"like a character expressing feelings of worthlessness, hopelessness, or detachment from reality."
|
112 |
+
)
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
print("\nProcessing text...") # Output indicating the text is being processed
|
117 |
+
chunk_size = 256 # Set the chunk size for text processing
|
118 |
+
overlap = 15 # Overlap between chunks for context preservation
|
119 |
+
script_chunks = [] # List to store script chunks
|
120 |
+
|
121 |
+
# Split the script into smaller chunks
|
122 |
+
for i in range(0, len(script), chunk_size - overlap):
|
123 |
+
chunk = script[i:i + chunk_size]
|
124 |
+
script_chunks.append(chunk)
|
125 |
+
|
126 |
+
print(f"Split into {len(script_chunks)} chunks with {overlap} token overlap") # Inform about the chunking
|
127 |
+
|
128 |
+
identified_triggers = {} # Dictionary to store the identified triggers
|
129 |
+
|
130 |
+
# Process each chunk of the script
|
131 |
+
for chunk_idx, chunk in enumerate(script_chunks, 1):
|
132 |
+
print(f"\n--- Processing Chunk {chunk_idx}/{len(script_chunks)} ---")
|
133 |
+
print(f"Chunk text (preview): {chunk[:50]}...") # Preview of the current chunk
|
134 |
+
|
135 |
+
# Check each category for triggers
|
136 |
+
for category, info in trigger_categories.items():
|
137 |
+
mapped_name = info["mapped_name"]
|
138 |
+
description = info["description"]
|
139 |
+
|
140 |
+
print(f"\nAnalyzing for {mapped_name}...")
|
141 |
+
prompt = f"""
|
142 |
+
Check this text for any indication of {mapped_name} ({description}).
|
143 |
+
Be sensitive to subtle references or implications, make sure the text is not metaphorical.
|
144 |
+
Respond concisely with: YES, NO, or MAYBE.
|
145 |
+
Text: {chunk}
|
146 |
+
Answer:
|
147 |
+
"""
|
148 |
+
|
149 |
+
print(f"Sending prompt to model...") # Indicate that prompt is being sent to the model
|
150 |
+
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512) # Tokenize the prompt
|
151 |
+
inputs = {k: v.to(device) for k, v in inputs.items()} # Send inputs to the chosen device
|
152 |
+
|
153 |
+
with torch.no_grad(): # Disable gradient calculation for inference
|
154 |
+
print("Generating response...") # Indicate that the model is generating a response
|
155 |
+
outputs = model.generate(
|
156 |
+
**inputs,
|
157 |
+
max_new_tokens=10, # Limit response length
|
158 |
+
do_sample=True, # Enable sampling for more diverse output
|
159 |
+
temperature=0.5, # Control randomness of the output
|
160 |
+
top_p=0.9, # Use nucleus sampling
|
161 |
+
pad_token_id=tokenizer.eos_token_id # Pad token ID
|
162 |
+
)
|
163 |
+
|
164 |
+
response_text = tokenizer.decode(outputs[0], skip_special_tokens=True).strip().upper() # Decode and format the response
|
165 |
+
first_word = response_text.split("\n")[-1].split()[0] if response_text else "NO" # Get the first word of the response
|
166 |
+
print(f"Model response for {mapped_name}: {first_word}")
|
167 |
+
|
168 |
+
# Update identified triggers based on model response
|
169 |
+
if first_word == "YES":
|
170 |
+
print(f"Detected {mapped_name} in this chunk!") # Trigger detected
|
171 |
+
identified_triggers[mapped_name] = identified_triggers.get(mapped_name, 0) + 1
|
172 |
+
elif first_word == "MAYBE":
|
173 |
+
print(f"Possible {mapped_name} detected, marking for further review.") # Possible trigger detected
|
174 |
+
identified_triggers[mapped_name] = identified_triggers.get(mapped_name, 0) + 0.5
|
175 |
+
else:
|
176 |
+
print(f"No {mapped_name} detected in this chunk.") # No trigger detected
|
177 |
+
|
178 |
+
print("\n=== Analysis Complete ===") # Indicate that analysis is complete
|
179 |
+
print("Final Results:")
|
180 |
+
final_triggers = [] # List to store final triggers
|
181 |
+
|
182 |
+
# Filter and output the final trigger results
|
183 |
+
for mapped_name, count in identified_triggers.items():
|
184 |
+
if count > 0.5:
|
185 |
+
final_triggers.append(mapped_name)
|
186 |
+
print(f"- {mapped_name}: found in {count} chunks")
|
187 |
+
|
188 |
+
if not final_triggers:
|
189 |
+
print("No triggers detected") # No triggers detected
|
190 |
+
final_triggers = ["None"]
|
191 |
+
|
192 |
+
print("\nReturning results...")
|
193 |
+
return final_triggers # Return the list of detected triggers
|
194 |
+
|
195 |
+
except Exception as e:
|
196 |
+
# Handle errors and provide stack trace
|
197 |
+
print(f"\nERROR OCCURRED: {str(e)}")
|
198 |
+
print("Stack trace:")
|
199 |
+
import traceback
|
200 |
+
traceback.print_exc()
|
201 |
+
return {"error": str(e)}
|
202 |
+
|
203 |
+
def get_detailed_analysis(script):
|
204 |
+
print("\n=== Starting Detailed Analysis ===")
|
205 |
+
triggers = analyze_script(script) # Call the analyze_script function
|
206 |
+
|
207 |
+
if isinstance(triggers, list) and triggers != ["None"]:
|
208 |
+
result = {
|
209 |
+
"detected_triggers": triggers,
|
210 |
+
"confidence": "High - Content detected",
|
211 |
+
"model": "Llama-3.2-1B",
|
212 |
+
"analysis_timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
213 |
+
}
|
214 |
+
else:
|
215 |
+
result = {
|
216 |
+
"detected_triggers": ["None"],
|
217 |
+
"confidence": "High - No concerning content detected",
|
218 |
+
"model": "Llama-3.2-1B",
|
219 |
+
"analysis_timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
220 |
+
}
|
221 |
+
|
222 |
+
print("\nFinal Result Dictionary:", result) # Output the final result dictionary
|
223 |
+
return result
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
flask
|
2 |
+
flask_cors
|
3 |
+
torch
|
4 |
+
transformers
|
5 |
+
accelerate
|
static/css/style.css
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Chocolate and sweets theme for TREAT */
|
2 |
+
|
3 |
+
/* Set up the main styles for the body of the document */
|
4 |
+
body {
|
5 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* font */
|
6 |
+
margin: 0; /* Remove default margin */
|
7 |
+
padding: 0; /* Remove default padding */
|
8 |
+
background-color: #836853; /* Background color resembling chocolate */
|
9 |
+
color: #FFFFFF; /* White text color for contrast */
|
10 |
+
height: 100vh; /* Full viewport height */
|
11 |
+
display: flex; /* Flexbox layout for centering content */
|
12 |
+
flex-direction: column; /* Column layout */
|
13 |
+
justify-content: center; /* Center content vertically */
|
14 |
+
align-items: center; /* Center content horizontally */
|
15 |
+
overflow: hidden; /* Hide overflow content */
|
16 |
+
}
|
17 |
+
|
18 |
+
/* Style the container for the main content */
|
19 |
+
.choco-container {
|
20 |
+
background: rgba(78, 52, 46, 0.9); /* Semi-transparent dark chocolate background */
|
21 |
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); /* Soft shadow effect */
|
22 |
+
border-radius: 20px; /* Rounded corners */
|
23 |
+
padding: 40px; /* Padding inside the container */
|
24 |
+
width: 90%; /* 90% width of the viewport */
|
25 |
+
max-width: 1000px; /* Maximum width */
|
26 |
+
height: 80%; /* 80% height of the viewport */
|
27 |
+
animation: fadeIn 1s ease-in-out; /* Fade-in animation */
|
28 |
+
border: 1px solid #5d4037; /* Slightly lighter chocolate border */
|
29 |
+
display: flex; /* Flexbox layout for centering content */
|
30 |
+
flex-direction: column; /* Column layout */
|
31 |
+
align-items: center; /* Center content horizontally */
|
32 |
+
justify-content: center; /* Center content vertically */
|
33 |
+
}
|
34 |
+
|
35 |
+
/* Define the fade-in animation for the container */
|
36 |
+
@keyframes fadeIn {
|
37 |
+
from {
|
38 |
+
opacity: 0; /* Start with no opacity */
|
39 |
+
transform: scale(0.95); /* Start slightly scaled down */
|
40 |
+
}
|
41 |
+
to {
|
42 |
+
opacity: 1; /* Fade to full opacity */
|
43 |
+
transform: scale(1); /* Scale to full size */
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
/* Style the header of the document */
|
48 |
+
.header {
|
49 |
+
text-align: center; /* Center-align text */
|
50 |
+
margin-bottom: 20px; /* Margin below the header */
|
51 |
+
}
|
52 |
+
|
53 |
+
/* Style the main heading */
|
54 |
+
.header h1 {
|
55 |
+
font-size: 3rem; /* Large font size */
|
56 |
+
color: #d7ccc8; /* Light creamy color */
|
57 |
+
}
|
58 |
+
|
59 |
+
/* Style the paragraph under the heading */
|
60 |
+
.header p {
|
61 |
+
font-size: 1.5rem; /* Medium font size */
|
62 |
+
color: #ffab91; /* Candy-like color */
|
63 |
+
}
|
64 |
+
|
65 |
+
/* Highlight certain text */
|
66 |
+
.highlight {
|
67 |
+
color: #ff7043; /* Sweet orange color */
|
68 |
+
}
|
69 |
+
|
70 |
+
/* Style the main content area */
|
71 |
+
.main-content {
|
72 |
+
width: 100%; /* Full width */
|
73 |
+
height: 100%; /* Full height */
|
74 |
+
display: flex; /* Flexbox layout */
|
75 |
+
flex-direction: column; /* Column layout */
|
76 |
+
align-items: center; /* Center content horizontally */
|
77 |
+
justify-content: center; /* Center content vertically */
|
78 |
+
}
|
79 |
+
|
80 |
+
/* Style the form */
|
81 |
+
.sweet-form {
|
82 |
+
width: 100%; /* Full width */
|
83 |
+
height: 100%; /* Full height */
|
84 |
+
display: flex; /* Flexbox layout */
|
85 |
+
flex-direction: column; /* Column layout */
|
86 |
+
align-items: center; /* Center content horizontally */
|
87 |
+
justify-content: center; /* Center content vertically */
|
88 |
+
}
|
89 |
+
|
90 |
+
/* Style the textarea element */
|
91 |
+
textarea {
|
92 |
+
width: 100%; /* Full width */
|
93 |
+
height: 70%; /* 70% height of the container */
|
94 |
+
padding: 20px; /* Padding inside the textarea */
|
95 |
+
font-size: 1.2rem; /* Medium font size */
|
96 |
+
border: none; /* Remove default border */
|
97 |
+
border-radius: 10px; /* Rounded corners */
|
98 |
+
resize: none; /* Disable resizing */
|
99 |
+
background: #3e2723; /* Chocolate color background */
|
100 |
+
color: #ffecb3; /* Creamy color text */
|
101 |
+
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); /* Inset shadow */
|
102 |
+
transition: all 0.3s ease-in-out; /* Smooth transition for all properties */
|
103 |
+
}
|
104 |
+
|
105 |
+
/* Style the textarea when it is focused */
|
106 |
+
textarea:focus {
|
107 |
+
outline: none; /* Remove default outline */
|
108 |
+
box-shadow: 0 0 15px #ffab91; /* Light glow when focused */
|
109 |
+
}
|
110 |
+
|
111 |
+
/* Style the button element */
|
112 |
+
button {
|
113 |
+
width: 50%; /* 50% width of the container */
|
114 |
+
padding: 20px; /* Padding inside the button */
|
115 |
+
font-size: 1.5rem; /* Large font size */
|
116 |
+
margin-top: 20px; /* Margin above the button */
|
117 |
+
background: linear-gradient(45deg, #8d6e63, #d7ccc8); /* Chocolate gradient background */
|
118 |
+
color: white; /* White text color */
|
119 |
+
border: none; /* Remove default border */
|
120 |
+
border-radius: 10px; /* Rounded corners */
|
121 |
+
cursor: pointer; /* Pointer cursor on hover */
|
122 |
+
text-transform: uppercase; /* Uppercase text */
|
123 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); /* Soft shadow */
|
124 |
+
transition: all 0.3s ease; /* Smooth transition for all properties */
|
125 |
+
position: relative; /* Relative positioning for overlay effects */
|
126 |
+
}
|
127 |
+
|
128 |
+
/* Style the button on hover */
|
129 |
+
button:hover {
|
130 |
+
background: linear-gradient(45deg, #d7ccc8, #8d6e63); /* Reversed gradient on hover */
|
131 |
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.6); /* Stronger shadow on hover */
|
132 |
+
}
|
133 |
+
|
134 |
+
/* Create an overlay effect for the button */
|
135 |
+
button::after {
|
136 |
+
content: ''; /* Empty content */
|
137 |
+
position: absolute; /* Absolute positioning */
|
138 |
+
top: 0; /* Position at the top */
|
139 |
+
left: 0; /* Position at the left */
|
140 |
+
width: 100%; /* Full width */
|
141 |
+
height: 100%; /* Full height */
|
142 |
+
background: rgba(255, 255, 255, 0.1); /* Light overlay */
|
143 |
+
border-radius: 10px; /* Rounded corners */
|
144 |
+
opacity: 0; /* Initially hidden */
|
145 |
+
transition: opacity 0.3s ease; /* Smooth transition for opacity */
|
146 |
+
}
|
147 |
+
|
148 |
+
/* Show the overlay effect on hover */
|
149 |
+
button:hover::after {
|
150 |
+
opacity: 1; /* Show overlay on hover */
|
151 |
+
}
|
152 |
+
|
153 |
+
/* Style the loading bar element */
|
154 |
+
.loading-bar {
|
155 |
+
display: none; /* Initially hidden */
|
156 |
+
width: 100%; /* Full width */
|
157 |
+
height: 5px; /* Fixed height */
|
158 |
+
background: linear-gradient(90deg, #8d6e63, #d7ccc8); /* Chocolate gradient background */
|
159 |
+
margin-top: 20px; /* Margin above the loading bar */
|
160 |
+
border-radius: 2px; /* Slightly rounded corners */
|
161 |
+
overflow: hidden; /* Hide overflow content */
|
162 |
+
position: relative; /* Relative positioning */
|
163 |
+
}
|
164 |
+
|
165 |
+
/* Create an animation for the loading bar */
|
166 |
+
.loading-bar::before {
|
167 |
+
content: ''; /* Empty content */
|
168 |
+
position: absolute; /* Absolute positioning */
|
169 |
+
width: 100%; /* Full width */
|
170 |
+
height: 100%; /* Full height */
|
171 |
+
background: linear-gradient(90deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0)); /* Gradient background */
|
172 |
+
animation: loading-animation 2s infinite; /* Infinite loading animation */
|
173 |
+
}
|
174 |
+
|
175 |
+
/* Define the loading animation */
|
176 |
+
@keyframes loading-animation {
|
177 |
+
0% {
|
178 |
+
transform: translateX(-100%); /* Start outside on the left */
|
179 |
+
}
|
180 |
+
50% {
|
181 |
+
transform: translateX(0); /* Move to the center */
|
182 |
+
}
|
183 |
+
100% {
|
184 |
+
transform: translateX(100%); /* Move outside on the right */
|
185 |
+
}
|
186 |
+
}
|
187 |
+
|
188 |
+
/* Style the results container */
|
189 |
+
#results {
|
190 |
+
overflow-x: auto; /* Add horizontal scrollbar only if needed */
|
191 |
+
overflow-y: hidden; /* Hide vertical overflow */
|
192 |
+
margin-top: 30px; /* Margin above the results */
|
193 |
+
color: #ff7043; /* Sweet orange color */
|
194 |
+
font-size: 1.5rem; /* Large font size */
|
195 |
+
font-weight: bold; /* Bold text */
|
196 |
+
text-shadow: 0 0 5px #ff7043, 0 0 10px #ff7043; /* Reduced shininess */
|
197 |
+
text-align: center; /* Center-align text */
|
198 |
+
word-wrap: break-word; /* Ensure text wraps appropriately */
|
199 |
+
overflow-wrap: break-word; /* Compatibility with other browsers */
|
200 |
+
padding: 10px; /* Padding inside the results container */
|
201 |
+
box-sizing: border-box; /* Include padding in width calculations */
|
202 |
+
white-space: nowrap; /* Prevent line breaks so horizontal scrolling is possible */
|
203 |
+
}
|
204 |
+
|
205 |
+
/* Style the horizontal scrollbar */
|
206 |
+
#results::-webkit-scrollbar {
|
207 |
+
height: 10px; /* Horizontal scrollbar height */
|
208 |
+
}
|
209 |
+
|
210 |
+
/* Style the scrollbar thumb */
|
211 |
+
#results::-webkit-scrollbar-thumb {
|
212 |
+
background: linear-gradient(45deg, #8d6e63, #d7ccc8); /* Chocolate gradient for scrollbar thumb */
|
213 |
+
border-radius: 5px; /* Rounded corners */
|
214 |
+
}
|
215 |
+
|
216 |
+
/* Style the scrollbar track */
|
217 |
+
#results::-webkit-scrollbar-track {
|
218 |
+
background: rgba(78, 52, 46, 0.5); /* Lighter chocolate track */
|
219 |
+
border-radius: 5px;
|
220 |
+
}
|
221 |
+
|
222 |
+
/* To Fix: Title moving upwards when Output is shown*/
|
static/images/android-chrome-192x192.png
ADDED
![]() |
static/images/apple-touch-icon.png
ADDED
![]() |
static/images/favicon-16x16.png
ADDED
![]() |
static/images/favicon-32x32.png
ADDED
![]() |
static/images/favicon.ico
ADDED
|
static/images/readme-images/Request-Access-Page.png
ADDED
![]() |
static/images/site.webmanifest
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "",
|
3 |
+
"short_name": "",
|
4 |
+
"icons": [
|
5 |
+
{
|
6 |
+
"src": "/android-chrome-192x192.png",
|
7 |
+
"sizes": "192x192",
|
8 |
+
"type": "image/png"
|
9 |
+
}
|
10 |
+
],
|
11 |
+
"theme_color": "#FFFFFF",
|
12 |
+
"background_color": "#FFFFFF",
|
13 |
+
"display": "standalone"
|
14 |
+
}
|
static/js/app.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Add an event listener for the form submission
|
2 |
+
document.getElementById('text-form').addEventListener('submit', async function(event) {
|
3 |
+
event.preventDefault(); // Prevent the default form submission behavior
|
4 |
+
|
5 |
+
const text = document.getElementById('text-input').value; // Get the text input value
|
6 |
+
const loadingBar = document.getElementById('loading-bar'); // Get the loading bar element
|
7 |
+
const resultsDiv = document.getElementById('results'); // Get the results div element
|
8 |
+
resultsDiv.textContent = ''; // Clear previous results
|
9 |
+
loadingBar.style.display = 'block'; // Display the loading bar
|
10 |
+
|
11 |
+
try {
|
12 |
+
// Make a POST request to the server with the text input
|
13 |
+
const response = await fetch('/upload', {
|
14 |
+
method: 'POST', // Use the POST method
|
15 |
+
headers: {
|
16 |
+
'Content-Type': 'application/json' // Set the Content-Type header
|
17 |
+
},
|
18 |
+
body: JSON.stringify({ text: text }) // Convert the text to JSON and send it in the request body
|
19 |
+
});
|
20 |
+
|
21 |
+
// Check if the response is not ok (status code outside the range 200-299)
|
22 |
+
if (!response.ok) {
|
23 |
+
throw new Error(`HTTP error! status: ${response.status}`); // Throw an error with the status code
|
24 |
+
}
|
25 |
+
|
26 |
+
// Parse the JSON response from the server
|
27 |
+
const result = await response.json();
|
28 |
+
resultsDiv.innerHTML = `<span>Triggers:</span> ${result.triggers.join(', ')}`; // Display the triggers in the results div
|
29 |
+
} catch (error) {
|
30 |
+
// Handle any errors that occurred during the request
|
31 |
+
console.error('Error analyzing text:', error); // Log the error to the console
|
32 |
+
resultsDiv.innerHTML = '<span>Error:</span> Unable to analyze text.'; // Display an error message in the results div
|
33 |
+
} finally {
|
34 |
+
loadingBar.style.display = 'none'; // Hide the loading bar once the request is complete
|
35 |
+
}
|
36 |
+
});
|
templates/index.html
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Ensure proper scaling on mobile devices -->
|
6 |
+
<title>TREAT</title> <!-- Page title -->
|
7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <!-- Link to external stylesheet -->
|
8 |
+
|
9 |
+
<!-- Favicon and Apple Touch Icons -->
|
10 |
+
<link rel="apple-touch-icon" sizes="180x180" href="./static/images/apple-touch-icon.png">
|
11 |
+
<link rel="icon" type="image/png" sizes="32x32" href="./static/images/favicon-32x32.png">
|
12 |
+
<link rel="icon" type="image/png" sizes="16x16" href="./static/images/favicon-16x16.png">
|
13 |
+
<link rel="manifest" href="./static/images/site.webmanifest">
|
14 |
+
|
15 |
+
</head>
|
16 |
+
<body>
|
17 |
+
<div class="choco-container"> <!-- Main container with chocolate theme -->
|
18 |
+
<header class="header"> <!-- Header section -->
|
19 |
+
<h1>TREAT</h1> <!-- Main heading -->
|
20 |
+
<p> <!-- Subheading with acronym explanation -->
|
21 |
+
<span class="highlight">T</span>rigger
|
22 |
+
<span class="highlight">R</span>ecognition for
|
23 |
+
<span class="highlight">E</span>njoyable and
|
24 |
+
<span class="highlight">A</span>ppropriate
|
25 |
+
<span class="highlight">T</span>elevision
|
26 |
+
</p>
|
27 |
+
</header>
|
28 |
+
<main class="main-content"> <!-- Main content section -->
|
29 |
+
<form id="text-form" method="POST" class="sweet-form"> <!-- Form for text input -->
|
30 |
+
<textarea id="text-input" name="text" rows="20" placeholder="Enter your text or script here..."></textarea> <!-- Textarea for user input -->
|
31 |
+
<button type="submit">Analyze Script</button> <!-- Submit button -->
|
32 |
+
</form>
|
33 |
+
<div id="loading-bar" class="loading-bar"></div> <!-- Loading bar displayed during analysis -->
|
34 |
+
<div id="results"></div> <!-- Div to display results -->
|
35 |
+
</main>
|
36 |
+
</div>
|
37 |
+
<script src="{{ url_for('static', filename='js/app.js') }}"></script> <!-- Link to external JavaScript file -->
|
38 |
+
</body>
|
39 |
+
</html>
|
utils/utils.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def identify_triggers(outputs):
|
2 |
+
# Initialize a set to store identified triggers
|
3 |
+
identified_triggers = set()
|
4 |
+
|
5 |
+
# Iterate over the model outputs
|
6 |
+
for response_text in outputs:
|
7 |
+
# Check if the response contains a positive indication of the category
|
8 |
+
if "yes" in response_text.lower():
|
9 |
+
# Extract the category from the response_text
|
10 |
+
category = extract_category(response_text)
|
11 |
+
if category:
|
12 |
+
identified_triggers.add(category)
|
13 |
+
|
14 |
+
# Convert the set of identified triggers to a list and return it
|
15 |
+
return list(identified_triggers)
|
16 |
+
|
17 |
+
def extract_category(response_text):
|
18 |
+
# Define trigger categories
|
19 |
+
trigger_categories = [
|
20 |
+
"Violence",
|
21 |
+
"Self-Harm",
|
22 |
+
"Death",
|
23 |
+
"Substance Use"
|
24 |
+
"Sexual Content"
|
25 |
+
"Sexual Abuse",
|
26 |
+
"Gun Use",
|
27 |
+
"Gore",
|
28 |
+
"Vomit",
|
29 |
+
"Mental Health Issues"
|
30 |
+
"Animal Cruelty"
|
31 |
+
]
|
32 |
+
|
33 |
+
# Check if any category is present in the response_text
|
34 |
+
for category in trigger_categories:
|
35 |
+
if category.lower() in response_text.lower():
|
36 |
+
return category
|
37 |
+
# Return None if no category is found
|
38 |
+
return None
|