feat: Update Dograh's UI Design (#67)

* feat: create app sidebar and update layout

* fix: fix loading errors

* fix: fix stack auth hydration issue

* fix: fix design for create-workflow

* fix: fix service configuration page design

* Add header for workflow detail

* feat: fix workflow editor design

* Fix css classes

* Fix callback status parsing for Vobiz

* Fix filter and remove gender service
This commit is contained in:
Abhishek 2025-11-29 15:39:57 +05:30 committed by GitHub
parent 8342cd1dda
commit a7f2238044
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 4398 additions and 2312 deletions

View file

@ -5,15 +5,15 @@ Revises: e02f387b7538
Create Date: 2025-11-27 21:24:34.072030
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from alembic_postgresql_enum import TableReference
# revision identifiers, used by Alembic.
revision: str = 'a188ff90e76f'
down_revision: Union[str, None] = 'e02f387b7538'
revision: str = "a188ff90e76f"
down_revision: Union[str, None] = "e02f387b7538"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@ -21,10 +21,23 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.sync_enum_values(
enum_schema='public',
enum_name='workflow_run_mode',
new_values=['twilio', 'vonage', 'vobiz', 'stasis', 'webrtc', 'smallwebrtc', 'VOICE', 'CHAT'],
affected_columns=[TableReference(table_schema='public', table_name='workflow_runs', column_name='mode')],
enum_schema="public",
enum_name="workflow_run_mode",
new_values=[
"twilio",
"vonage",
"vobiz",
"stasis",
"webrtc",
"smallwebrtc",
"VOICE",
"CHAT",
],
affected_columns=[
TableReference(
table_schema="public", table_name="workflow_runs", column_name="mode"
)
],
enum_values_to_rename=[],
)
# ### end Alembic commands ###
@ -33,10 +46,22 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.sync_enum_values(
enum_schema='public',
enum_name='workflow_run_mode',
new_values=['twilio', 'vonage', 'stasis', 'webrtc', 'smallwebrtc', 'VOICE', 'CHAT'],
affected_columns=[TableReference(table_schema='public', table_name='workflow_runs', column_name='mode')],
enum_schema="public",
enum_name="workflow_run_mode",
new_values=[
"twilio",
"vonage",
"stasis",
"webrtc",
"smallwebrtc",
"VOICE",
"CHAT",
],
affected_columns=[
TableReference(
table_schema="public", table_name="workflow_runs", column_name="mode"
)
],
enum_values_to_rename=[],
)
# ### end Alembic commands ###

View file

@ -3,7 +3,7 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import Integer, and_, cast
from sqlalchemy import Integer, and_, cast, func
from sqlalchemy.dialects.postgresql import JSONB
from api.db.models import WorkflowRunModel
@ -128,10 +128,16 @@ def apply_workflow_run_filters(
):
tags = value.get("codes", [])
if tags:
# The gathered_context column is JSON type (not JSONB)
# JSON type doesn't support subscripting, so we must cast to JSONB first
# Then extract call_tags and check containment with @>
gathered_context_jsonb = cast(
WorkflowRunModel.gathered_context, JSONB
)
# Use -> operator with literal text key to get call_tags as JSONB
call_tags = gathered_context_jsonb.op("->")("call_tags")
filter_conditions.append(
cast(WorkflowRunModel.gathered_context, JSONB)[
"call_tags"
].contains(tags)
call_tags.op("@>")(func.cast(tags, JSONB))
)
elif filter_type == "text" and field == "initial_context.phone":

View file

@ -277,7 +277,6 @@ async def handle_twilio_status_callback(
# Parse form data
form_data = await request.form()
callback_data = dict(form_data)
logger.info(
f"[run {workflow_run_id}] Received status callback: {json.dumps(callback_data)}"
)
@ -519,20 +518,11 @@ async def handle_vobiz_hangup_callback(
This includes call duration, status, and billing information.
"""
# Parse the callback data (Vobiz sends form data or JSON)
try:
callback_data = await request.json()
logger.info(
f"[run {workflow_run_id}] Received Vobiz hangup callback (JSON): "
f"{json.dumps(callback_data)}"
)
except Exception:
# Fallback to form data if JSON fails
form_data = await request.form()
callback_data = dict(form_data)
logger.info(
f"[run {workflow_run_id}] Received Vobiz hangup callback (form): "
f"{json.dumps(callback_data)}"
)
form_data = await request.form()
callback_data = dict(form_data)
logger.info(
f"[run {workflow_run_id}] Received Vobiz hangup callback {json.dumps(callback_data)}"
)
# Get workflow run for processing
workflow_run = await db_client.get_workflow_run_by_id(workflow_run_id)
@ -591,19 +581,11 @@ async def handle_vobiz_ring_callback(
This is optional and used for tracking ringing status.
"""
# Parse the callback data
try:
callback_data = await request.json()
logger.info(
f"[run {workflow_run_id}] Received Vobiz ring callback (JSON): "
f"{json.dumps(callback_data)}"
)
except Exception:
form_data = await request.form()
callback_data = dict(form_data)
logger.info(
f"[run {workflow_run_id}] Received Vobiz ring callback (form): "
f"{json.dumps(callback_data)}"
)
form_data = await request.form()
callback_data = dict(form_data)
logger.info(
f"[run {workflow_run_id}] Received Vobiz ring callback {json.dumps(callback_data)}"
)
# Get workflow run for processing
workflow_run = await db_client.get_workflow_run_by_id(workflow_run_id)

View file

@ -275,13 +275,13 @@ class VobizProvider(TelephonyProvider):
- status, from, to, duration, etc.
"""
return {
"call_id": data.get("call_uuid", data.get("CallUUID", "")),
"status": data.get("status", data.get("Status", "")),
"from_number": data.get("from", data.get("From")),
"to_number": data.get("to", data.get("To")),
"direction": data.get("direction", data.get("Direction")),
"duration": data.get("duration", data.get("Duration")),
"extra": data, # Include all original data
"call_id": data.get("CallUUID", ""),
"status": data.get("CallStatus", ""),
"from_number": data.get("From"),
"to_number": data.get("To"),
"direction": data.get("Direction"),
"duration": data.get("Duration"),
"extra": data,
}
async def handle_websocket(

View file

@ -1,7 +1,6 @@
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Union
from api.constants import DEPLOYMENT_MODE, ENABLE_TRACING, VOICEMAIL_RECORDING_DURATION
from api.services.gender.gender_service import GenderService
from api.services.workflow.disposition_mapper import (
apply_disposition_mapping,
get_organization_id_from_workflow_run,
@ -94,8 +93,6 @@ class PipecatEngine:
# access to _context
self._variable_extraction_manager = None
self._gender_service = GenderService(confidence_threshold=0.5)
# Voicemail detection state
self._detect_voicemail = False
self._voicemail_detector = None
@ -160,13 +157,6 @@ class PipecatEngine:
# Register built-in functions with the LLM
await self._register_builtin_functions()
# Set gender in initial context predicted from first name
if "first_name" in self._call_context_vars:
salutation = await self._gender_service.get_salutation(
self._call_context_vars["first_name"]
)
self._call_context_vars["salutation"] = salutation
await self.set_node(self.workflow.start_node_id)
logger.debug(f"{self.__class__.__name__} initialized")
except Exception as e: