rairo commited on
Commit
1c5a346
·
verified ·
1 Parent(s): 6ef20f2

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +256 -0
main.py CHANGED
@@ -947,7 +947,263 @@ def delete_project(project_id):
947
  blob.delete()
948
  return jsonify({"success": True, "message": f"Project {project_id} deleted."})
949
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  # -----------------------------------------------------------------------------
952
  # 7. MAIN EXECUTION
953
  # -----------------------------------------------------------------------------
 
947
  blob.delete()
948
  return jsonify({"success": True, "message": f"Project {project_id} deleted."})
949
 
950
+ # AI call ElevenLabs
951
+ CREDITS_PER_MIN = 3 # Adjust based on ElevenLabs conversational AI pricing
952
+ VOICE_NAME = "Daniel" # British male voice (more reliable than Archer)
953
+
954
+ class ConversationalAIHandler:
955
+ def __init__(self):
956
+ self.client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
957
+ self.base_url = "https://api.elevenlabs.io/v1"
958
+ self.headers = {
959
+ "xi-api-key": os.getenv("ELEVENLABS_API_KEY"),
960
+ "Content-Type": "application/json"
961
+ }
962
+
963
+ def get_british_male_voice_id(self):
964
+ """Get British male voice ID"""
965
+ try:
966
+ voices = self.client.voices.list().voices
967
+ # Preferred British male voices in order
968
+ preferred_voices = ["Daniel", "Charlie", "Archer", "George"]
969
+
970
+ for voice_name in preferred_voices:
971
+ voice = next((v for v in voices if v.name == voice_name), None)
972
+ if voice:
973
+ return voice.voice_id
974
+
975
+ # Fallback to first available voice
976
+ return voices[0].voice_id if voices else None
977
+ except Exception as e:
978
+ logger.error(f"Error getting voice: {str(e)}")
979
+ return None
980
+
981
+ def create_or_get_agent(self, project_id, project_data):
982
+ """Create or retrieve existing agent for the project"""
983
+ try:
984
+ voice_id = self.get_british_male_voice_id()
985
+ if not voice_id:
986
+ raise Exception("No suitable voice available")
987
+
988
+ # DIY Expert persona context
989
+ diy_expert_prompt = f"""
990
+ You are an experienced DIY expert with years of hands-on experience in Home Appliance Repair, Automotive Maintenance, Gardening & Urban Farming,
991
+ Upcycling & Sustainable Crafts, or DIY Project Creation.. You speak in a friendly, knowledgeable British manner and provide
992
+ practical, actionable advice. You're working on this specific project:
993
+
994
+ Project: {project_data.get('title', 'DIY Project')}
995
+ Description: {project_data.get('projectDescription', '')}
996
+ Initial Plan: {project_data.get('initialPlan', '')}
997
+
998
+ Provide helpful, step-by-step guidance while being encouraging and safety-conscious.
999
+ Ask clarifying questions when needed and share relevant tips from your experience.
1000
+ """
1001
+
1002
+ agent_data = {
1003
+ "name": f"DIY Expert - {project_data.get('title', 'Project')}",
1004
+ "prompt": diy_expert_prompt,
1005
+ "voice_id": voice_id,
1006
+ "language": "en",
1007
+ "conversation_config": {
1008
+ "turn_detection": {
1009
+ "type": "server_vad",
1010
+ "threshold": 0.5,
1011
+ "prefix_padding_ms": 300,
1012
+ "silence_duration_ms": 800
1013
+ }
1014
+ },
1015
+ "webhook_url": f"{request.host_url}api/webhook/agent/{project_id}",
1016
+ "max_duration_seconds": 1800 # 30 minutes max
1017
+ }
1018
+
1019
+ # Check if agent already exists for this project
1020
+ existing_agent_id = db_ref.child(f'projects/{project_id}/agent_id').get()
1021
+
1022
+ if existing_agent_id:
1023
+ # Verify agent still exists
1024
+ response = requests.get(
1025
+ f"{self.base_url}/agents/{existing_agent_id}",
1026
+ headers=self.headers
1027
+ )
1028
+ if response.status_code == 200:
1029
+ return existing_agent_id
1030
+
1031
+ # Create new agent
1032
+ response = requests.post(
1033
+ f"{self.base_url}/agents",
1034
+ headers=self.headers,
1035
+ json=agent_data
1036
+ )
1037
+
1038
+ if response.status_code == 201:
1039
+ agent_info = response.json()
1040
+ agent_id = agent_info["id"]
1041
+
1042
+ # Store agent ID in database
1043
+ db_ref.child(f'projects/{project_id}').update({
1044
+ 'agent_id': agent_id,
1045
+ 'agent_created_at': int(time.time())
1046
+ })
1047
+
1048
+ return agent_id
1049
+ else:
1050
+ raise Exception(f"Failed to create agent: {response.text}")
1051
+
1052
+ except Exception as e:
1053
+ logger.error(f"Error creating/getting agent: {str(e)}")
1054
+ raise
1055
+
1056
+ def simulate_conversation(self, agent_id, user_message):
1057
+ """Simulate a conversation with the agent"""
1058
+ try:
1059
+ simulation_data = {
1060
+ "simulation_specification": {
1061
+ "user_message": user_message,
1062
+ "max_turns": 10,
1063
+ "evaluation_criteria": ["helpfulness", "accuracy", "clarity"]
1064
+ }
1065
+ }
1066
+
1067
+ response = requests.post(
1068
+ f"{self.base_url}/agents/{agent_id}/simulate-conversation",
1069
+ headers=self.headers,
1070
+ json=simulation_data
1071
+ )
1072
+
1073
+ if response.status_code == 200:
1074
+ return response.json()
1075
+ else:
1076
+ raise Exception(f"Simulation failed: {response.text}")
1077
+
1078
+ except Exception as e:
1079
+ logger.error(f"Error simulating conversation: {str(e)}")
1080
+ raise
1081
+
1082
+ def calculate_cost(duration_seconds):
1083
+ """Calculate cost based on conversation duration"""
1084
+ minutes = (duration_seconds + 59) // 60
1085
+ return minutes * CREDITS_PER_MIN
1086
+
1087
+ @app.route('/api/projects/<project_id>/start-conversation', methods=['POST'])
1088
+ def start_conversation(project_id):
1089
+ """Start a conversational AI session for a project"""
1090
+ t0 = time.time()
1091
+
1092
+ try:
1093
+ uid = verify_token(request.headers.get('Authorization'))
1094
+ if not uid:
1095
+ return jsonify({'error': 'Unauthorized'}), 401
1096
+
1097
+ user_ref = db_ref.child(f'users/{uid}')
1098
+ user = user_ref.get()
1099
+ if not user:
1100
+ return jsonify({'error': 'User not found'}), 404
1101
+
1102
+ project = db_ref.child(f'projects/{project_id}').get()
1103
+ if not project or project.get('uid') != uid:
1104
+ return jsonify({'error': 'Project not found'}), 404
1105
 
