Ajit Panday commited on
Commit
7d72bcf
·
1 Parent(s): 36a486d

Update dependencies and Hugging Face configuration

Browse files
alembic.ini ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ # path to migration scripts
5
+ script_location = app/migrations
6
+
7
+ # template used to generate migration files
8
+ # file_template = %%(rev)s_%%(slug)s
9
+
10
+ # sys.path path, will be prepended to sys.path if present.
11
+ # defaults to the current working directory.
12
+ prepend_sys_path = .
13
+
14
+ # timezone to use when rendering the date within the migration file
15
+ # as well as the filename.
16
+ # If specified, requires the python-dateutil library that can be
17
+ # installed by adding `alembic[tz]` to the pip requirements
18
+ # timezone =
19
+
20
+ # max length of characters to apply to the
21
+ # "slug" field
22
+ #truncate_slug_length = 40
23
+
24
+ # set to 'true' to run the environment during
25
+ # the 'revision' command, regardless of autogenerate
26
+ # revision_environment = false
27
+
28
+ # set to 'true' to allow .pyc and .pyo files without
29
+ # a source .py file to be detected as revisions in the
30
+ # versions/ directory
31
+ # sourceless = false
32
+
33
+ # version location specification; This defaults
34
+ # to app/migrations/versions. When using multiple version
35
+ # directories, initial revisions must be specified with --version-path.
36
+ # The path separator used here should be the separator specified by "version_path_separator" below.
37
+ # version_locations = %(here)s/bar:%(here)s/bat:app/migrations/versions
38
+
39
+ # version path separator; As mentioned above, this is the character used to split
40
+ # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
41
+ # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or colons.
42
+ # Valid values for version_path_separator are:
43
+ #
44
+ # version_path_separator = :
45
+ # version_path_separator = ;
46
+ # version_path_separator = space
47
+ version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
48
+
49
+ # the output encoding used when revision files
50
+ # are written from script.py.mako
51
+ # output_encoding = utf-8
52
+
53
+ sqlalchemy.url = driver://user:pass@localhost/dbname
54
+
55
+
56
+ [post_write_hooks]
57
+ # post_write_hooks defines scripts or Python functions that are run
58
+ # on newly generated revision scripts. See the documentation for further
59
+ # detail and examples
60
+
61
+ # format using "black" - use the console_scripts runner, against the "black" entrypoint
62
+ # hooks = black
63
+ # black.type = console_scripts
64
+ # black.entrypoint = black
65
+ # black.options = -l 79 REVISION_SCRIPT_FILENAME
66
+
67
+ # Logging configuration
68
+ [loggers]
69
+ keys = root,sqlalchemy,alembic
70
+
71
+ [handlers]
72
+ keys = console
73
+
74
+ [formatters]
75
+ keys = generic
76
+
77
+ [logger_root]
78
+ level = WARN
79
+ handlers = console
80
+ qualname =
81
+
82
+ [logger_sqlalchemy]
83
+ level = WARN
84
+ handlers =
85
+ qualname = sqlalchemy.engine
86
+
87
+ [logger_alembic]
88
+ level = INFO
89
+ handlers =
90
+ qualname = alembic
91
+
92
+ [handler_console]
93
+ class = StreamHandler
94
+ args = (sys.stderr,)
95
+ level = NOTSET
96
+ formatter = generic
97
+
98
+ [formatter_generic]
99
+ format = %(levelname)-5.5s [%(name)s] %(message)s
100
+ datefmt = %H:%M:%S
app/api/calls.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Header
2
+ from typing import Optional
3
+ from datetime import datetime
4
+ from ..models import Customer
5
+ from ..call_processor import CallProcessor
6
+ from ..auth import get_current_customer
7
+ import logging
8
+
9
+ router = APIRouter()
10
+ logger = logging.getLogger(__name__)
11
+
12
+ @router.post("/process-call")
13
+ async def process_call(
14
+ file: UploadFile = File(...),
15
+ caller_number: str = Header(...),
16
+ called_number: str = Header(...),
17
+ customer: Customer = Depends(get_current_customer)
18
+ ):
19
+ """Process a call and send results to customer's webhook"""
20
+ try:
21
+ # Create call processor for the customer
22
+ processor = CallProcessor(customer)
23
+
24
+ # Process and send the call
25
+ result = processor.process_and_send_call(file, caller_number, called_number)
26
+
27
+ if not result:
28
+ raise HTTPException(
29
+ status_code=500,
30
+ detail="Failed to send call results to webhook"
31
+ )
32
+
33
+ return result
34
+
35
+ except Exception as e:
36
+ logger.error(f"Failed to process call: {str(e)}")
37
+ raise HTTPException(status_code=500, detail=str(e))
38
+
39
+ @router.post("/webhook")
40
+ async def update_webhook(
41
+ webhook_url: str,
42
+ customer: Customer = Depends(get_current_customer)
43
+ ):
44
+ """Update customer's webhook URL"""
45
+ try:
46
+ # Update webhook URL
47
+ customer.webhook_url = webhook_url
48
+ customer.save()
49
+
50
+ return {"message": "Webhook URL updated successfully"}
51
+
52
+ except Exception as e:
53
+ logger.error(f"Failed to update webhook URL: {str(e)}")
54
+ raise HTTPException(status_code=500, detail=str(e))
55
+
56
+ @router.get("/calls/{call_id}")
57
+ async def get_call(
58
+ call_id: str,
59
+ customer: Customer = Depends(get_current_customer)
60
+ ):
61
+ """Get call details from customer's database"""
62
+ try:
63
+ processor = CallProcessor(customer)
64
+ call_data = processor.get_call_details(call_id)
65
+
66
+ if not call_data:
67
+ raise HTTPException(status_code=404, detail="Call not found")
68
+
69
+ return call_data
70
+
71
+ except Exception as e:
72
+ logger.error(f"Failed to get call details: {str(e)}")
73
+ raise HTTPException(status_code=500, detail=str(e))
74
+
75
+ @router.get("/calls/search")
76
+ async def search_calls(
77
+ start_date: Optional[datetime] = None,
78
+ end_date: Optional[datetime] = None,
79
+ caller_number: Optional[str] = None,
80
+ called_number: Optional[str] = None,
81
+ customer: Customer = Depends(get_current_customer)
82
+ ):
83
+ """Search calls in customer's database"""
84
+ try:
85
+ processor = CallProcessor(customer)
86
+
87
+ filters = {
88
+ 'start_date': start_date,
89
+ 'end_date': end_date,
90
+ 'caller_number': caller_number,
91
+ 'called_number': called_number
92
+ }
93
+
94
+ results = processor.search_calls(filters)
95
+ return {
96
+ 'calls': results,
97
+ 'total': len(results)
98
+ }
99
+
100
+ except Exception as e:
101
+ logger.error(f"Failed to search calls: {str(e)}")
102
+ raise HTTPException(status_code=500, detail=str(e))
app/call_processor.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .webhook_manager import WebhookManager
2
+ import logging
3
+ from datetime import datetime
4
+ import uuid
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class CallProcessor:
9
+ def __init__(self, customer):
10
+ self.customer = customer
11
+ self.webhook_manager = WebhookManager(customer)
12
+
13
+ def process_and_send_call(self, audio_file, caller_number, called_number):
14
+ """Process call and send results to customer's webhook"""
15
+ try:
16
+ # Generate a unique call ID
17
+ call_id = str(uuid.uuid4())
18
+
19
+ # Process the call (transcription, summary, sentiment)
20
+ # This is where you would integrate with your existing call processing logic
21
+ # For now, we'll use placeholder data
22
+ call_data = {
23
+ 'id': call_id,
24
+ 'caller_number': caller_number,
25
+ 'called_number': called_number,
26
+ 'transcription': "Sample transcription...", # Replace with actual transcription
27
+ 'summary': "Sample summary...", # Replace with actual summary
28
+ 'sentiment': "positive", # Replace with actual sentiment
29
+ 'keywords': "sample, keywords" # Replace with actual keywords
30
+ }
31
+
32
+ # Send to customer's webhook
33
+ success = self.webhook_manager.send_call_results(call_data)
34
+
35
+ if success:
36
+ logger.info(f"Successfully processed and sent call {call_id} for customer {self.customer.id}")
37
+ return call_data
38
+ else:
39
+ logger.warning(f"Failed to send call results to webhook for customer {self.customer.id}")
40
+ return None
41
+
42
+ except Exception as e:
43
+ logger.error(f"Failed to process and send call for customer {self.customer.id}: {str(e)}")
44
+ raise
45
+
46
+ def get_call_details(self, call_id):
47
+ """Get call details from customer's database"""
48
+ try:
49
+ return self.db_manager.get_call_record(call_id)
50
+ except Exception as e:
51
+ logger.error(f"Failed to get call details for call {call_id}: {str(e)}")
52
+ raise
53
+
54
+ def search_calls(self, filters=None):
55
+ """Search calls in customer's database"""
56
+ try:
57
+ return self.db_manager.search_call_records(filters)
58
+ except Exception as e:
59
+ logger.error(f"Failed to search calls for customer {self.customer.id}: {str(e)}")
60
+ raise
app/customer_db.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine, text
2
+ from datetime import datetime
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ class CustomerDBManager:
8
+ def __init__(self, customer):
9
+ self.customer = customer
10
+ self.engine = None
11
+
12
+ def get_connection(self):
13
+ """Get database connection for the customer"""
14
+ if not self.engine:
15
+ try:
16
+ # Create database URL from customer's database credentials
17
+ db_url = f"mysql+pymysql://{self.customer.db_user}:{self.customer.db_password}@{self.customer.db_host}:{self.customer.db_port}/{self.customer.db_name}"
18
+ self.engine = create_engine(db_url)
19
+ except Exception as e:
20
+ logger.error(f"Failed to create database connection for customer {self.customer.id}: {str(e)}")
21
+ raise
22
+ return self.engine
23
+
24
+ def save_call_record(self, call_data):
25
+ """Save call record to customer's database"""
26
+ try:
27
+ engine = self.get_connection()
28
+
29
+ # Prepare the SQL query
30
+ query = text("""
31
+ INSERT INTO call_records (
32
+ id, customer_id, caller_number, called_number,
33
+ transcription, summary, sentiment, keywords,
34
+ created_at, updated_at
35
+ ) VALUES (
36
+ :id, :customer_id, :caller_number, :called_number,
37
+ :transcription, :summary, :sentiment, :keywords,
38
+ :created_at, :updated_at
39
+ )
40
+ """)
41
+
42
+ # Prepare the data
43
+ now = datetime.utcnow()
44
+ record_data = {
45
+ 'id': call_data.get('id'),
46
+ 'customer_id': self.customer.id,
47
+ 'caller_number': call_data.get('caller_number'),
48
+ 'called_number': call_data.get('called_number'),
49
+ 'transcription': call_data.get('transcription'),
50
+ 'summary': call_data.get('summary'),
51
+ 'sentiment': call_data.get('sentiment'),
52
+ 'keywords': call_data.get('keywords'),
53
+ 'created_at': now,
54
+ 'updated_at': now
55
+ }
56
+
57
+ # Execute the query
58
+ with engine.connect() as connection:
59
+ connection.execute(query, record_data)
60
+ connection.commit()
61
+
62
+ logger.info(f"Successfully saved call record {call_data.get('id')} for customer {self.customer.id}")
63
+ return True
64
+
65
+ except Exception as e:
66
+ logger.error(f"Failed to save call record for customer {self.customer.id}: {str(e)}")
67
+ raise
68
+
69
+ def get_call_record(self, call_id):
70
+ """Retrieve a call record from customer's database"""
71
+ try:
72
+ engine = self.get_connection()
73
+
74
+ query = text("""
75
+ SELECT * FROM call_records
76
+ WHERE id = :call_id AND customer_id = :customer_id
77
+ """)
78
+
79
+ with engine.connect() as connection:
80
+ result = connection.execute(query, {
81
+ 'call_id': call_id,
82
+ 'customer_id': self.customer.id
83
+ }).fetchone()
84
+
85
+ return dict(result) if result else None
86
+
87
+ except Exception as e:
88
+ logger.error(f"Failed to retrieve call record {call_id} for customer {self.customer.id}: {str(e)}")
89
+ raise
90
+
91
+ def search_call_records(self, filters=None):
92
+ """Search call records in customer's database"""
93
+ try:
94
+ engine = self.get_connection()
95
+
96
+ # Build the query based on filters
97
+ query = text("""
98
+ SELECT * FROM call_records
99
+ WHERE customer_id = :customer_id
100
+ """)
101
+ params = {'customer_id': self.customer.id}
102
+
103
+ if filters:
104
+ if filters.get('start_date'):
105
+ query = query.text + " AND created_at >= :start_date"
106
+ params['start_date'] = filters['start_date']
107
+ if filters.get('end_date'):
108
+ query = query.text + " AND created_at <= :end_date"
109
+ params['end_date'] = filters['end_date']
110
+ if filters.get('caller_number'):
111
+ query = query.text + " AND caller_number = :caller_number"
112
+ params['caller_number'] = filters['caller_number']
113
+ if filters.get('called_number'):
114
+ query = query.text + " AND called_number = :called_number"
115
+ params['called_number'] = filters['called_number']
116
+
117
+ query = query.text + " ORDER BY created_at DESC"
118
+
119
+ with engine.connect() as connection:
120
+ results = connection.execute(query, params).fetchall()
121
+
122
+ return [dict(row) for row in results]
123
+
124
+ except Exception as e:
125
+ logger.error(f"Failed to search call records for customer {self.customer.id}: {str(e)}")
126
+ raise
app/migrations/env.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from logging.config import fileConfig
2
+
3
+ from sqlalchemy import engine_from_config
4
+ from sqlalchemy import pool
5
+
6
+ from alembic import context
7
+
8
+ import os
9
+ import sys
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
11
+
12
+ from app.models import Base
13
+ from app.database import get_db_url
14
+
15
+ # this is the Alembic Config object, which provides
16
+ # access to the values within the .ini file in use.
17
+ config = context.config
18
+
19
+ # Interpret the config file for Python logging.
20
+ # This line sets up loggers basically.
21
+ if config.config_file_name is not None:
22
+ fileConfig(config.config_file_name)
23
+
24
+ # add your model's MetaData object here
25
+ # for 'autogenerate' support
26
+ target_metadata = Base.metadata
27
+
28
+ # other values from the config, defined by the needs of env.py,
29
+ # can be acquired:
30
+ # my_important_option = config.get_main_option("my_important_option")
31
+ # ... etc.
32
+
33
+ def run_migrations_offline() -> None:
34
+ """Run migrations in 'offline' mode.
35
+
36
+ This configures the context with just a URL
37
+ and not an Engine, though an Engine is acceptable
38
+ here as well. By skipping the Engine creation
39
+ we don't even need a DBAPI to be available.
40
+
41
+ Calls to context.execute() here emit the given string to the
42
+ script output.
43
+
44
+ """
45
+ url = get_db_url()
46
+ context.configure(
47
+ url=url,
48
+ target_metadata=target_metadata,
49
+ literal_binds=True,
50
+ dialect_opts={"paramstyle": "named"},
51
+ )
52
+
53
+ with context.begin_transaction():
54
+ context.run_migrations()
55
+
56
+
57
+ def run_migrations_online() -> None:
58
+ """Run migrations in 'online' mode.
59
+
60
+ In this scenario we need to create an Engine
61
+ and associate a connection with the context.
62
+
63
+ """
64
+ configuration = config.get_section(config.config_ini_section)
65
+ configuration["sqlalchemy.url"] = get_db_url()
66
+ connectable = engine_from_config(
67
+ configuration,
68
+ prefix="sqlalchemy.",
69
+ poolclass=pool.NullPool,
70
+ )
71
+
72
+ with connectable.connect() as connection:
73
+ context.configure(
74
+ connection=connection, target_metadata=target_metadata
75
+ )
76
+
77
+ with context.begin_transaction():
78
+ context.run_migrations()
79
+
80
+
81
+ if context.is_offline_mode():
82
+ run_migrations_offline()
83
+ else:
84
+ run_migrations_online()
app/migrations/versions/add_webhook_url.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Add webhook_url column to customers table
2
+
3
+ Revision ID: add_webhook_url
4
+ Revises:
5
+ Create Date: 2024-03-14 12:00:00.000000
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = 'add_webhook_url'
13
+ down_revision = None
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+ def upgrade():
18
+ # Add webhook_url column to customers table
19
+ op.add_column('customers', sa.Column('webhook_url', sa.String(255), nullable=True))
20
+
21
+ def downgrade():
22
+ # Remove webhook_url column from customers table
23
+ op.drop_column('customers', 'webhook_url')
app/models.py CHANGED
@@ -20,7 +20,7 @@ class Customer(Base):
20
  company_name = Column(String(100), nullable=False)
