Add AI stack configuration: ELAI avatars, system params, training tips import
- Add ELAI avatar seed data (7 avatars with codes, URLs, voice configs) from the original backend's avatars.json - Add missing system parameters: encoach.aws_region (eu-west-1), encoach.whisper_workers (4) - Add training tips import script with pathways_2_rw.json data source - Add action_compute_embeddings() method to training tip model for computing sentence-transformer embeddings on demand Made-with: Cursor
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
"depends": ["encoach_ai"],
|
"depends": ["encoach_ai"],
|
||||||
"data": [
|
"data": [
|
||||||
"security/ir.model.access.csv",
|
"security/ir.model.access.csv",
|
||||||
|
"data/elai_avatars.xml",
|
||||||
"views/avatar_views.xml",
|
"views/avatar_views.xml",
|
||||||
],
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
|
|||||||
74
encoach_ai_media/data/elai_avatars.xml
Normal file
74
encoach_ai_media/data/elai_avatars.xml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="avatar_gia" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Gia</field>
|
||||||
|
<field name="avatar_code">gia.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/gia/business/gia_business.png</field>
|
||||||
|
<field name="gender">female</field>
|
||||||
|
<field name="canvas">https://elai-avatars.s3.us-east-2.amazonaws.com/common/gia/business/gia_business.png</field>
|
||||||
|
<field name="voice_id">EXAVITQu4vr4xnSDxMaL</field>
|
||||||
|
<field name="voice_provider">elevenlabs</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_vadim" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Vadim</field>
|
||||||
|
<field name="avatar_code">vadim.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/vadim/business/vadim_business.png</field>
|
||||||
|
<field name="gender">male</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/vadim/business/vadim_business.png</field>
|
||||||
|
<field name="voice_id">flq6f7yk4E4fJM5XTYuZ</field>
|
||||||
|
<field name="voice_provider">elevenlabs</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_orhan" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Orhan</field>
|
||||||
|
<field name="avatar_code">orhan.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/orhan/business/orhan.png</field>
|
||||||
|
<field name="gender">male</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/orhan/business/orhan.png</field>
|
||||||
|
<field name="voice_id">en-US-AndrewMultilingualNeural</field>
|
||||||
|
<field name="voice_provider">azure</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_flora" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Flora</field>
|
||||||
|
<field name="avatar_code">flora.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/flora/business/flora_business.png</field>
|
||||||
|
<field name="gender">female</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/flora/business/flora_business.png</field>
|
||||||
|
<field name="voice_id">en-US-JaneNeural</field>
|
||||||
|
<field name="voice_provider">azure</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_scarlett" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Scarlett</field>
|
||||||
|
<field name="avatar_code">scarlett.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/scarlett/business/scarlett_business.png</field>
|
||||||
|
<field name="gender">female</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/scarlett/business/scarlett_business.png</field>
|
||||||
|
<field name="voice_id">en-US-NancyNeural</field>
|
||||||
|
<field name="voice_provider">azure</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_parker" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Parker</field>
|
||||||
|
<field name="avatar_code">parker.casual</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/parker/casual/parker_casual.png</field>
|
||||||
|
<field name="gender">male</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/parker/casual/parker_casual.png</field>
|
||||||
|
<field name="voice_id">en-US-TonyNeural</field>
|
||||||
|
<field name="voice_provider">azure</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="avatar_ethan" model="encoach.elai.avatar">
|
||||||
|
<field name="name">Ethan</field>
|
||||||
|
<field name="avatar_code">ethan.business</field>
|
||||||
|
<field name="avatar_url">https://elai-avatars.s3.us-east-2.amazonaws.com/common/ethan/business/ethan_business_low.png</field>
|
||||||
|
<field name="gender">male</field>
|
||||||
|
<field name="canvas">https://d3u63mhbhkevz8.cloudfront.net/common/ethan/business/ethan_business_low.png</field>
|
||||||
|
<field name="voice_id">en-US-JasonNeural</field>
|
||||||
|
<field name="voice_provider">azure</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -32,6 +32,19 @@
|
|||||||
<field name="key">encoach.gptzero_api_key</field>
|
<field name="key">encoach.gptzero_api_key</field>
|
||||||
<field name="value"></field>
|
<field name="value"></field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- AWS region for Polly TTS -->
|
||||||
|
<record id="param_aws_region" model="ir.config_parameter">
|
||||||
|
<field name="key">encoach.aws_region</field>
|
||||||
|
<field name="value">eu-west-1</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Whisper STT worker count -->
|
||||||
|
<record id="param_whisper_workers" model="ir.config_parameter">
|
||||||
|
<field name="key">encoach.whisper_workers</field>
|
||||||
|
<field name="value">4</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="param_stripe_secret" model="ir.config_parameter">
|
<record id="param_stripe_secret" model="ir.config_parameter">
|
||||||
<field name="key">encoach.stripe_secret_key</field>
|
<field name="key">encoach.stripe_secret_key</field>
|
||||||
<field name="value"></field>
|
<field name="value"></field>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
from odoo import models, fields
|
import logging
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
from odoo import api, models, fields
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EncoachTrainingTip(models.Model):
|
class EncoachTrainingTip(models.Model):
|
||||||
@@ -20,3 +25,24 @@ class EncoachTrainingTip(models.Model):
|
|||||||
)
|
)
|
||||||
content = fields.Text(required=True)
|
content = fields.Text(required=True)
|
||||||
embedding = fields.Binary(string="FAISS Embedding Vector")
|
embedding = fields.Binary(string="FAISS Embedding Vector")
|
||||||
|
|
||||||
|
def action_compute_embeddings(self):
|
||||||
|
"""Compute sentence-transformer embeddings for tips missing them."""
|
||||||
|
try:
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
import numpy as np
|
||||||
|
except ImportError:
|
||||||
|
_logger.error("sentence-transformers not installed; cannot compute embeddings")
|
||||||
|
return
|
||||||
|
|
||||||
|
tips = self.search([("embedding", "=", False)])
|
||||||
|
if not tips:
|
||||||
|
_logger.info("All tips already have embeddings")
|
||||||
|
return
|
||||||
|
|
||||||
|
model = SentenceTransformer("all-MiniLM-L6-v2")
|
||||||
|
for tip in tips:
|
||||||
|
vec = model.encode([tip.content]).astype(np.float32)[0]
|
||||||
|
tip.embedding = pickle.dumps(vec)
|
||||||
|
|
||||||
|
_logger.info("Computed embeddings for %d tips", len(tips))
|
||||||
|
|||||||
76
scripts/import_training_tips.py
Normal file
76
scripts/import_training_tips.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""Import training tips from pathways JSON into encoach.training.tip model.
|
||||||
|
|
||||||
|
Run inside the Odoo container via:
|
||||||
|
odoo shell -d encoach < /mnt/custom/scripts/import_training_tips.py
|
||||||
|
|
||||||
|
This script:
|
||||||
|
1. Reads pathways_2_rw.json
|
||||||
|
2. Creates encoach.training.tip records (tip_id, category, content)
|
||||||
|
3. Computes sentence-transformer embeddings and stores them
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
JSON_PATH = os.path.join(SCRIPT_DIR, "pathways_2_rw.json")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with open(JSON_PATH, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
TipModel = env["encoach.training.tip"].sudo()
|
||||||
|
|
||||||
|
tips_data = []
|
||||||
|
for unit in data["units"]:
|
||||||
|
for page in unit["pages"]:
|
||||||
|
for tip in page["tips"]:
|
||||||
|
category = tip["category"].lower().replace(" ", "_")
|
||||||
|
tips_data.append({
|
||||||
|
"tip_id": tip["id"],
|
||||||
|
"category": category,
|
||||||
|
"content": tip["text"],
|
||||||
|
})
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
skipped = 0
|
||||||
|
for td in tips_data:
|
||||||
|
existing = TipModel.search([("tip_id", "=", td["tip_id"])], limit=1)
|
||||||
|
if existing:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
TipModel.create(td)
|
||||||
|
created += 1
|
||||||
|
|
||||||
|
env.cr.commit()
|
||||||
|
print(f"Tips import complete: {created} created, {skipped} skipped (already exist)")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
print("Computing embeddings with all-MiniLM-L6-v2...")
|
||||||
|
model = SentenceTransformer("all-MiniLM-L6-v2")
|
||||||
|
|
||||||
|
tips = TipModel.search([("embedding", "=", False)])
|
||||||
|
if not tips:
|
||||||
|
print("All tips already have embeddings.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, tip in enumerate(tips):
|
||||||
|
vec = model.encode([tip.content]).astype(np.float32)[0]
|
||||||
|
tip.embedding = pickle.dumps(vec)
|
||||||
|
if (i + 1) % 10 == 0:
|
||||||
|
print(f" Embedded {i + 1}/{len(tips)} tips...")
|
||||||
|
|
||||||
|
env.cr.commit()
|
||||||
|
print(f"Embeddings computed for {len(tips)} tips.")
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print("WARNING: sentence-transformers not available. Tips created without embeddings.")
|
||||||
|
print("Embeddings can be computed later by re-running this script.")
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
7579
scripts/pathways_2_rw.json
Normal file
7579
scripts/pathways_2_rw.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user