1106
+ data = request.get_json()
1107
+ initial_message = data.get('message', 'Hello, I need help with my DIY project.')
1108
+
1109
+ # Initialize conversational AI handler
1110
+ ai_handler = ConversationalAIHandler()
1111
+
1112
+ # Create or get existing agent
1113
+ agent_id = ai_handler.create_or_get_agent(project_id, project)
1114
+
1115
+ # Start conversation simulation
1116
+ conversation_result = ai_handler.simulate_conversation(agent_id, initial_message)
1117
+
1118
+ duration = time.time() - t0
1119
+ cost = calculate_cost(duration)
1120
+
1121
+ # Check credits
1122
+ if user.get('credits', 0) < cost:
1123
+ return jsonify({'error': 'Insufficient credits', 'needed': cost}), 402
1124
+
1125
+ # Deduct credits
1126
+ new_credits = user['credits'] - cost
1127
+ user_ref.update({'credits': new_credits})
1128
+
1129
+ # Log the conversation
1130
+ conversation_id = f"{project_id}_{int(t0)}"
1131
+ db_ref.child(f'conversations/{conversation_id}').set({
1132
+ 'project_id': project_id,
1133
+ 'uid': uid,
1134
+ 'agent_id': agent_id,
1135
+ 'initial_message': initial_message,
1136
+ 'result': conversation_result,
1137
+ 'duration_seconds': duration,
1138
+ 'credits_used': cost,
1139
+ 'created_at': int(t0)
1140
+ })
1141
+
1142
+ logger.info(f"User {uid} started conversation for project {project_id}, "
1143
+ f"duration {duration:.1f}s, cost {cost} credits.")
1144
+
1145
+ return jsonify({
1146
+ 'conversation_id': conversation_id,
1147
+ 'agent_id': agent_id,
1148
+ 'simulation_result': conversation_result,
1149
+ 'durationSeconds': round(duration, 1),
1150
+ 'creditsDeducted': cost,
1151
+ 'remainingCredits': new_credits
1152
+ }), 200
1153
+
1154
+ except Exception as e:
1155
+ logger.error(f"Error starting conversation: {str(e)}")
1156
+ return jsonify({'error': 'Failed to start conversation'}), 500
1157
+
1158
+ @app.route('/api/projects/<project_id>/continue-conversation', methods=['POST'])
1159
+ def continue_conversation(project_id):
1160
+ """Continue an existing conversation"""
1161
+ try:
1162
+ uid = verify_token(request.headers.get('Authorization'))
1163
+ if not uid:
1164
+ return jsonify({'error': 'Unauthorized'}), 401
1165
+
1166
+ data = request.get_json()
1167
+ agent_id = data.get('agent_id')
1168
+ message = data.get('message')
1169
+
1170
+ if not agent_id or not message:
1171
+ return jsonify({'error': 'agent_id and message required'}), 400
1172
+
1173
+ ai_handler = ConversationalAIHandler()
1174
+ result = ai_handler.simulate_conversation(agent_id, message)
1175
+
1176
+ return jsonify({
1177
+ 'simulation_result': result,
1178
+ 'timestamp': int(time.time())
1179
+ }), 200
1180
+
1181
+ except Exception as e:
1182
+ logger.error(f"Error continuing conversation: {str(e)}")
1183
+ return jsonify({'error': 'Failed to continue conversation'}), 500
1184
+
1185
+ @app.route('/api/webhook/agent/<project_id>', methods=['POST'])
1186
+ def agent_webhook(project_id):
1187
+ """Webhook to handle agent events"""
1188
+ try:
1189
+ data = request.get_json()
1190
+ event_type = data.get('event_type')
1191
+
1192
+ # Log the webhook event
1193
+ logger.info(f"Agent webhook for project {project_id}: {event_type}")
1194
+
1195
+ # Store webhook data for debugging/monitoring
1196
+ db_ref.child(f'webhooks/{project_id}/{int(time.time())}').set({
1197
+ 'event_type': event_type,
1198
+ 'data': data,
1199
+ 'timestamp': int(time.time())
1200
+ })
1201
+
1202
+ return jsonify({'status': 'received'}), 200
1203
+
1204
+ except Exception as e:
1205
+ logger.error(f"Webhook error: {str(e)}")
1206
+ return jsonify({'error': 'Webhook processing failed'}), 500
1207
  # -----------------------------------------------------------------------------
1208
  # 7. MAIN EXECUTION
1209
  # -----------------------------------------------------------------------------