-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathmodels.py
More file actions
178 lines (142 loc) · 6.81 KB
/
Copy pathmodels.py
File metadata and controls
178 lines (142 loc) · 6.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import os
try:
import tiktoken
TIKTOKEN_AVAILABLE = True
except Exception:
TIKTOKEN_AVAILABLE = False
tiktoken = None
import json
db = SQLAlchemy()
class User(db.Model):
"""User model for authentication and ownership"""
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
display_name = db.Column(db.String(50), unique=True, nullable=False, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
last_login = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
# Relationships
owned_rooms = db.relationship('Room', backref='owner', lazy='dynamic', foreign_keys='Room.owner_id')
def __repr__(self):
return f'<User {self.display_name} ({self.email})>'
class OTPToken(db.Model):
"""One-Time Password tokens for email authentication"""
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), nullable=False, index=True)
otp_code = db.Column(db.String(6), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
expires_at = db.Column(db.DateTime, nullable=False)
used = db.Column(db.Boolean, default=False, nullable=False)
def __init__(self, email, otp_code, expiration_minutes=10):
self.email = email
self.otp_code = otp_code
self.created_at = datetime.utcnow()
self.expires_at = self.created_at + timedelta(minutes=expiration_minutes)
self.used = False
def is_valid(self):
"""Check if the OTP is still valid (not used and not expired)"""
return not self.used and datetime.utcnow() < self.expires_at
def __repr__(self):
return f'<OTPToken {self.email} expires_at={self.expires_at}>'
class Room(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), nullable=False, unique=True)
title = db.Column(db.String(128), nullable=True)
active_users = db.Column(db.Text, default="") # Store as a comma-separated string
inactive_users = db.Column(db.Text, default="") # Store as a comma-separated string
is_private = db.Column(db.Boolean, default=False, nullable=False, index=True)
is_archived = db.Column(db.Boolean, default=False, nullable=False, index=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.Integer, default=lambda: int(datetime.utcnow().timestamp()), nullable=False)
forked_from_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=True)
def add_user(self, username):
active_users = set(self.active_users.split(",")) if self.active_users else set()
inactive_users = (
set(self.inactive_users.split(",")) if self.inactive_users else set()
)
# Move from inactive to active if necessary
if username in inactive_users:
inactive_users.discard(username)
active_users.add(username)
self.active_users = ",".join(sorted(active_users))
self.inactive_users = ",".join(sorted(inactive_users))
def remove_user(self, username):
active_users = set(self.active_users.split(",")) if self.active_users else set()
inactive_users = (
set(self.inactive_users.split(",")) if self.inactive_users else set()
)
if username in active_users:
active_users.discard(username)
inactive_users.add(username) # Move to inactive users
self.active_users = ",".join(sorted(active_users))
self.inactive_users = ",".join(sorted(inactive_users))
def get_active_users(self):
return self.active_users.split(",") if self.active_users else []
def get_inactive_users(self):
return self.inactive_users.split(",") if self.inactive_users else []
class UserSession(db.Model):
id = db.Column(db.Integer, primary_key=True)
session_id = db.Column(db.String(128), unique=True, nullable=False)
username = db.Column(db.String(128))
room_name = db.Column(db.String(128))
room_id = db.Column(db.Integer)
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(128), nullable=False)
content = db.Column(db.String(1024), nullable=False)
token_count = db.Column(db.Integer)
room_id = db.Column(db.Integer, db.ForeignKey("room.id"), nullable=False)
def __init__(self, username, content, room_id):
self.username = username
self.content = content
self.room_id = room_id
self.count_tokens()
def count_tokens(self):
if self.token_count is None:
if self.is_base64_image():
self.token_count = 0
elif not TIKTOKEN_AVAILABLE:
# Fallback: estimate ~4 chars per token when tiktoken unavailable
self.token_count = len(self.content) // 4 + 1
else:
try:
encoding = tiktoken.encoding_for_model("gpt-4")
self.token_count = len(encoding.encode(self.content))
except Exception:
# Fallback on any tiktoken error (network, SSL, etc.)
self.token_count = len(self.content) // 4 + 1
return self.token_count
def is_base64_image(self):
"""Check if message contains a base64-encoded image."""
if not self.content:
return False
# Check for any base64 image (jpeg, png, gif, webp, etc.)
return '<img' in self.content and 'data:image/' in self.content and ';base64,' in self.content
class ActivityState(db.Model):
id = db.Column(db.Integer, primary_key=True)
room_id = db.Column(db.Integer, db.ForeignKey("room.id"), nullable=False)
section_id = db.Column(db.String(128), nullable=False)
step_id = db.Column(db.String(128), nullable=False)
attempts = db.Column(db.Integer, default=0)
max_attempts = db.Column(db.Integer, default=3)
s3_file_path = db.Column(db.String(256), nullable=False)
json_metadata = db.Column(db.UnicodeText, default="{}")
@property
def dict_metadata(self):
return json.loads(self.json_metadata) if self.json_metadata else {}
@dict_metadata.setter
def dict_metadata(self, value):
self.json_metadata = json.dumps(value)
def add_metadata(self, key, value):
metadata = self.dict_metadata
metadata[key] = value
self.dict_metadata = metadata
def remove_metadata(self, key):
metadata = self.dict_metadata
if key in metadata:
del metadata[key]
self.dict_metadata = metadata
def clear_metadata(self):
self.dict_metadata = {}