21
  email = Column(String(100), unique=True, nullable=False)
22
  api_key = Column(String(64), unique=True, nullable=False)
23
- webhook_url = Column(String(255), nullable=True) # URL where call results will be sent
24
  is_active = Column(Boolean, default=True)
25
  created_at = Column(DateTime(timezone=True), server_default=func.now())
26
  updated_at = Column(DateTime(timezone=True), onupdate=func.now())
@@ -66,6 +66,24 @@ class Customer(Base):
66
  response.raise_for_status()
67
  return response.json()
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  class CallRecord(Base):
70
  __tablename__ = "call_records"
71
 
 
20
  company_name = Column(String(100), nullable=False)
21
  email = Column(String(100), unique=True, nullable=False)
22
  api_key = Column(String(64), unique=True, nullable=False)
23
+ webhook_url = Column(String(255), nullable=True, default=None) # URL where call results will be sent
24
  is_active = Column(Boolean, default=True)
25
  created_at = Column(DateTime(timezone=True), server_default=func.now())
26
  updated_at = Column(DateTime(timezone=True), onupdate=func.now())
 
66
  response.raise_for_status()
67
  return response.json()
68
 
69
+ def send_webhook(self, data: Dict) -> bool:
70
+ """Send call results to webhook URL if configured"""
71
+ if not self.webhook_url:
72
+ return False
73
+
74
+ try:
75
+ response = requests.post(
76
+ self.webhook_url,
77
+ json=data,
78
+ headers={"Content-Type": "application/json"},
79
+ timeout=5
80
+ )
81
+ response.raise_for_status()
82
+ return True
83
+ except Exception as e:
84
+ print(f"Webhook delivery failed: {str(e)}")
85
+ return False
86
+
87
  class CallRecord(Base):
