soyailabs / app /database.py
SOY NV AI
Add Gemini API integration with REST API support, improve error handling, and add markdown bold formatting for messages
665bcdc
raw
history blame
9.49 kB
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
# ์‚ฌ์šฉ์ž ๋ชจ๋ธ
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
nickname = db.Column(db.String(80), nullable=True) # ๋‹‰๋„ค์ž„ ํ•„๋“œ ์ถ”๊ฐ€
password_hash = db.Column(db.String(255), nullable=False)
is_admin = db.Column(db.Boolean, default=False, nullable=False)
is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
last_login = db.Column(db.DateTime, nullable=True)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'nickname': self.nickname,
'is_admin': self.is_admin,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'last_login': self.last_login.isoformat() if self.last_login else None
}
# ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ณด ๋ชจ๋ธ
class UploadedFile(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
original_filename = db.Column(db.String(255), nullable=False)
file_path = db.Column(db.String(500), nullable=False)
file_size = db.Column(db.Integer, nullable=False)
model_name = db.Column(db.String(100), nullable=True) # ์—ฐ๊ฒฐ๋œ ๋ชจ๋ธ ์ด๋ฆ„
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
parent_file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=True) # ์ด์–ด์„œ ์—…๋กœ๋“œํ•œ ๊ฒฝ์šฐ ์›๋ณธ ํŒŒ์ผ ID
# ๊ด€๊ณ„
parent_file = db.relationship('UploadedFile', remote_side=[id], backref='child_files')
def to_dict(self):
# ์ฒญํฌ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ
chunk_count = len(self.chunks) if hasattr(self, 'chunks') else 0
return {
'id': self.id,
'filename': self.filename,
'original_filename': self.original_filename,
'file_size': self.file_size,
'model_name': self.model_name,
'uploaded_at': self.uploaded_at.isoformat() if self.uploaded_at else None,
'uploaded_by': self.uploaded_by,
'parent_file_id': self.parent_file_id,
'chunk_count': chunk_count,
'child_count': len(self.child_files) if self.child_files else 0
}
# ๋Œ€ํ™” ์„ธ์…˜ ๋ชจ๋ธ
class ChatSession(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
title = db.Column(db.String(255), nullable=True)
model_name = db.Column(db.String(100), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# ๊ด€๊ณ„
user = db.relationship('User', backref='chat_sessions')
messages = db.relationship('ChatMessage', backref='session', lazy=True, cascade='all, delete-orphan', order_by='ChatMessage.created_at')
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'title': self.title,
'model_name': self.model_name,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'message_count': len(self.messages)
}
# ๋Œ€ํ™” ๋ฉ”์‹œ์ง€ ๋ชจ๋ธ
class ChatMessage(db.Model):
id = db.Column(db.Integer, primary_key=True)
session_id = db.Column(db.Integer, db.ForeignKey('chat_session.id'), nullable=False)
role = db.Column(db.String(20), nullable=False) # 'user' or 'ai'
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
def to_dict(self):
return {
'id': self.id,
'session_id': self.session_id,
'role': self.role,
'content': self.content,
'created_at': self.created_at.isoformat() if self.created_at else None
}
# ๋ฌธ์„œ ์ฒญํฌ ๋ชจ๋ธ (RAG์šฉ)
class DocumentChunk(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
chunk_index = db.Column(db.Integer, nullable=False) # ์ฒญํฌ ์ˆœ์„œ
content = db.Column(db.Text, nullable=False) # ์ฒญํฌ ๋‚ด์šฉ
embedding = db.Column(db.Text, nullable=True) # ์ž„๋ฒ ๋”ฉ ๋ฒกํ„ฐ (JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
# ๊ด€๊ณ„
file = db.relationship('UploadedFile', backref='chunks')
def to_dict(self):
return {
'id': self.id,
'file_id': self.file_id,
'chunk_index': self.chunk_index,
'content': self.content,
'created_at': self.created_at.isoformat() if self.created_at else None
}
# Parent Chunk ๋ชจ๋ธ (AI ๋ถ„์„ ๊ฒฐ๊ณผ ์ €์žฅ)
class ParentChunk(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False, unique=True)
world_view = db.Column(db.Text, nullable=True) # ์„ธ๊ณ„๊ด€ ์„ค๋ช…
characters = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์บ๋ฆญํ„ฐ ๋ถ„์„
story = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์Šคํ† ๋ฆฌ ๋ถ„์„
episodes = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์—ํ”ผ์†Œ๋“œ ๋ถ„์„
others = db.Column(db.Text, nullable=True) # ๊ธฐํƒ€
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# ๊ด€๊ณ„
file = db.relationship('UploadedFile', backref='parent_chunk')
def to_dict(self):
return {
'id': self.id,
'file_id': self.file_id,
'world_view': self.world_view,
'characters': self.characters,
'story': self.story,
'episodes': self.episodes,
'others': self.others,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
# ์‹œ์Šคํ…œ ์„ค์ • ๋ชจ๋ธ (API ํ‚ค ๋“ฑ)
class SystemConfig(db.Model):
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String(100), unique=True, nullable=False) # ์„ค์ • ํ‚ค (์˜ˆ: 'gemini_api_key')
value = db.Column(db.Text, nullable=True) # ์„ค์ • ๊ฐ’ (์•”ํ˜ธํ™”๋œ API ํ‚ค)
description = db.Column(db.String(255), nullable=True) # ์„ค์ • ์„ค๋ช…
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
def to_dict(self):
return {
'id': self.id,
'key': self.key,
'value': self.value, # ์‹ค์ œ ๊ฐ’ ๋ฐ˜ํ™˜ (๋ณด์•ˆ์„ ์œ„ํ•ด ๋งˆ์Šคํ‚น ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ)
'description': self.description,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
@staticmethod
def get_config(key, default=None):
"""์„ค์ • ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ"""
try:
config = SystemConfig.query.filter_by(key=key).first()
return config.value if config else default
except Exception as e:
# ํ…Œ์ด๋ธ”์ด ์—†๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
print(f"[SystemConfig.get_config] ์˜ค๋ฅ˜: {e}")
return default
@staticmethod
def set_config(key, value, description=None):
"""์„ค์ • ๊ฐ’ ์ €์žฅ/์—…๋ฐ์ดํŠธ"""
try:
# ํ…Œ์ด๋ธ”์ด ์—†์œผ๋ฉด ์ƒ์„ฑ ์‹œ๋„
from sqlalchemy import inspect
inspector = inspect(db.engine)
if 'system_config' not in inspector.get_table_names():
print("[SystemConfig.set_config] SystemConfig ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ค‘...")
db.create_all()
config = SystemConfig.query.filter_by(key=key).first()
if config:
config.value = value
if description:
config.description = description
config.updated_at = datetime.utcnow()
else:
config = SystemConfig(key=key, value=value, description=description)
db.session.add(config)
db.session.commit()
return config
except Exception as e:
db.session.rollback()
print(f"[SystemConfig.set_config] ์˜ค๋ฅ˜: {e}")
import traceback
traceback.print_exc()
raise