| import os |
| import json |
| import yaml |
| import traceback |
| import gradio as gr |
| import matplotlib.pyplot as plt |
| import numpy as np |
|
|
| from engine.loader import load_persona |
| from engine.drift import apply_objection_effects |
| from engine.responder import generate_persona_turn |
| from engine.coach import generate_coach_feedback |
| from engine.memory import SessionMemory |
| from engine.logger import log_transcript |
| from engine.utils import safe_log |
|
|
| |
| PERSONA_DIR = "./personas" |
| OBJECTIONS_PATH = "./stimuli/objections.json" |
| ERROR_LOG_PATH = "./salesiq_errors.log" |
|
|
| |
| session_memory = SessionMemory() |
|
|
| def get_persona_choices(): |
| try: |
| return [f for f in os.listdir(PERSONA_DIR) if f.endswith(".yml")] |
| except Exception: |
| return [] |
|
|
| def get_objection_choices(): |
| try: |
| with open(OBJECTIONS_PATH, "r") as f: |
| data = json.load(f) |
| return [o["objection"] for o in data] |
| except Exception as e: |
| safe_log("Objection load error", str(e)) |
| return [] |
|
|
| def plot_traits(state): |
| |
| traits = [k for k, v in state.items() if isinstance(v, (int, float, float))] |
| if not traits: |
| traits = ["openness"] |
| state = {"openness": 0.0} |
|
|
| values = [float(state.get(t, 0.0)) for t in traits] |
| angles = np.linspace(0, 2 * np.pi, len(traits), endpoint=False).tolist() |
| values += values[:1] |
| angles += angles[:1] |
|
|
| fig, ax = plt.subplots(figsize=(4, 4), subplot_kw=dict(polar=True)) |
| ax.plot(angles, values, color="green", linewidth=2) |
| ax.fill(angles, values, color="green", alpha=0.25) |
| ax.set_xticks(angles[:-1]) |
| ax.set_xticklabels(traits) |
| ax.set_yticklabels([]) |
| ax.set_title("Dynamic Client Trait Profile", fontsize=12) |
| fig.tight_layout() |
|
|
| chart_path = "./trait_chart.png" |
| fig.savefig(chart_path) |
| plt.close(fig) |
| return chart_path |
|
|
| def simulate(rep_input, selected_objection, selected_persona_file): |
| try: |
| |
| persona_path = os.path.join(PERSONA_DIR, selected_persona_file) |
| persona = load_persona(persona_path) |
|
|
| with open(OBJECTIONS_PATH, "r") as f: |
| objections = json.load(f) |
| objection = next((o for o in objections if o["objection"] == selected_objection), None) |
|
|
| |
| session_memory.ensure_session(persona.get("name", selected_persona_file)) |
|
|
| |
| if objection: |
| apply_objection_effects(persona, objection) |
|
|
| |
| persona_response, persona_question = generate_persona_turn( |
| rep_input=rep_input, |
| persona=persona, |
| objection=selected_objection, |
| history=session_memory.get_history(persona.get("name", selected_persona_file)) |
| ) |
|
|
| |
| session_key = persona.get("name", selected_persona_file) |
| session_memory.add_exchange( |
| session_key=session_key, |
| rep_input=rep_input, |
| persona_response=persona_response, |
| persona_question=persona_question, |
| objection=selected_objection, |
| traits_snapshot=persona.get("dynamic_state", {}) |
| ) |
|
|
| |
| coach_feedback = generate_coach_feedback( |
| rep_input=rep_input, |
| persona_response=persona_response, |
| persona_question=persona_question, |
| persona=persona, |
| history=session_memory.get_history(session_key) |
| ) |
|
|
| |
| state_yaml = yaml.dump(persona.get("dynamic_state", {}), sort_keys=False) |
| chart_path = plot_traits(persona.get("dynamic_state", {})) |
|
|
| |
| log_transcript( |
| persona=persona, |
| rep_input=rep_input, |
| objection=selected_objection, |
| persona_response=persona_response, |
| persona_question=persona_question, |
| coach_feedback=coach_feedback |
| ) |
|
|
| return persona_response, persona_question, coach_feedback, state_yaml, chart_path |
|
|
| except Exception: |
| error_msg = traceback.format_exc() |
| safe_log("Simulation error", error_msg) |
| return "[ERROR] Simulation failed. Check logs.", "", "", "", None |
|
|
| with gr.Blocks(title="SalesIQ: Client Simulation with Coaching") as ui: |
| gr.Markdown("## 💼 SalesIQ: Client Simulation with Coaching, a Lieberman, Inc/Empire Nexus Collaboration") |
| gr.Markdown("Practice sales conversations with dynamic client personas, editable objections, and real-time coaching.") |
|
|
| with gr.Row(): |
| persona_selector = gr.Dropdown( |
| label="Choose Client Persona", |
| choices=get_persona_choices(), |
| value=get_persona_choices()[0] if get_persona_choices() else None |
| ) |
| objection_selector = gr.Dropdown( |
| label="Buyer Objection", |
| choices=get_objection_choices(), |
| value=get_objection_choices()[0] if get_objection_choices() else None |
| ) |
|
|
| rep_input = gr.Textbox( |
| label="Sales Rep Input", |
| lines=3, |
| placeholder="Your pitch or response..." |
| ) |
|
|
| with gr.Row(): |
| simulate_btn = gr.Button("Run Turn") |
| clear_btn = gr.Button("Clear Session Memory") |
|
|
| persona_response_output = gr.Textbox(label="Persona Response", lines=6) |
| persona_question_output = gr.Textbox(label="Persona Follow-up Question", lines=4) |
| coach_feedback_output = gr.Textbox(label="Coach Feedback", lines=6) |
| state_output = gr.Textbox(label="Updated Persona State", lines=12) |
| trait_chart = gr.Image(label="Trait Radar Chart") |
|
|
| simulate_btn.click( |
| fn=simulate, |
| inputs=[rep_input, objection_selector, persona_selector], |
| outputs=[persona_response_output, persona_question_output, coach_feedback_output, state_output, trait_chart] |
| ) |
|
|
| def clear_all(): |
| session_memory.reset() |
| return ("", "", "", "", None) |
|
|
| clear_btn.click( |
| fn=clear_all, |
| inputs=[], |
| outputs=[persona_response_output, persona_question_output, coach_feedback_output, state_output, trait_chart] |
| ) |
|
|
| ui.launch() |