Files
2026-08-19 23:37:38 -03:00

2055 lines
56 KiB
Python

import os, json, uuid
import requests
from flask import Flask, Response, request, jsonify, session, redirect, url_for, send_file
from authlib.integrations.flask_client import OAuth
from functools import wraps
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from markdown import markdown
from config import Config
from models import Base, Conversation, Message, Attachment
from flask_sock import Sock
app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config["SECRET_KEY"]
engine = create_engine(os.getenv("DATABASE_URL"))
SessionLocal = sessionmaker(bind=engine)
Base.metadata.create_all(engine)
# TODO:
# conversation.user_email == email
# msgs = db.query(Message).filter_by(conversation_id=cid).all()
sock = Sock(app)
@sock.route("/ws/notifications")
def notifications(ws):
# All comments are in English.
# Simple example: send a ping every time a message is added.
while True:
data = ws.receive()
# You can interpret data or just echo.
ws.send(json.dumps({"type": "pong", "payload": data}))
@celery.task(name="tasks.admin_generate", bind=True, max_retries=3)
def admin_generate(self, conversation_id, model, prompt):
# Same logic as generate_task, but runs in the admin queue
...
@app.route("/api/queue/admin_generate", methods=["POST"])
@login_required
@admin_required
def queue_admin_generate():
data = request.json
cid = data["conversation_id"]
model = data["model"]
prompt = data["prompt"]
task = admin_generate.delay(cid, model, prompt)
return jsonify({"task_id": task.id})
# ================
# DECORATORS
# ================
def enforce_plan_limits(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
user = db.query(User).filter_by(email=email).first()
plan = db.query(Plan).filter_by(id=user.plan_id).first()
# Limite de conversas
conv_count = db.query(Conversation).filter_by(user_email=email).count()
if conv_count >= plan.max_conversations:
db.close()
return jsonify({"erro": "Você atingiu o limite de conversas do seu plano."}), 403
db.close()
return f(*args, **kwargs)
return wrapper
def require_subscription(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
user = db.query(User).filter_by(email=email).first()
plan = db.query(Plan).filter_by(id=user.plan_id).first()
sub = db.query(Subscription).filter_by(user_email=email, status="active").first()
db.close()
if not sub:
return jsonify({"erro": "Sua assinatura expirou ou não está ativa."}), 402
return f(*args, **kwargs)
return wrapper
import hashlib
def make_cache_key(model, prompt):
raw = f"{email}:{model}:{prompt}"
return hashlib.sha256(raw.encode()).hexdigest()
def get_cache(model, prompt):
key = make_cache_key(model, prompt)
db = SessionLocal()
entry = db.query(Cache).filter_by(key=key).first()
db.close()
return entry.response if entry else None
def set_cache(model, prompt, response):
key = make_cache_key(model, prompt)
db = SessionLocal()
entry = Cache(key=key, model=model, response=response)
db.add(entry)
db.commit()
db.close()
import structlog
logger = structlog.get_logger()
@app.before_request
def log_request():
logger.info(
"request",
path=request.path,
method=request.method,
remote=request.remote_addr,
)
@app.after_request
def log_response(response):
logger.info(
"response",
path=request.path,
status=response.status_code,
)
return response
from limits import RateLimitItemPerMinute
from time import time
rate_store = {}
def rate_limited(f):
@wraps(f)
def wrapper(*args, **kwargs):
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
key = f"{email}:{f.__name__}"
limit = RateLimitItemPerMinute(30) # 30 req/min por endpoint
now = int(time())
window = now // 60
used = rate_store.get((key, window), 0)
if used >= limit.amount:
return jsonify({"error": "Rate limit exceeded"}), 429
rate_store[(key, window)] = used + 1
return f(*args, **kwargs)
return wrapper
def call_ollama_with_fallback(payload):
for model in FALLBACK_MODELS:
payload["model"] = model
try:
r = requests.post(f"{app.config['OLLAMA_BASE_URL']}/api/generate", json=payload)
r.raise_for_status()
return r.json(), model
except Exception:
continue
raise Exception("All models failed")
def corp_admin_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
u = db.query(User).filter_by(email=email).first()
db.close()
if not u or u.priority not in ["system_admin", "corp_admin"]:
return jsonify({"erro": "Acesso restrito ao administrador corporativo."}), 403
return f(*args, **kwargs)
return wrapper
def system_admin_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
u = db.query(User).filter_by(email=email).first()
db.close()
if not u or u.priority != "system_admin":
return jsonify({"erro": "Acesso restrito ao administrador do sistema."}), 403
return f(*args, **kwargs)
return wrapper
def admin_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
u = db.query(User).filter_by(email=email).first()
db.close()
if not u or u.role != "admin":
return jsonify({"error": "Admin only"}), 403
return f(*args, **kwargs)
return wrapper
def require_capability(cap):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
db = SessionLocal()
email = session["user"]["email"]
u = db.query(User).filter_by(email=email).first()
db.close()
if not u or not getattr(u, cap, False):
return jsonify({"error": "Forbidden", "missing_capability": cap}), 403
return f(*args, **kwargs)
return wrapper
return decorator
@app.before_request
def enforce_system_language():
# All internal system logs/messages must be in English.
logger.info(
"request_received",
path=request.path,
method=request.method,
lang=Config.SYSTEM_LANG
)
# ================
# AUTH
# ================
import smtplib
from email.message import EmailMessage
import uuid
@app.route("/auth/forgot", methods=["POST"])
def forgot():
email = request.form.get("email")
db = SessionLocal()
user = db.query(User).filter_by(email=email).first()
if not user:
db.close()
return jsonify({"status": "ok"}) # não revela nada
token = str(uuid.uuid4())
user.reset_token = token
db.commit()
db.close()
msg = EmailMessage()
msg["Subject"] = "Password reset"
msg["From"] = Config.SMTP_USER
msg["To"] = email
msg.set_content(f"Reset link: https://{Config.APP_DOMAIN}/auth/reset?token={token}")
with smtplib.SMTP(Config.SMTP_HOST, Config.SMTP_PORT) as s:
s.starttls()
s.login(Config.SMTP_USER, Config.SMTP_PASS)
s.send_message(msg)
return jsonify({"status": "ok"})
@app.route("/auth/reset", methods=["POST"])
def reset():
token = request.form.get("token")
new_password = request.form.get("password")
db = SessionLocal()
user = db.query(User).filter_by(reset_token=token).first()
if not user:
db.close()
return jsonify({"error": "Invalid token"}), 400
user.password = new_password
user.reset_token = None
db.commit()
db.close()
return jsonify({"status": "ok"})
import pyotp, base64, os
@app.route("/auth/enable_2fa", methods=["POST"])
@login_required
def enable_2fa():
db = SessionLocal()
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
u = db.query(User).filter_by(email=email).first()
secret = base64.b32encode(os.urandom(10)).decode("utf-8")
u.totp_secret = secret
u.twofa_enabled = True
db.commit()
db.close()
# Você mostra esse secret como QR code no frontend (otpauth:// URI)
return jsonify({"secret": secret})
@app.route("/auth_mode")
def auth_mode():
return jsonify({"mode": app.config["AUTH_MODE"]})
@app.route("/auth/login", methods=["GET", "POST"])
def db_login():
if request.method == "GET":
return send_file("/srv/auth/login.html")
email = request.form.get("email")
password = request.form.get("password")
token = request.form.get("token") # código 2FA
db = SessionLocal()
user = db.query(User).filter_by(email=email).first()
if not user or user.password != password:
db.close()
return jsonify({"error": "Invalid credentials"}), 401
if user.twofa_enabled:
totp = pyotp.TOTP(user.totp_secret)
if not totp.verify(token):
db.close()
return jsonify({"error": "Invalid 2FA token"}), 401
db.close()
session["db_user"] = {"email": user.email, "name": user.name, "role": user.role}
return redirect("/")
oauth = OAuth(app)
google = oauth.register(
name="google",
client_id=app.config["GOOGLE_CLIENT_ID"],
client_secret=app.config["GOOGLE_CLIENT_SECRET"],
access_token_url="https://oauth2.googleapis.com/token",
authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
api_base_url="https://www.googleapis.com/oauth2/v2/",
client_kwargs={"scope": "openid email profile"},
)
def login_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
key = "user" if app.config["AUTH_MODE"] == "oauth" else "db_user"
if key not in session:
return redirect("/auth/login")
email = session[key]["email"]
db = SessionLocal()
u = db.query(User).filter_by(email=email).first()
db.close()
if not u:
session.pop(key, None)
return redirect("/auth/login")
return f(*args, **kwargs)
return wrapper
@app.route("/auth/login", methods=["GET", "POST"])
def db_login():
if request.method == "GET":
return send_file("/srv/auth/login.html")
email = request.form.get("email")
password = request.form.get("password")
db = SessionLocal()
user = db.query(User).filter_by(email=email).first()
db.close()
if not user or user.password != password:
return jsonify({"error": "Invalid credentials"}), 401
session["db_user"] = {
"email": user.email,
"name": user.name,
"role": user.role
}
return redirect("/")
@app.route("/auth/logout")
def db_logout():
session.pop("db_user", None)
return redirect("/auth/login")
@app.route("/auth/register", methods=["POST"])
def db_register():
email = request.form.get("email")
password = request.form.get("password")
name = request.form.get("name")
db = SessionLocal()
if db.query(User).filter_by(email=email).first():
return jsonify({"error": "User exists"}), 400
consent = request.form.get("consent_training") == "on"
u = User(
email=email,
name=name,
password=password,
role="user",
consent_training=consent,
can_use_audio=False,
can_use_vision=False,
can_share=True,
can_import_docs=True,
can_export_pdf=True,
can_export_md=True,
)
from datetime import datetime
now = datetime.utcnow()
u.terms_accepted_at = now
u.privacy_accepted_at = now
u.dpa_accepted_at = now
u.cookies_accepted_at = now
u.security_accepted_at = now
db.add(u)
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/login")
def login():
return google.authorize_redirect(app.config["GOOGLE_REDIRECT_URI"])
@app.route("/oauth/callback")
def oauth_callback():
token = google.authorize_access_token()
user_info = google.get("userinfo").json()
#session["user"] = user_info
db = SessionLocal()
u = db.query(User).filter_by(email=user_info["email"]).first()
if not u:
u = User(
email=user_info["email"],
name=user_info.get("name", ""),
role="user",
can_use_audio=False,
can_use_vision=False,
can_share=True,
)
db.add(u)
db.commit()
db.close()
return redirect("/")
# ================
# LEGAL
# ================
@app.route("/legal/links")
def legal_links():
return jsonify({
"termos": "/legal/termos-de-uso.html",
"privacidade": "/legal/politica-privacidade.html",
"dpa": "/legal/dpa.html",
"cookies": "/legal/politica-cookies.html",
"seguranca": "/legal/politica-seguranca.html",
"consentimento": "/legal/consentimento-treinamento.html",
})
# ================
# ADMIN
# ================
@app.route("/admin/set_priority/<email>", methods=["POST"])
@login_required
@system_admin_required
def set_priority(email):
data = request.json
priority = data.get("priority")
if priority not in ["system_admin", "corp_admin", "user"]:
return jsonify({"erro": "Prioridade inválida."}), 400
db = SessionLocal()
u = db.query(User).filter_by(email=email).first()
u.priority = priority
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/admin/plans")
@login_required
@admin_required
def admin_plans():
db = SessionLocal()
plans = db.query(Plan).all()
db.close()
return jsonify([{
"id": p.id,
"name": p.name,
"price_monthly": p.price_monthly,
"price_yearly": p.price_yearly,
"priority_queue": p.priority_queue
} for p in plans])
@app.route("/admin/plans/create", methods=["POST"])
@login_required
@admin_required
def admin_create_plan():
data = request.json
db = SessionLocal()
p = Plan(**data)
db.add(p)
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/admin/subscriptions/<email>")
@login_required
@admin_required
def admin_subscriptions(email):
db = SessionLocal()
subs = db.query(Subscription).filter_by(user_email=email).all()
db.close()
return jsonify([{
"id": s.id,
"plan_id": s.plan_id,
"status": s.status,
"renew_at": s.renew_at.isoformat()
} for s in subs])
@app.route("/admin/users")
@login_required
@admin_required
def admin_users():
db = SessionLocal()
#users = db.query(User).all()
users = db.query(User).filter_by(company_id=u.company_id)
data = [{
"email": u.email,
"name": u.name,
"role": u.role,
"can_use_audio": u.can_use_audio,
"can_use_vision": u.can_use_vision,
"can_share": u.can_share,
"can_import_docs": u.can_import_docs,
"can_export_pdf": u.can_export_pdf,
"can_export_md": u.can_export_md,
} for u in users]
db.close()
return jsonify(data)
@app.route("/admin/update_acl/<email>", methods=["POST"])
@login_required
@admin_required
def update_acl(email):
db = SessionLocal()
u = db.query(User).filter_by(email=email).first()
if not u:
return jsonify({"error": "User not found"}), 404
data = request.json
for key, value in data.items():
if hasattr(u, key):
setattr(u, key, value)
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/admin/promote/<email>", methods=["POST"])
@login_required
@admin_required
def promote(email):
db = SessionLocal()
u = db.query(User).filter_by(email=email).first()
if not u:
return jsonify({"error": "User not found"}), 404
u.role = "admin"
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/admin/conversations/<email>")
@login_required
@admin_required
def admin_conversations(email):
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
convs = db.query(Conversation).filter_by(user_email=email).all()
if not convs:
return jsonify({"erro": "Conversas não encontradas."}), 404
data = [{"id": c.id, "title": c.title, "tags": c.tags} for c in convs]
db.close()
return jsonify(data)
# ================
# API
# ================
@app.route("/api/chat", methods=["POST"])
@login_required
@rate_limited
def chat():
data = request.json
model = data.get("model", "llama3.2")
messages = data.get("messages", [])
url = f"{app.config['OLLAMA_BASE_URL']}/api/chat"
payload = {"model": model, "messages": messages}
r = requests.post(url, json=payload)
r.raise_for_status()
return jsonify(r.json())
@app.route("/api/chat/stream")
@login_required
@rate_limited
def chat_stream():
cid = request.args.get("conversation_id")
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
db = SessionLocal()
msgs = (
db.query(Message)
.join(Conversation, Message.conversation_id == Conversation.id)
.filter(Conversation.id == cid, Conversation.user_email == email)
.order_by(Message.created_at)
.all()
)
db.close()
chatml = [{"role": m.role, "content": m.content} for m in msgs]
# segue chamada ao Ollama
model = request.args.get("model", "llama3.2")
#cid = request.args.get("conversation_id")
def generate():
url = f"{app.config['OLLAMA_BASE_URL']}/api/chat"
payload = {"model": model, "messages": chatml, "stream": True}
with requests.post(url, json=payload, stream=True) as r:
for line in r.iter_lines():
if not line:
continue
data = json.loads(line.decode())
token = data.get("message", {}).get("content", "")
yield f"data: {json.dumps({'token': token})}\n\n"
return Response(generate(), mimetype="text/event-stream")
@app.route("/api/new_conversation", methods=["POST"])
@login_required
def new_conversation():
db = SessionLocal()
cid = str(uuid.uuid4())
title = request.json.get("title", "New conversation")
conv = Conversation(id=cid, title=title)
db.add(conv)
db.commit()
db.close()
return jsonify({"conversation_id": cid, "title": title})
@app.route("/api/conversations")
@login_required
def list_conversations():
db = SessionLocal()
user_email = session["user"]["email"]
convs = db.query(Conversation).filter(
(Conversation.user_email == user_email) |
(Conversation.shared == user_email)
).all()
data = [{"id": c.id, "title": c.title, "tags": c.tags} for c in convs]
db.close()
return jsonify(data)
@app.route("/api/export/md/<cid>/<filename>")
@login_required
def export_md(cid, filename):
# validação de conversa + usuário
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
conv = db.query(Conversation).filter_by(id=cid, user_email=email).first()
if not conv:
return jsonify({"erro": "Conversa não encontrada."}), 404
msgs = conv.messages
md = f"# {conv.title}\n\n"
if conv.tags:
md += f"**Tags:** {conv.tags}\n\n"
for m in msgs:
md += f"### {m.role.capitalize()}\n\n{m.content}\n\n"
db.close()
#return Response(md, mimetype="text/markdown")
return send_file(md_path, as_attachment=True, download_name=filename)
@app.route("/api/file/<file_id>/<filename>")
@login_required
def get_file(file_id, filename):
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
db = SessionLocal()
f = db.query(File).filter_by(id=file_id, user_email=email).first()
db.close()
if not f:
return jsonify({"erro": "Arquivo não encontrado."}), 404
# Segurança: impedir que o usuário tente baixar outro nome
if filename != f.original_name:
return jsonify({"erro": "Nome de arquivo inválido."}), 400
return send_file(
f.path,
as_attachment=True,
download_name=f.original_name
)
from weasyprint import HTML
import uuid
import pypandoc
from pdfminer.high_level import extract_text
from docx import Document
from io.pandoc import convert_document
@app.route("/api/import/<cid>", methods=["POST"])
@login_required
@rate_limited
def import_file(cid):
file = request.files.get("file")
if not file:
return jsonify({"erro": "Nenhum arquivo enviado"}), 400
email = session["db_user"]["email"] if app.config["AUTH_MODE"] == "db" else session["user"]["email"]
file_id = str(uuid.uuid4())
filename = file.filename
path = f"/data/{file_id}_{filename}"
file.save(path)
ext = file.filename.lower().split(".")[-1]
if ext not in ["doc", "docx", "odt", "txt", "md", "html", "htm", "rtf", "epub"]:
return jsonify({"erro": "Formato não suportado."}), 400
if ext == "pdf":
text = extract_text(path)
elif ext == "docx":
doc = Document(path)
text = "\n".join([p.text for p in doc.paragraphs])
elif ext == "doc":
text = pypandoc.convert_file(path, "md")
else:
return jsonify({"error": "Unsupported format"}), 400
md = pypandoc.convert_text(text, "md", format="plain")
db = SessionLocal()
db.add(File(id=file_id, user_email=email, path=path, original_name=filename))
db.commit()
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="user",
content=f"Arquivo importado: {filename}\n```\n{md}\n```"
)
db.add(msg)
db.commit()
db.close()
return jsonify({"status": "ok", "filename": filename,"imported_as_markdown": True})
@app.route("/api/export/pandoc/<writer>/<cid>/<filename>")
@login_required
def export_pandoc(writer, cid, filename):
if not writer in ["docx","xlsx","pptx","markdown","txt","odt","rtf","epub","html"]:
return jsonify({"erro": "Formato de exportação não suportado."}), 400
email = session["db_user"]["email"] if app.config["AUTH_MODE"] == "db" else session["user"]["email"]
db = SessionLocal()
msgs = (
db.query(Message)
.join(Conversation)
.filter(Conversation.id == cid, Conversation.user_email == email)
.order_by(Message.created_at)
.all()
)
db.close()
text = "\n".join([m.content for m in msgs])
out = f"/data/{uuid.uuid4()}_{filename}"
pypandoc.convert_text(text, writer, format="plain", outputfile=out)
return send_file(out, as_attachment=True, download_name=filename)
@app.route("/api/export/pdf/<cid>/<filename>")
@login_required
def export_pdf(cid, filename):
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
conv = db.query(Conversation).filter_by(id=cid, user_email=email).first()
if not conv:
return jsonify({"erro": "Conversa não encontrada."}), 404
msgs = conv.messages
html = "<h1>{}</h1>".format(conv.title)
if conv.tags:
html += "<p><strong>Tags:</strong> {}</p>".format(conv.tags)
for m in msgs:
html += "<h3>{}</h3><p>{}</p>".format(m.role.capitalize(), m.content)
db.close()
pdf = HTML(string=html).write_pdf()
#return Response(pdf, mimetype="application/pdf")
return send_file(pdf_path, as_attachment=True, download_name=filename)
@app.route("/api/history/<cid>")
@login_required
@rate_limited
def history(cid):
db = SessionLocal()
#msgs = db.query(Message).filter_by(conversation_id=cid, user_email=email).order_by(Message.created_at).all()
msgs = db.query(Message).join(Conversation).filter(
Conversation.id == cid,
Conversation.user_email == email
).all()
data = [{"role": m.role, "content": markdown(m.content)} for m in msgs]
db.close()
return jsonify(data)
@app.route("/api/attachment/<cid>", methods=["POST"])
@login_required
@rate_limited
def upload_attachment(cid):
db = SessionLocal()
file = request.files.get("file")
if not file:
return jsonify({"error": "No file"}), 400
content = file.read().decode("latin1")
att = Attachment(
id=str(uuid.uuid4()),
conversation_id=cid,
filename=file.filename,
mime_type=file.mimetype,
data=content,
)
db.add(att)
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/api/attachments/<cid>")
@login_required
@rate_limited
def list_attachments(cid):
db = SessionLocal()
atts = db.query(Attachment).filter_by(conversation_id=cid, user_email=email).all()
data = [{"id": a.id, "filename": a.filename} for a in atts]
db.close()
return jsonify(data)
@app.route("/api/download/<att_id>")
@login_required
@rate_limited
def download(att_id):
db = SessionLocal()
att = db.query(Attachment).filter_by(id=att_id, user_email=email).first()
db.close()
if not att:
return "Not found", 404
return send_file(att.path, as_attachment=True, download_name=att.filename)
@app.route("/api/file/<file_id>")
@login_required
def get_file(file_id):
db = SessionLocal()
f = db.query(File).filter_by(id=file_id, user_email=email).first()
if not f:
return jsonify({"erro": "Arquivo não encontrado"}), 404
return send_file(f.path)
@app.route("/api/stream/<cid>")
@login_required
def stream(cid):
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
db = SessionLocal()
conv = db.query(Conversation).filter_by(id=cid, user_email=email).first()
db.close()
if not conv:
return jsonify({"erro": "Conversa não encontrada."}), 404
# segue streaming normalmente
# TODO
...
@app.route("/api/stream")
@login_required
@rate_limited
def stream():
cid = request.args.get("conversation_id")
model = request.args.get("model", "llama3.2")
prompt = request.args.get("prompt")
db = SessionLocal()
db.add(Message(id=str(uuid.uuid4()), conversation_id=cid, role="user", content=prompt))
db.commit()
def generate():
url = f"{app.config['OLLAMA_BASE_URL']}/api/generate"
payload = {"model": model, "prompt": prompt, "stream": True}
with requests.post(url, json=payload, stream=True) as r:
buffer = ""
for line in r.iter_lines():
if not line:
continue
try:
data = json.loads(line.decode())
token = data.get("response", "")
buffer += token
# streaming animation: send partial buffer
yield f"data: {json.dumps({'token': token})}\n\n"
except:
continue
db.add(Message(id=str(uuid.uuid4()), conversation_id=cid, role="assistant", content=buffer))
db.commit()
db.close()
return Response(generate(), mimetype="text/event-stream")
@app.route("/api/share/<cid>", methods=["POST"])
@login_required
@require_capability("can_share")
def share(cid):
target_email = request.json.get("email")
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
conv = db.query(Conversation).filter_by(id=cid, user_email=email).first()
if not conv:
return jsonify({"erro": "Conversa não encontrada."}), 404
conv.shared = target_email
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/api/tags/<cid>", methods=["POST"])
@login_required
def update_tags(cid):
tags = request.json.get("tags", [])
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
conv = db.query(Conversation).filter_by(id=cid, user_email=email).first()
if not conv:
return jsonify({"erro": "Conversa não encontrada."}), 404
conv.tags = ",".join(tags)
db.commit()
db.close()
return jsonify({"status": "ok"})
@app.route("/api/search")
@login_required
@rate_limited
def search():
q = request.args.get("q", "").lower()
user_email = session["user"]["email"]
db = SessionLocal()
convs = db.query(Conversation).filter_by(user_email=user_email).all()
results = []
for c in convs:
for m in c.messages:
if q in m.content.lower():
results.append({
"conversation_id": c.id,
"title": c.title,
"role": m.role,
"content": m.content,
"created_at": m.created_at.isoformat()
})
db.close()
return jsonify(results)
from pywhispercpp.model import Model
@app.route("/api/audio/transcribe/<cid>", methods=["POST"])
@login_required
@require_capability("can_use_audio")
def transcribe(cid):
# Initialize the model (automatically downloads 'base.en' if not present)
model = Model('base.en', print_realtime=False, print_progress=False)
file = request.files.get("file")
if not file:
return jsonify({"error": "No audio file"}), 400
path = f"/data/audio-{uuid.uuid4()}.wav"
file.save(path)
# Example using whisper (pseudo-code)
# result = whisper.transcribe(path)
# text = result["text"]
# Transcribe your audio file (must be 16kHz WAV format)
segments = model.transcribe(path)
# Print the text results
for segment in segments:
print(f"[{segment.t0} -> {segment.t1}]: {segment.text}")
text = "Transcribed text placeholder"
db = SessionLocal()
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="user",
content=text,
)
db.add(msg)
db.commit()
db.close()
return jsonify({"status": "ok", "text": text})
from gtts import gTTS
@app.route("/api/audio/synthesize/<cid>", methods=["POST"])
@login_required
@require_capability("can_use_audio")
def synthesize(cid):
text = request.json.get("text", "")
if not text:
return jsonify({"error": "No text"}), 400
tts = gTTS(text=text, lang="en")
path = f"/data/tts-{uuid.uuid4()}.mp3"
tts.save(path)
return send_file(path, mimetype="audio/mpeg", as_attachment=False)
from PIL import Image
@app.route("/api/vision/<cid>", methods=["POST"])
@login_required
@require_capability("can_use_vision")
def vision(cid):
file = request.files.get("file")
if not file:
return jsonify({"error": "No image"}), 400
img_id = str(uuid.uuid4())
path = f"/data/img-{img_id}.png"
file.save(path)
# Here you would call a vision-capable model (pseudo-code).
# description = call_vision_model(path)
description = "Image description placeholder"
db = SessionLocal()
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="assistant",
content=description,
)
db.add(msg)
db.commit()
db.close()
return jsonify({"status": "ok", "description": description})
@app.route("/api/subscribe", methods=["POST"])
@login_required
def subscribe():
data = request.json
plan_id = data["plan_id"]
db = SessionLocal()
if app.config["AUTH_MODE"] == "oauth":
email = session["user"]["email"]
else:
email = session["db_user"]["email"]
sub = Subscription(
id=str(uuid.uuid4()),
user_email=email,
plan_id=plan_id,
status="active",
renew_at=datetime.utcnow() + timedelta(days=30)
)
user = db.query(User).filter_by(email=email).first()
user.plan_id = plan_id
db.add(sub)
db.commit()
db.close()
return jsonify({"status": "ok"})
# ===========
# IO
from io.document import convert_document
@app.route("/api/import/d/<cid>", methods=["POST"])
@login_required
def import_document(cid):
file = request.files.get("file")
if not file:
return jsonify({"erro": "Nenhum arquivo enviado."}), 400
ext = file.filename.lower().split(".")[-1]
if ext not in ["doc", "docx", "odt", "txt", "md", "html", "htm"]:
return jsonify({"erro": "Formato não suportado."}), 400
email = session["db_user"]["email"] if app.config["AUTH_MODE"] == "db" else session["user"]["email"]
temp_path = f"/data/{uuid.uuid4()}.{ext}"
file.save(temp_path)
text = convert_document(temp_path)
# Convert to TSV (single column)
tsv = "\x09".join([normalize_newlines(text)])
filename = f"{file.filename}.tsv"
file_id = str(uuid.uuid4())
path = f"/data/{file_id}_{filename}"
with open(path, "w", encoding="utf-8") as f:
f.write(tsv)
db = SessionLocal()
db.add(File(
id=file_id,
user_email=email,
path=path,
original_name=filename
))
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="user",
content=f"Arquivo importado: {filename}\n```\n{tsv}\n```"
)
db.add(msg)
db.commit()
db.close()
return jsonify({"status": "ok", "filename": filename})
@app.route("/api/export/txt/<cid>/<filename>")
@login_required
def export_txt(cid, filename):
email = session["db_user"]["email"] if app.config["AUTH_MODE"] == "db" else session["user"]["email"]
db = SessionLocal()
msgs = (
db.query(Message)
.join(Conversation)
.filter(Conversation.id == cid, Conversation.user_email == email)
.order_by(Message.created_at)
.all()
)
db.close()
text = "\n".join([m.content for m in msgs])
out = f"/data/{uuid.uuid4()}_{filename}"
with open(out, "w", encoding="utf-8") as f:
f.write(text)
return send_file(out, as_attachment=True, download_name=filename)
@app.route("/api/export/md/<cid>/<filename>")
@login_required
def export_md(cid, filename):
# similar to TXT, but wrap content in Markdown
...
@app.route("/api/export/html/<cid>/<filename>")
@login_required
def export_html(cid, filename):
# wrap messages in <p> tags
...
from docx import Document
@app.route("/api/export/docx/<cid>/<filename>")
@login_required
def export_docx(cid, filename):
email = session["db_user"]["email"] if app.config["AUTH_MODE"] == "db" else session["user"]["email"]
db = SessionLocal()
msgs = (
db.query(Message)
.join(Conversation)
.filter(Conversation.id == cid, Conversation.user_email == email)
.order_by(Message.created_at)
.all()
)
db.close()
doc = Document()
for m in msgs:
doc.add_paragraph(m.content)
out = f"/data/{uuid.uuid4()}_{filename}"
doc.save(out)
return send_file(out, as_attachment=True, download_name=filename)
@app.route("/api/export/odt/<cid>/<filename>")
@login_required
def export_odt(cid, filename):
# similar approach using odfpy
...
from io.spreadsheet import convert_excel, convert_excel_all_sheets
@app.route("/api/import/s/<cid>", methods=["POST"])
@login_required
def import_excel(cid):
file = request.files.get("file")
if not file:
return jsonify({"error": "No file"}), 400
ext = file.filename.lower().split(".")[-1]
if ext not in ["xlsx", "xls", "ods"]:
return jsonify({"error": "Unsupported format"}), 400
temp_path = f"/data/{uuid.uuid4()}.{ext}"
file.save(temp_path)
# tsv = convert_excel(temp_path)
# Convert all sheets
sheets = convert_excel_all_sheets(temp_path)
db = SessionLocal()
# Each sheet becomes a separate TSV message
for sheet_name, tsv in sheets.items():
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="user",
content=f"Arquivo importado: {sheet_name}.tsv\n```\n{tsv}\n```"
)
db.add(msg)
# return jsonify({"status": "ok"})
return jsonify({"status": "ok", "sheets": list(sheets.keys())})
from io.presentation import convert_presentation
@app.route("/api/import/p/<cid>", methods=["POST"])
@login_required
def import_presentation(cid):
file = request.files.get("file")
if not file:
return jsonify({"erro": "Nenhum arquivo enviado."}), 400
ext = file.filename.lower().split(".")[-1]
if ext not in ["ppt", "pptx", "odp"]:
return jsonify({"erro": "Formato não suportado."}), 400
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
temp_path = f"/data/{uuid.uuid4()}.{ext}"
file.save(temp_path)
slides = convert_presentation(temp_path)
db = SessionLocal()
for slide_name, content in slides:
tsv = "\x09".join([normalize_newlines(content)])
filename = f"{slide_name}.tsv"
file_id = str(uuid.uuid4())
path = f"/data/{file_id}_{filename}"
with open(path, "w", encoding="utf-8") as f:
f.write(tsv)
db.add(File(
id=file_id,
user_email=email,
path=path,
original_name=filename
))
msg = Message(
id=str(uuid.uuid4()),
conversation_id=cid,
role="user",
content=f"Slide importado: {filename}\n```\n{tsv}\n```"
)
db.add(msg)
db.commit()
db.close()
return jsonify({"status": "ok", "slides": [s[0] for s in slides]})
from pptx import Presentation
@app.route("/api/export/pptx/<cid>/<filename>")
@login_required
def export_pptx(cid, filename):
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
db = SessionLocal()
msgs = (
db.query(Message)
.join(Conversation)
.filter(Conversation.id == cid, Conversation.user_email == email)
.order_by(Message.created_at)
.all()
)
db.close()
prs = Presentation()
for m in msgs:
slide = prs.slides.add_slide(prs.slide_layouts[1])
body = slide.shapes.placeholders[1]
tf = body.text_frame
tf.text = m.content
out = f"/data/{uuid.uuid4()}_{filename}"
prs.save(out)
return send_file(out, as_attachment=True, download_name=filename)
@app.route("/api/export/odp/<cid>/<filename>")
@login_required
def export_odp(cid, filename):
# similar approach using odfpy
# create ODP with each message as a slide
...
# ================
# TASKS
# ================
from tasks import generate_task
@app.route("/api/queue/generate", methods=["POST"])
@login_required
def queue_generate():
data = request.json
cid = data["conversation_id"]
model = data["model"]
prompt = data["prompt"]
task = generate_task.delay(cid, model, prompt)
return jsonify({"task_id": task.id})
@app.route("/api/generate", methods=["POST"])
@login_required
def generate():
data = request.json
cid = data["conversation_id"]
model = data["model"]
prompt = data["prompt"]
db = SessionLocal()
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
user = db.query(User).filter_by(email=email).first()
db.close()
#task = enqueue_by_priority(user, cid, model, prompt)
task = enqueue_by_priority(user.email, cid, model, prompt)
return jsonify({"task_id": task.id})
from celery_app import celery
@app.route("/api/queue/status/<task_id>")
@login_required
def queue_status(task_id):
result = celery.AsyncResult(task_id)
return jsonify({
"task_id": task_id,
"state": result.state,
"result": result.result if result.ready() else None
})
def enqueue_by_priority(user, cid, model, prompt):
if user.priority == "system_admin":
return system_admin_generate.delay(cid, model, prompt)
if user.priority == "corp_admin":
return corp_admin_generate.delay(cid, model, prompt)
return user_generate.delay(cid, model, prompt)
def enqueue_task(model, prompt, cid, user):
db = SessionLocal()
plan = db.query(Plan).filter_by(id=user.plan_id).first()
db.close()
queue = plan.priority_queue # default, premium, admin
if queue == "admin":
return admin_generate.delay(cid, model, prompt)
if queue == "premium":
return premium_generate.delay(cid, model, prompt)
return generate_task.delay(cid, model, prompt)
# ***
import json
import requests
from flask import Flask, Response, request, jsonify, session, redirect, url_for
from authlib.integrations.flask_client import OAuth
from functools import wraps
from config import Config
from chat_store import ChatStore
app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config["SECRET_KEY"]
store = ChatStore()
oauth = OAuth(app)
google = oauth.register(
name="google",
client_id=app.config["GOOGLE_CLIENT_ID"],
client_secret=app.config["GOOGLE_CLIENT_SECRET"],
access_token_url="https://oauth2.googleapis.com/token",
authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
api_base_url="https://www.googleapis.com/oauth2/v2/",
client_kwargs={"scope": "openid email profile"},
)
def login_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
if "user" not in session:
return redirect(url_for("login"))
return f(*args, **kwargs)
return wrapper
@app.route("/login")
def login():
return google.authorize_redirect(app.config["GOOGLE_REDIRECT_URI"])
@app.route("/oauth/callback")
def oauth_callback():
token = google.authorize_access_token()
user_info = google.get("userinfo").json()
session["user"] = user_info
return redirect("/")
@app.route("/api/new_conversation")
@login_required
def new_conversation():
cid = store.new_conversation()
return jsonify({"conversation_id": cid})
@app.route("/api/history/<cid>")
@login_required
def history(cid):
return jsonify(store.get_messages(cid))
@app.route("/api/stream")
@login_required
def stream():
cid = request.args.get("conversation_id")
model = request.args.get("model", "llama3.2")
prompt = request.args.get("prompt")
store.add_message(cid, "user", prompt)
def generate():
url = f"{app.config['OLLAMA_BASE_URL']}/api/generate"
payload = {"model": model, "prompt": prompt, "stream": True}
with requests.post(url, json=payload, stream=True) as r:
for line in r.iter_lines():
if not line:
continue
try:
data = json.loads(line.decode())
token = data.get("response", "")
store.add_message(cid, "assistant", token)
yield f"data: {json.dumps({'token': token})}\n\n"
except:
continue
return Response(generate(), mimetype="text/event-stream")
# ***
import os
import json
import requests
from functools import wraps
from flask import Flask, request, jsonify, redirect, session, url_for
from authlib.integrations.flask_client import OAuth
from config import Config
# All comments are in English.
app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config["SECRET_KEY"]
oauth = OAuth(app)
google = oauth.register(
name="google",
client_id=app.config["GOOGLE_CLIENT_ID"],
client_secret=app.config["GOOGLE_CLIENT_SECRET"],
access_token_url="https://oauth2.googleapis.com/token",
authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
authorize_params={"access_type": "offline", "prompt": "consent"},
api_base_url="https://www.googleapis.com/oauth2/v2/",
client_kwargs={"scope": "openid email profile"},
)
def login_required(f):
"""Decorator to ensure the user is authenticated via Google OAuth."""
@wraps(f)
def wrapper(*args, **kwargs):
if "user" not in session:
return redirect(url_for("login"))
return f(*args, **kwargs)
return wrapper
@app.route("/login")
def login():
"""Start Google OAuth login flow."""
return google.authorize_redirect(app.config["Google_REDIRECT_URI"])
@app.route("/oauth/callback")
def oauth_callback():
"""Handle Google OAuth callback and store user session."""
token = google.authorize_access_token()
user_info = google.get("userinfo").json()
session["user"] = {
"email": user_info.get("email"),
"name": user_info.get("name"),
}
return redirect(url_for("home"))
@app.route("/")
def home():
return jsonify({
"message": "Ollama Gateway is running.",
"domain": "https://safira.renatorosa.com",
"auth": "Google OAuth required for /api/* endpoints.",
"endpoints": {
"health": "/health",
"generate": "/api/generate"
}
})
@app.route("/health")
def health():
return jsonify({"status": "ok"})
@app.route("/api/generate", methods=["POST"])
@login_required
def generate():
data = request.get_json(silent=True) or {}
model_name = data.get("model", "llama3.2")
prompt = data.get("prompt")
if not prompt:
return jsonify({"error": "Missing 'prompt' field."}), 400
model_id = app.config["MODEL_MAP"].get(model_name)
if not model_id:
return jsonify({"error": f"Unsupported model '{model_name}'."}), 400
# ollama_url = f"{app.config['OLLAMA_BASE_URL']}/api/generate"
# payload = {"model": model_id, "prompt": prompt}
# try:
# response = requests.post(ollama_url, json=payload, timeout=60)
# response.raise_for_status()
# except requests.RequestException as e:
# return jsonify({"error": "Failed to call Ollama API.", "details": str(e)}), 502
# try:
# ollama_data = response.json()
# except json.JSONDecodeError:
# return jsonify({"error": "Invalid JSON response from Ollama."}), 502
#output_text = ollama_data.get("response") or ollama_data.get("output") or ""
#return jsonify({"model": model_name, "output": output_text})
email = session["user"]["email"] if app.config["AUTH_MODE"] == "oauth" else session["db_user"]["email"]
cached = get_cache(email, model, prompt)
if cached:
return jsonify({"model": model, "output": cached, "cached": True})
result, used_model = call_ollama_with_fallback({"prompt": prompt})
output = result.get("response")
set_cache(email, used_model, prompt, output)
return jsonify({"model": used_model, "output": output, "cached": False})
@app.route("/api/multi", methods=["POST"])
@login_required
@rate_limited
def multi():
data = request.json
models = data.get("models", ["llama3.2", "gemma4"])
prompt = data.get("prompt")
results = {}
for m in models:
try:
r = requests.post(
f"{app.config['OLLAMA_BASE_URL']}/api/generate",
json={"model": m, "prompt": prompt}
)
r.raise_for_status()
results[m] = r.json().get("response")
except Exception as e:
results[m] = f"Error: {str(e)}"
return jsonify(results)
@app.route("/api/multi/stream")
@login_required
def multi_stream():
models = request.args.get("models", "llama3.2,gemma4").split(",")
prompt = request.args.get("prompt")
def generate():
for m in models:
yield f"event: model\n"
yield f"data: {json.dumps({'model': m})}\n\n"
url = f"{app.config['OLLAMA_BASE_URL']}/api/generate"
payload = {"model": m, "prompt": prompt, "stream": True}
with requests.post(url, json=payload, stream=True) as r:
for line in r.iter_lines():
if not line:
continue
data = json.loads(line.decode())
token = data.get("response", "")
yield f"event: token\n"
yield f"data: {json.dumps({'model': m, 'token': token})}\n\n"
return Response(generate(), mimetype="text/event-stream")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
# ***
import os
import json
import requests
from functools import wraps
from flask import Flask, request, jsonify, redirect, session, url_for
from authlib.integrations.flask_client import OAuth
from config import Config
# All comments are in English.
app = Flask(__name__)
app.config.from_object(Config)
# Configure session secret key.
app.secret_key = app.config["SECRET_KEY"]
# Configure OAuth with Google.
oauth = OAuth(app)
google = oauth.register(
name="google",
client_id=app.config["GOOGLE_CLIENT_ID"],
client_secret=app.config["GOOGLE_CLIENT_SECRET"],
access_token_url="https://oauth2.googleapis.com/token",
access_token_params=None,
authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
authorize_params={
"access_type": "offline",
"prompt": "consent",
},
api_base_url="https://www.googleapis.com/oauth2/v2/",
client_kwargs={"scope": "openid email profile"},
)
def login_required(f):
"""Decorator to ensure the user is authenticated via Google OAuth."""
@wraps(f)
def wrapper(*args, **kwargs):
# Check if user info is stored in session.
if "user" not in session:
# Redirect to login if not authenticated.
return redirect(url_for("login"))
return f(*args, **kwargs)
return wrapper
@app.route("/login")
def login():
"""Start Google OAuth login flow."""
redirect_uri = app.config["GOOGLE_REDIRECT_URI"]
# Use the configured redirect URI for OAuth callback.
return google.authorize_redirect(redirect_uri)
@app.route("/oauth/callback")
def oauth_callback():
"""Handle Google OAuth callback and store user session."""
# Exchange authorization code for tokens.
token = google.authorize_access_token()
# Fetch user info from Google.
resp = google.get("userinfo")
user_info = resp.json()
# Store minimal user info in session.
session["user"] = {
"email": user_info.get("email"),
"name": user_info.get("name"),
}
# Redirect to a simple home or API docs page.
return redirect(url_for("home"))
@app.route("/")
def home():
"""Simple home endpoint explaining the service."""
# This endpoint is intentionally simple and easy to understand.
return jsonify(
{
"message": "Ollama Gateway is running.",
"auth": "Google OAuth required for /api/* endpoints.",
"endpoints": {
"health": "/health",
"generate": "/api/generate",
},
}
)
@app.route("/health")
def health():
"""Health check endpoint."""
return jsonify({"status": "ok"})
@app.route("/api/generate", methods=["POST"])
@login_required
def generate():
"""Generate text using a selected Ollama model.
Expected JSON body:
{
"model": "llama3.2",
"prompt": "Your prompt here"
}
"""
data = request.get_json(silent=True) or {}
# Extract model and prompt from request.
model_name = data.get("model", "llama3.2")
prompt = data.get("prompt")
if not prompt:
return jsonify({"error": "Missing 'prompt' field."}), 400
# Map user-facing model name to Ollama model identifier.
model_id = app.config["MODEL_MAP"].get(model_name)
if not model_id:
return jsonify({"error": f"Unsupported model '{model_name}'."}), 400
# Prepare request to Ollama API.
ollama_url = f"{app.config['OLLAMA_BASE_URL']}/api/generate"
payload = {
"model": model_id,
"prompt": prompt,
# You can add more Ollama-specific parameters here if needed.
}
try:
# Call Ollama HTTP API.
response = requests.post(ollama_url, json=payload, timeout=60)
response.raise_for_status()
except requests.RequestException as e:
# Handle network or API errors gracefully.
return jsonify({"error": "Failed to call Ollama API.", "details": str(e)}), 502
# Parse Ollama response.
try:
ollama_data = response.json()
except json.JSONDecodeError:
return jsonify({"error": "Invalid JSON response from Ollama."}), 502
# Extract output text (this depends on Ollama's response format).
output_text = ollama_data.get("response") or ollama_data.get("output") or ""
return jsonify(
{
"model": model_name,
"output": output_text,
}
)
if __name__ == "__main__":
# This block is mainly for local debugging.
# In production, Gunicorn will run the app.
app.run(host="0.0.0.0", port=8000)
# ***
from flask import Flask, request, jsonify
from flask_cors import CORS
from db import Base, engine
from models import User
from services.intent_service import IntentService
from services.mode_service import ModeService
from services.preference_service import PreferenceService
from services.suggestion_service import SuggestionService
from services.document_service import DocumentService
from services.search_service import SearchService
from services.monitoring_service import MonitoringService
from services.export_service import ExportService
from services.audit_service import AuditService
from services.security_service import SecurityService
from services.ollama_service import OllamaService
ollama_service = OllamaService()
from services.model_router import ModelRouter
router = ModelRouter()
generated = router.generate(intent, mode, prompt)
app = Flask(__name__)
CORS(app)
Base.metadata.create_all(bind=engine)
intent_service = IntentService()
mode_service = ModeService()
preference_service = PreferenceService()
suggestion_service = SuggestionService()
document_service = DocumentService()
search_service = SearchService()
monitoring_service = MonitoringService()
export_service = ExportService()
audit_service = AuditService()
security_service = SecurityService()
USER_ID = 1
@app.route("/api/conversation", methods=["POST"])
def conversation():
data = request.json
message = data.get("message", "")
mode = mode_service.get_current_mode(USER_ID)
if not mode:
reply = (
"{user.name}, como você quer trabalhar hoje?\n\n"
"- Modo Advogado\n- Modo Corporativo\n- Apenas Conversar\n\n"
"Me diga qual modo você prefere agora."
)
return jsonify({"reply": reply, "suggestions": [], "quick_actions": []})
intent = intent_service.detect_intent(message)
prefs = preference_service.get_preferences(USER_ID)
if intent == "set_mode":
mode_service.set_mode(USER_ID, intent_service.extract_mode(message))
reply = f"Perfeito, vou atuar no modo {mode_service.get_current_mode(USER_ID)}. O que vamos fazer agora?"
return jsonify({"reply": reply, "suggestions": [], "quick_actions": []})
if intent == "document":
response = document_service.handle(message, mode, prefs)
elif intent == "search":
response = search_service.handle(message, mode, prefs)
elif intent == "monitoring":
response = monitoring_service.handle(message, mode, prefs)
else:
response = document_service.chat_like(message, mode, prefs)
suggestions = suggestion_service.generate_suggestions(response, intent, mode, prefs)
quick_actions = suggestion_service.generate_quick_actions(response, intent, mode, prefs)
return jsonify({
"reply": response["text"],
"suggestions": suggestions,
"quick_actions": quick_actions
})
if __name__ == "__main__":
app.run(debug=True)