Files
odoo-ai/addons/activeblue_ai/controllers/approval.py
Carlos Garcia 7a0aad3f37 fix: three bugs blocking bot presence and approval UI
1. OdooClient missing self._timeout — every _xmlrpc_call raised
   AttributeError, making the odoo health check permanently fail.
   Fix: set self._timeout = XMLRPC_TIMEOUT in __init__.

2. action_ping only accepted ollama=='ok' but health.py now returns
   'warming' when the model is not yet hot in VRAM. Fix: treat
   warming as passing so the bot goes online and the model loads
   on the first real request.

3. /ai/approval/pending declared methods=['GET'] on a type='json'
   route — Odoo JSON-RPC always POSTs, so every browser call got
   405 METHOD NOT ALLOWED. Fix: change to methods=['POST'].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 20:53:49 -04:00

99 lines
3.9 KiB
Python

import json
import logging
import requests
from odoo import http
from odoo.http import request, Response
_logger = logging.getLogger(__name__)
class AiApprovalController(http.Controller):
@http.route('/ai/approval/pending', type='json', auth='user', methods=['POST'])
def list_pending(self):
if not request.env.user.has_group('activeblue_ai.group_ai_manager'):
return {'error': 'Access denied', 'items': []}
bot = request.env['ab.ai.bot'].sudo().search([('active', '=', True)], limit=1)
if not bot:
return {'items': []}
url = bot._get_service_url() + '/approval/pending'
try:
resp = requests.get(url, headers=bot._build_headers(), timeout=10)
resp.raise_for_status()
return {'items': resp.json()}
except Exception as exc:
_logger.error('list_pending failed: %s', exc)
return {'error': str(exc), 'items': []}
@http.route('/ai/approval/respond', type='json', auth='user', methods=['POST'])
def respond(self, directive_id, approved, note=None):
if not request.env.user.has_group('activeblue_ai.group_ai_manager'):
return {'error': 'Access denied'}
bot = request.env['ab.ai.bot'].sudo().search([('active', '=', True)], limit=1)
if not bot:
return {'error': 'No bot configured'}
url = bot._get_service_url() + '/approval/respond'
payload = {
'directive_id': directive_id,
'approved': approved,
'approver_id': str(request.env.user.id),
'note': note or '',
}
try:
resp = requests.post(url, json=payload, headers=bot._build_headers(), timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as exc:
_logger.error('respond approval failed: %s', exc)
return {'error': str(exc)}
@http.route('/ai/chat', type='json', auth='user', methods=['POST'])
def chat(self, message, context=None, session_id=None):
bot = request.env['ab.ai.bot'].sudo().get_active_bot()
result = bot.dispatch_message(
user_id=request.env.user.id,
message=message,
context=context or {},
session_id=session_id,
)
return result
@http.route('/ai/upload', type='http', auth='user', methods=['POST'], csrf=False)
def upload(self, **kwargs):
bot = request.env['ab.ai.bot'].sudo().get_active_bot()
if not bot:
return Response(
json.dumps({'error': 'No bot configured'}),
content_type='application/json', status=503)
url = bot._get_service_url() + '/upload'
message = request.httprequest.form.get(
'message', 'Create an employee expense report from these receipts.')
session_id = request.httprequest.form.get('session_id', '')
files_data = [
('files', (f.filename, f.read(), f.content_type or 'application/octet-stream'))
for f in request.httprequest.files.getlist('files')
]
try:
resp = requests.post(
url,
data={
'user_id': str(request.env.user.id),
'message': message,
'session_id': session_id,
},
files=files_data or [('files', ('empty', b'', 'application/octet-stream'))],
headers=bot._build_headers(),
timeout=120,
)
resp.raise_for_status()
return Response(resp.text, content_type='application/json')
except Exception as exc:
_logger.error('upload proxy failed: %s', exc)
return Response(
json.dumps({'error': str(exc), 'reply': 'Upload failed. Please try again.'}),
content_type='application/json', status=500)