88
  __tablename__ = "call_records"
89
 
app/webhook_manager.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import logging
3
+ from datetime import datetime
4
+ import json
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class WebhookManager:
9
+ def __init__(self, customer):
10
+ self.customer = customer
11
+ self.webhook_url = customer.webhook_url
12
+
13
+ def send_call_results(self, call_data):
14
+ """Send call analysis results to customer's webhook URL"""
15
+ if not self.webhook_url:
16
+ logger.warning(f"No webhook URL configured for customer {self.customer.id}")
17
+ return False
18
+
19
+ try:
20
+ # Prepare the payload
21
+ payload = {
22
+ 'call_id': call_data.get('id'),
23
+ 'caller_number': call_data.get('caller_number'),
24
+ 'called_number': call_data.get('called_number'),
25
+ 'transcription': call_data.get('transcription'),
26
+ 'summary': call_data.get('summary'),
27
+ 'sentiment': call_data.get('sentiment'),
28
+ 'keywords': call_data.get('keywords'),
29
+ 'timestamp': datetime.utcnow().isoformat(),
30
+ 'customer_id': self.customer.id
31
+ }
32
+
33
+ # Send POST request to webhook URL
34
+ response = requests.post(
35
+ self.webhook_url,
36
+ json=payload,
37
+ headers={'Content-Type': 'application/json'},
38
+ timeout=10 # 10 seconds timeout
39
+ )
40
+
41
+ # Check if request was successful
42
+ if response.status_code in [200, 201, 202]:
43
+ logger.info(f"Successfully sent call results to webhook for customer {self.customer.id}")
44
+ return True
45
+ else:
46
+ logger.error(f"Failed to send call results to webhook. Status code: {response.status_code}")
47
+ return False
48
+
49
+ except requests.exceptions.RequestException as e:
50
+ logger.error(f"Error sending webhook for customer {self.customer.id}: {str(e)}")
51
+ return False
52
+ except Exception as e:
53
+ logger.error(f"Unexpected error sending webhook for customer {self.customer.id}: {str(e)}")
54
+ return False
customer_webhook/README.md ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # vBot Customer Webhook Handler
2
+
3
+ This package provides a simple webhook handler for vBot customers to receive and process call analysis results on their own servers.
4
+
5
+ ## Features
6
+
7
+ - Secure webhook endpoint with API key validation
8
+ - Automatic call record storage in MySQL database
9
+ - Configurable webhook secret for additional security
10
+ - Easy integration with LAMP stack
11
+ - Example implementation included
12
+
13
+ ## Requirements
14
+
15
+ - PHP 7.4 or higher
16
+ - MySQL 5.7 or higher
17
+ - Apache/Nginx web server
18
+ - mod_rewrite enabled (for Apache)
19
+
20
+ ## Installation
21
+
22
+ 1. Copy the contents of this package to your web server directory:
23
+ ```bash
24
+ cp -r customer_webhook/* /var/www/html/vbot-webhook/
25
+ ```
26
+
27
+ 2. Create the MySQL database and table:
28
+ ```sql
29
+ CREATE DATABASE vbot_calls;
30
+ USE vbot_calls;
31
+
32
+ CREATE TABLE call_records (
33
+ id VARCHAR(36) PRIMARY KEY,
34
+ customer_id INT NOT NULL,
35
+ caller_number VARCHAR(20) NOT NULL,
36
+ called_number VARCHAR(20) NOT NULL,
37
+ transcription TEXT,
38
+ summary TEXT,
39
+ sentiment VARCHAR(50),
40
+ keywords TEXT,
41
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
42
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
43
+ );
44
+ ```
45
+
46
+ 3. Configure the webhook handler:
47
+ - Copy `config/config.example.php` to `config/config.php`
48
+ - Update the database credentials and webhook secret
49
+
50
+ 4. Set up Apache/Nginx:
51
+ - Ensure the webhook directory is accessible
52
+ - Configure URL rewriting (see examples)
53
+
54
+ ## Configuration
55
+
56
+ Edit `config/config.php` to set your:
57
+ - Database credentials
58
+ - Webhook secret (for additional security)
59
+ - API key (provided by vBot)
60
+
61
+ ## Usage
62
+
63
+ 1. Your webhook URL will be: `https://your-domain.com/vbot-webhook/webhook.php`
64
+
65
+ 2. The webhook will receive POST requests with call analysis data in JSON format:
66
+ ```json
67
+ {
68
+ "call_id": "uuid",
69
+ "caller_number": "+1234567890",
70
+ "called_number": "+0987654321",
71
+ "transcription": "Call transcript...",
72
+ "summary": "Call summary...",
73
+ "sentiment": "positive",
74
+ "keywords": "keyword1, keyword2",
75
+ "timestamp": "2024-03-14T12:00:00Z",
76
+ "customer_id": 123
77
+ }
78
+ ```
79
+
80
+ 3. The handler will:
81
+ - Validate the API key
82
+ - Store the call record in your database
83
+ - Return a success response
84
+
85
+ ## Security
86
+
87
+ - All requests must include the API key in the `X-API-Key` header
88
+ - Optional webhook secret for additional security
89
+ - Input validation and sanitization
90
+ - SQL injection prevention
91
+
92
+ ## Examples
93
+
94
+ See the `examples` directory for:
95
+ - Apache configuration
96
+ - Nginx configuration
97
+ - Custom processing example
98
+
99
+ ## Support
100
+
101
+ For support, contact vBot support team or refer to the documentation.
customer_webhook/config/config.example.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Database configuration
3
+ define('DB_HOST', 'localhost');
4
+ define('DB_NAME', 'vbot_calls');
5
+ define('DB_USER', 'your_db_user');
6
+ define('DB_PASS', 'your_db_password');
7
+
8
+ // vBot API configuration
9
+ define('VBOT_API_KEY', 'your_api_key_here'); // API key provided by vBot
10
+ define('WEBHOOK_SECRET', 'your_webhook_secret_here'); // Optional secret for additional security
11
+
12
+ // Error reporting
13
+ error_reporting(E_ALL);
14
+ ini_set('display_errors', 0); // Set to 1 for development
15
+ ini_set('log_errors', 1);
16
+ ini_set('error_log', __DIR__ . '/../logs/error.log');
17
+
18
+ // Response settings
19
+ define('ALLOW_ORIGIN', '*'); // Set to your domain in production
20
+ define('MAX_RETRIES', 3); // Maximum number of retry attempts
21
+ define('RETRY_DELAY', 5); // Delay between retries in seconds
22
+
23
+ // Custom processing
24
+ define('ENABLE_CUSTOM_PROCESSING', false); // Set to true to enable custom processing
25
+ define('CUSTOM_PROCESSOR_FILE', __DIR__ . '/../src/custom_processor.php');
customer_webhook/examples/apache.conf ADDED
@@ -0,0 +1 @@
 
 
1
+
customer_webhook/examples/nginx.conf ADDED
@@ -0,0 +1 @@
 
 
1
+
customer_webhook/src/Database.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Database {
3
+ private static $instance = null;
4
+ private $connection = null;
5
+
6
+ private function __construct() {
7
+ try {
8
+ $this->connection = new PDO(
9
+ "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME,
10
+ DB_USER,
11
+ DB_PASS,
12
+ [
13
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
14
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
15
+ PDO::ATTR_EMULATE_PREPARES => false
16
+ ]
17
+ );
18
+ } catch (PDOException $e) {
19
+ error_log("Database Connection Error: " . $e->getMessage());
20
+ throw new Exception("Database connection failed");
21
+ }
22
+ }
23
+
24
+ public static function getInstance() {
25
+ if (self::$instance === null) {
26
+ self::$instance = new self();
27
+ }
28
+ return self::$instance;
29
+ }
30
+
31
+ public function getConnection() {
32
+ return $this->connection;
33
+ }
34
+
35
+ public function saveCallRecord($data) {
36
+ try {
37
+ $sql = "INSERT INTO call_records (
38
+ id, customer_id, caller_number, called_number,
39
+ transcription, summary, sentiment, keywords,
40
+ created_at, updated_at
41
+ ) VALUES (
42
+ :id, :customer_id, :caller_number, :called_number,
43
+ :transcription, :summary, :sentiment, :keywords,
44
+ NOW(), NOW()
45
+ )";
46
+
47
+ $stmt = $this->connection->prepare($sql);
48
+
49
+ return $stmt->execute([
50
+ 'id' => $data['call_id'],
51
+ 'customer_id' => $data['customer_id'],
52
+ 'caller_number' => $data['caller_number'],
53
+ 'called_number' => $data['called_number'],
54
+ 'transcription' => $data['transcription'],
55
+ 'summary' => $data['summary'],
56
+ 'sentiment' => $data['sentiment'],
57
+ 'keywords' => $data['keywords']
58
+ ]);
59
+
60
+ } catch (PDOException $e) {
61
+ error_log("Database Error: " . $e->getMessage());
62
+ throw new Exception("Failed to save call record");
63
+ }
64
+ }
65
+
66
+ public function getCallRecord($callId) {
67
+ try {
68
+ $sql = "SELECT * FROM call_records WHERE id = :id";
69
+ $stmt = $this->connection->prepare($sql);
70
+ $stmt->execute(['id' => $callId]);
71
+ return $stmt->fetch();
72
+
73
+ } catch (PDOException $e) {
74
+ error_log("Database Error: " . $e->getMessage());
75
+ throw new Exception("Failed to retrieve call record");
76
+ }
77
+ }
78
+
79
+ public function searchCallRecords($filters = []) {
80
+ try {
81
+ $sql = "SELECT * FROM call_records WHERE 1=1";
82
+ $params = [];
83
+
84
+ if (!empty($filters['start_date'])) {
85
+ $sql .= " AND created_at >= :start_date";
86
+ $params['start_date'] = $filters['start_date'];
87
+ }
88
+
89
+ if (!empty($filters['end_date'])) {
90
+ $sql .= " AND created_at <= :end_date";
91
+ $params['end_date'] = $filters['end_date'];
92
+ }
93
+
94
+ if (!empty($filters['caller_number'])) {
95
+ $sql .= " AND caller_number = :caller_number";
96
+ $params['caller_number'] = $filters['caller_number'];
97
+ }
98
+
99
+ if (!empty($filters['called_number'])) {
100
+ $sql .= " AND called_number = :called_number";
101
+ $params['called_number'] = $filters['called_number'];
102
+ }
103
+
104
+ $sql .= " ORDER BY created_at DESC";
105
+
106
+ $stmt = $this->connection->prepare($sql);
107
+ $stmt->execute($params);
108
+ return $stmt->fetchAll();
109
+
110
+ } catch (PDOException $e) {
111
+ error_log("Database Error: " . $e->getMessage());
112
+ throw new Exception("Failed to search call records");
113
+ }
114
+ }
115
+ }
customer_webhook/src/WebhookHandler.php ADDED
@@ -0,0 +1 @@
 
 
1
+
customer_webhook/src/custom_processor.php ADDED
@@ -0,0 +1 @@
 
 
1
+
customer_webhook/src/webhook.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once __DIR__ . '/../config/config.php';
3
+ require_once __DIR__ . '/Database.php';
4
+ require_once __DIR__ . '/WebhookHandler.php';
5
+
6
+ // Set headers
7
+ header('Content-Type: application/json');
8
+ header('Access-Control-Allow-Origin: ' . ALLOW_ORIGIN);
9
+ header('Access-Control-Allow-Methods: POST');
10
+ header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
11
+
12
+ // Handle preflight requests
13
+ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
14
+ http_response_code(200);
15
+ exit();
16
+ }
17
+
18
+ // Only allow POST requests
19
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
20
+ http_response_code(405);
21
+ echo json_encode(['error' => 'Method not allowed']);
22
+ exit();
23
+ }
24
+
25
+ try {
26
+ // Initialize webhook handler
27
+ $handler = new WebhookHandler();
28
+
29
+ // Process the webhook
30
+ $result = $handler->process();
31
+
32
+ // Return success response
33
+ http_response_code(200);
34
+ echo json_encode([
35
+ 'success' => true,
36
+ 'message' => 'Call record processed successfully',
37
+ 'call_id' => $result['call_id'] ?? null
38
+ ]);
39
+
40
+ } catch (Exception $e) {
41
+ // Log error
42
+ error_log("Webhook Error: " . $e->getMessage());
43
+
44
+ // Return error response
45
+ http_response_code(500);
46
+ echo json_encode([
47
+ 'success' => false,
48
+ 'error' => 'Internal server error'
49
+ ]);
50
+ }
huggingface.yml CHANGED
@@ -1 +1,3 @@
1
-
 
 
 
1
+ image: python:3.9
2
+ pip:
3
+ - requirements.txt
requirements.txt CHANGED
@@ -12,4 +12,6 @@ transformers==4.37.2
12
  torch==2.2.0
13
  soundfile==0.12.1
14
  librosa==0.10.1
15
- numpy==1.26.3
 
 
 
12
  torch==2.2.0
13
  soundfile==0.12.1
14
  librosa==0.10.1
15
+ numpy==1.26.3
16
+ alembic==1.13.1
17
+ requests==2.31.0