lots of progress on input processing
This commit is contained in:
16
app/__init__.py
Normal file
16
app/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from config import config
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
def create_app(config_name='default'):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config[config_name])
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
from app.routes import main
|
||||
app.register_blueprint(main)
|
||||
|
||||
return app
|
||||
BIN
app/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/models.cpython-310.pyc
Normal file
BIN
app/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/routes.cpython-310.pyc
Normal file
BIN
app/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
21
app/models.py
Normal file
21
app/models.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from app import db
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
import uuid
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = 'users'
|
||||
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
email = db.Column(db.String(255), unique=True, nullable=False)
|
||||
# Placeholders for Milestone 1
|
||||
password_hash = db.Column(db.LargeBinary)
|
||||
imap_config = db.Column(db.JSON) # Using db.JSON instead of db.JSONB for compatibility
|
||||
|
||||
class Folder(db.Model):
|
||||
__tablename__ = 'folders'
|
||||
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=False)
|
||||
name = db.Column(db.String(255), nullable=False)
|
||||
rule_text = db.Column(db.Text)
|
||||
priority = db.Column(db.Integer)
|
||||
|
||||
user = db.relationship('User', backref=db.backref('folders', lazy=True))
|
||||
58
app/routes.py
Normal file
58
app/routes.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from flask import Blueprint, render_template, request
|
||||
from app import db
|
||||
from app.models import Folder, User
|
||||
import uuid
|
||||
|
||||
main = Blueprint('main', __name__)
|
||||
|
||||
# For prototype, use a fixed user ID
|
||||
MOCK_USER_ID = '123e4567-e89b-12d3-a456-426614174000'
|
||||
|
||||
@main.route('/')
|
||||
def index():
|
||||
# Ensure the mock user exists
|
||||
user = User.query.get(MOCK_USER_ID)
|
||||
if not user:
|
||||
user = User(id=MOCK_USER_ID, email='prototype@example.com')
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
|
||||
return render_template('index.html', folders=folders)
|
||||
|
||||
@main.route('/api/folders', methods=['POST'])
|
||||
def add_folder():
|
||||
try:
|
||||
# Get form data instead of JSON
|
||||
name = request.form.get('name')
|
||||
rule_text = request.form.get('rule_text')
|
||||
priority = request.form.get('priority')
|
||||
|
||||
if not name:
|
||||
# Return the folders list unchanged with an error message
|
||||
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
|
||||
# We'll add error handling in the template
|
||||
return render_template('partials/folders_list.html', folders=folders)
|
||||
|
||||
# Create new folder
|
||||
folder = Folder(
|
||||
user_id=MOCK_USER_ID,
|
||||
name=name,
|
||||
rule_text=rule_text,
|
||||
priority=int(priority) if priority else None
|
||||
)
|
||||
|
||||
db.session.add(folder)
|
||||
db.session.commit()
|
||||
|
||||
# Get updated list of folders
|
||||
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
|
||||
|
||||
# Return the updated folders list HTML
|
||||
return render_template('partials/folders_list.html', folders=folders)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
# Return the folders list unchanged
|
||||
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
|
||||
return render_template('partials/folders_list.html', folders=folders)
|
||||
251
app/templates/index.html
Normal file
251
app/templates/index.html
Normal file
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Organizer - Prototype</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#8B5CF6', /* Purple 500 */
|
||||
secondary: '#A78BFA', /* Purple 400 */
|
||||
accent: '#C4B5FD', /* Purple 300 */
|
||||
background: '#0F172A', /* Slate 900 */
|
||||
surface: '#1E293B', /* Slate 800 */
|
||||
text: '#F1F5F9', /* Slate 100 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #0F172A; /* background */
|
||||
color: #F1F5F9; /* text */
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
.sidebar {
|
||||
background-color: #1E293B; /* surface */
|
||||
border-right: 1px solid #334155; /* slate 700 */
|
||||
}
|
||||
.card {
|
||||
background-color: #1E293B; /* surface */
|
||||
border: 1px solid #334155; /* slate 700 */
|
||||
}
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #8B5CF6; /* primary */
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #7C3AED; /* primary darker */
|
||||
}
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
border: 1px solid #334155; /* slate 700 */
|
||||
color: #F1F5F9; /* text */
|
||||
}
|
||||
.btn-outline:hover {
|
||||
background-color: #334155; /* slate 700 */
|
||||
}
|
||||
.btn-error {
|
||||
color: #F87171; /* red 400 */
|
||||
}
|
||||
.btn-error:hover {
|
||||
background-color: #7F1D1D; /* red 900 */
|
||||
}
|
||||
.input, .textarea {
|
||||
background-color: #0F172A; /* background */
|
||||
border: 1px solid #334155; /* slate 700 */
|
||||
border-radius: 0.375rem;
|
||||
color: #F1F5F9; /* text */
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.input:focus, .textarea:focus {
|
||||
border-color: #8B5CF6; /* primary */
|
||||
box-shadow: 0 0 0 1px #8B5CF6; /* primary */
|
||||
outline: none;
|
||||
}
|
||||
.nav-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background-color: #334155; /* slate 700 */
|
||||
}
|
||||
.nav-item.active {
|
||||
background-color: #8B5CF6; /* primary */
|
||||
color: white;
|
||||
}
|
||||
.user-dropdown {
|
||||
background-color: #1E293B; /* surface */
|
||||
border: 1px solid #334155; /* slate 700 */
|
||||
}
|
||||
.modal-box {
|
||||
background-color: #1E293B; /* surface */
|
||||
border: 1px solid #334155; /* slate 700 */
|
||||
}
|
||||
.notification-badge {
|
||||
background-color: #F87171; /* red 400 */
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar w-64 min-h-screen p-4 flex flex-col">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-primary flex items-center">
|
||||
<i class="fas fa-envelope mr-2"></i>
|
||||
Email Organizer
|
||||
</h1>
|
||||
<p class="text-secondary text-sm mt-1">AI-powered email organization</p>
|
||||
</div>
|
||||
|
||||
<nav class="flex-grow">
|
||||
<div class="nav-item active">
|
||||
<i class="fas fa-folder mr-3"></i>
|
||||
Folders
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-inbox mr-3"></i>
|
||||
Inbox
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-cog mr-3"></i>
|
||||
Settings
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-chart-bar mr-3"></i>
|
||||
Analytics
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-question-circle mr-3"></i>
|
||||
Help
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="mt-auto pt-4 border-t border-slate-700">
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-sign-out-alt mr-3"></i>
|
||||
Logout
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<!-- Top Bar -->
|
||||
<header class="border-b border-slate-700 p-4 flex justify-between items-center">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold">Folders</h2>
|
||||
<p class="text-secondary text-sm">Manage your email organization rules</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="relative p-2 rounded-full hover:bg-slate-700">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span class="notification-badge absolute top-0 right-0">3</span>
|
||||
</button>
|
||||
|
||||
<div class="relative">
|
||||
<button id="user-menu-button" class="flex items-center space-x-2 p-2 rounded-lg hover:bg-slate-700">
|
||||
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
|
||||
<span class="font-semibold">U</span>
|
||||
</div>
|
||||
<span>User Name</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<div id="user-dropdown" class="user-dropdown absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 hidden">
|
||||
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Settings</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 p-6 overflow-auto">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold">Email Folders</h2>
|
||||
<p class="text-secondary">Create and manage your email organization rules</p>
|
||||
</div>
|
||||
<button class="btn btn-primary flex items-center" onclick="document.getElementById('add-folder-modal').showModal()">
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
Add Folder
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section id="folders-list" class="mb-12">
|
||||
{% include 'partials/folders_list.html' %}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Add Folder Modal -->
|
||||
<dialog id="add-folder-modal" class="modal">
|
||||
<div class="modal-box card">
|
||||
<h3 class="font-bold text-lg mb-4">Add New Folder</h3>
|
||||
<form id="add-folder-form" hx-post="/api/folders" hx-target="#folders-list" hx-swap="innerHTML" hx-on:htmx:after-request="document.getElementById('add-folder-modal').close(); document.getElementById('add-folder-form').reset();">
|
||||
<div class="mb-4">
|
||||
<label for="folder-name" class="block text-sm font-medium mb-1">Name</label>
|
||||
<input type="text" id="folder-name" name="name" class="input w-full" placeholder="e.g., Work, Personal, Newsletters" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="folder-rule" class="block text-sm font-medium mb-1">Rule (Natural Language)</label>
|
||||
<textarea id="folder-rule" name="rule_text" class="textarea w-full h-24" placeholder="e.g., Move emails from 'newsletter@company.com' to this folder" required></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="folder-priority" class="block text-sm font-medium mb-1">Priority</label>
|
||||
<select id="folder-priority" name="priority" class="input w-full">
|
||||
<option value="1">High</option>
|
||||
<option value="0" selected>Normal</option>
|
||||
<option value="-1">Low</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn btn-outline" onclick="document.getElementById('add-folder-modal').close()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Add Folder</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
// User dropdown toggle
|
||||
document.getElementById('user-menu-button').addEventListener('click', function() {
|
||||
const dropdown = document.getElementById('user-dropdown');
|
||||
dropdown.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
const dropdown = document.getElementById('user-dropdown');
|
||||
const button = document.getElementById('user-menu-button');
|
||||
if (!button.contains(event.target) && !dropdown.contains(event.target)) {
|
||||
dropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
34
app/templates/partials/folders_list.html
Normal file
34
app/templates/partials/folders_list.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for folder in folders %}
|
||||
<div class="card rounded-lg p-6 shadow-lg">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<h3 class="text-xl font-bold">{{ folder.name }}</h3>
|
||||
<div class="flex space-x-2">
|
||||
<button class="p-2 rounded-full hover:bg-slate-700">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="p-2 rounded-full hover:bg-slate-700 text-red-400">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-secondary mb-4">{{ folder.rule_text }}</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs px-2 py-1 rounded-full bg-slate-700">Priority: {{ folder.priority or 'Normal' }}</span>
|
||||
<span class="text-xs text-secondary">0 emails</span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-span-full text-center py-12">
|
||||
<div class="text-5xl mb-4 text-secondary">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-2">No folders yet</h3>
|
||||
<p class="text-secondary mb-4">Add your first folder to get started organizing your emails.</p>
|
||||
<button class="btn btn-primary" onclick="document.getElementById('add-folder-modal').showModal()">
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
Create Folder
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user