elevenlabs-webhook-nodejs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +27 -0
- package/.dockerignore +6 -0
- package/.env.example +43 -0
- package/BEFORE-PRODUCTION.md +32 -0
- package/Dockerfile +23 -0
- package/SYSTEM_OVERVIEW.md +942 -0
- package/SYSTEM_STATUS.md +332 -0
- package/backup_script/main.py +146 -0
- package/baileys/.dockerignore +7 -0
- package/baileys/Dockerfile +13 -0
- package/baileys/README.md +412 -0
- package/baileys/index.js +499 -0
- package/baileys/package-lock.json +2532 -0
- package/baileys/package.json +25 -0
- package/baileys/server.js +96 -0
- package/baileys/src/config.js +55 -0
- package/baileys/src/middleware/api-key.js +16 -0
- package/baileys/src/routes/accounts.js +51 -0
- package/baileys/src/routes/chats.js +103 -0
- package/baileys/src/routes/webhooks.js +34 -0
- package/baileys/src/services/account-store.js +259 -0
- package/baileys/src/services/webhook-dispatcher.js +161 -0
- package/baileys/src/services/worker-manager.js +597 -0
- package/baileys/src/utils/jid.js +79 -0
- package/baileys/src/utils/logger.js +16 -0
- package/baileys/src/utils/use-mongodb-auth-state.js +122 -0
- package/baileys/worker.js +721 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +84 -0
- package/dist/app.js.map +1 -0
- package/dist/config/agentPrompts.json +19 -0
- package/dist/config/database.d.ts +1 -0
- package/dist/config/database.js +26 -0
- package/dist/config/database.js.map +1 -0
- package/dist/config/env.d.ts +41 -0
- package/dist/config/env.js +81 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +73 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controllers/webhook.controller.d.ts +10 -0
- package/dist/controllers/webhook.controller.js +128 -0
- package/dist/controllers/webhook.controller.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/hooks/webhookValidator.d.ts +2 -0
- package/dist/hooks/webhookValidator.js +47 -0
- package/dist/hooks/webhookValidator.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/001_session-architecture.d.ts +3 -0
- package/dist/migrations/001_session-architecture.js +119 -0
- package/dist/migrations/001_session-architecture.js.map +1 -0
- package/dist/migrations/002_agent-ref.d.ts +3 -0
- package/dist/migrations/002_agent-ref.js +55 -0
- package/dist/migrations/002_agent-ref.js.map +1 -0
- package/dist/migrations/003_shift-schedule.d.ts +3 -0
- package/dist/migrations/003_shift-schedule.js +48 -0
- package/dist/migrations/003_shift-schedule.js.map +1 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.d.ts +3 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.js +27 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.js.map +1 -0
- package/dist/migrations/005_composite-baileys-chat-ids.d.ts +3 -0
- package/dist/migrations/005_composite-baileys-chat-ids.js +47 -0
- package/dist/migrations/005_composite-baileys-chat-ids.js.map +1 -0
- package/dist/migrations/migration.model.d.ts +18 -0
- package/dist/migrations/migration.model.js +45 -0
- package/dist/migrations/migration.model.js.map +1 -0
- package/dist/migrations/migration.routes.d.ts +2 -0
- package/dist/migrations/migration.routes.js +37 -0
- package/dist/migrations/migration.routes.js.map +1 -0
- package/dist/migrations/migration.runner.d.ts +19 -0
- package/dist/migrations/migration.runner.js +74 -0
- package/dist/migrations/migration.runner.js.map +1 -0
- package/dist/models/ConversationClaim.d.ts +13 -0
- package/dist/models/ConversationClaim.js +44 -0
- package/dist/models/ConversationClaim.js.map +1 -0
- package/dist/models/ConversationEvaluation.d.ts +23 -0
- package/dist/models/ConversationEvaluation.js +92 -0
- package/dist/models/ConversationEvaluation.js.map +1 -0
- package/dist/models/Summary.d.ts +37 -0
- package/dist/models/Summary.js +78 -0
- package/dist/models/Summary.js.map +1 -0
- package/dist/models/Transcription.d.ts +34 -0
- package/dist/models/Transcription.js +71 -0
- package/dist/models/Transcription.js.map +1 -0
- package/dist/models/WhatsAppRecipient.d.ts +23 -0
- package/dist/models/WhatsAppRecipient.js +53 -0
- package/dist/models/WhatsAppRecipient.js.map +1 -0
- package/dist/modules/admin/admin.routes.d.ts +2 -0
- package/dist/modules/admin/admin.routes.js +1966 -0
- package/dist/modules/admin/admin.routes.js.map +1 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.d.ts +2 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.js +158 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.js.map +1 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.d.ts +2 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.js +204 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.js.map +1 -0
- package/dist/modules/agents/agent.model.d.ts +38 -0
- package/dist/modules/agents/agent.model.js +92 -0
- package/dist/modules/agents/agent.model.js.map +1 -0
- package/dist/modules/agents/agent.routes.d.ts +2 -0
- package/dist/modules/agents/agent.routes.js +61 -0
- package/dist/modules/agents/agent.routes.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.model.d.ts +76 -0
- package/dist/modules/appointment-validation/appointment-validation.model.js +118 -0
- package/dist/modules/appointment-validation/appointment-validation.model.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.d.ts +2 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.js +202 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.service.d.ts +53 -0
- package/dist/modules/appointment-validation/appointment-validation.service.js +827 -0
- package/dist/modules/appointment-validation/appointment-validation.service.js.map +1 -0
- package/dist/modules/auth/auth.model.d.ts +17 -0
- package/dist/modules/auth/auth.model.js +64 -0
- package/dist/modules/auth/auth.model.js.map +1 -0
- package/dist/modules/auth/auth.routes.d.ts +2 -0
- package/dist/modules/auth/auth.routes.js +202 -0
- package/dist/modules/auth/auth.routes.js.map +1 -0
- package/dist/modules/auth/auth.service.d.ts +28 -0
- package/dist/modules/auth/auth.service.js +183 -0
- package/dist/modules/auth/auth.service.js.map +1 -0
- package/dist/modules/auth/refresh-token.model.d.ts +13 -0
- package/dist/modules/auth/refresh-token.model.js +52 -0
- package/dist/modules/auth/refresh-token.model.js.map +1 -0
- package/dist/modules/billing/billing-alert.model.d.ts +16 -0
- package/dist/modules/billing/billing-alert.model.js +47 -0
- package/dist/modules/billing/billing-alert.model.js.map +1 -0
- package/dist/modules/billing/billing-period-snapshot.model.d.ts +35 -0
- package/dist/modules/billing/billing-period-snapshot.model.js +68 -0
- package/dist/modules/billing/billing-period-snapshot.model.js.map +1 -0
- package/dist/modules/billing/billing.model.d.ts +18 -0
- package/dist/modules/billing/billing.model.js +62 -0
- package/dist/modules/billing/billing.model.js.map +1 -0
- package/dist/modules/billing/billing.routes.d.ts +2 -0
- package/dist/modules/billing/billing.routes.js +63 -0
- package/dist/modules/billing/billing.routes.js.map +1 -0
- package/dist/modules/billing/billing.service.d.ts +69 -0
- package/dist/modules/billing/billing.service.js +498 -0
- package/dist/modules/billing/billing.service.js.map +1 -0
- package/dist/modules/billing/payment.model.d.ts +24 -0
- package/dist/modules/billing/payment.model.js +57 -0
- package/dist/modules/billing/payment.model.js.map +1 -0
- package/dist/modules/calls/call.model.d.ts +41 -0
- package/dist/modules/calls/call.model.js +97 -0
- package/dist/modules/calls/call.model.js.map +1 -0
- package/dist/modules/calls/call.routes.d.ts +2 -0
- package/dist/modules/calls/call.routes.js +103 -0
- package/dist/modules/calls/call.routes.js.map +1 -0
- package/dist/modules/campaigns/campaign.model.d.ts +45 -0
- package/dist/modules/campaigns/campaign.model.js +98 -0
- package/dist/modules/campaigns/campaign.model.js.map +1 -0
- package/dist/modules/campaigns/campaign.routes.d.ts +2 -0
- package/dist/modules/campaigns/campaign.routes.js +323 -0
- package/dist/modules/campaigns/campaign.routes.js.map +1 -0
- package/dist/modules/campaigns/campaign.service.d.ts +11 -0
- package/dist/modules/campaigns/campaign.service.js +86 -0
- package/dist/modules/campaigns/campaign.service.js.map +1 -0
- package/dist/modules/google-calendar/google-calendar.routes.d.ts +2 -0
- package/dist/modules/google-calendar/google-calendar.routes.js +32 -0
- package/dist/modules/google-calendar/google-calendar.routes.js.map +1 -0
- package/dist/modules/inbound-call/inbound-call-config.model.d.ts +20 -0
- package/dist/modules/inbound-call/inbound-call-config.model.js +68 -0
- package/dist/modules/inbound-call/inbound-call-config.model.js.map +1 -0
- package/dist/modules/inbound-call/inbound-call.routes.d.ts +2 -0
- package/dist/modules/inbound-call/inbound-call.routes.js +243 -0
- package/dist/modules/inbound-call/inbound-call.routes.js.map +1 -0
- package/dist/modules/leads/lead.model.d.ts +24 -0
- package/dist/modules/leads/lead.model.js +54 -0
- package/dist/modules/leads/lead.model.js.map +1 -0
- package/dist/modules/leads/lead.routes.d.ts +2 -0
- package/dist/modules/leads/lead.routes.js +201 -0
- package/dist/modules/leads/lead.routes.js.map +1 -0
- package/dist/modules/plans/plan.model.d.ts +25 -0
- package/dist/modules/plans/plan.model.js +59 -0
- package/dist/modules/plans/plan.model.js.map +1 -0
- package/dist/modules/surveys/survey.model.d.ts +30 -0
- package/dist/modules/surveys/survey.model.js +75 -0
- package/dist/modules/surveys/survey.model.js.map +1 -0
- package/dist/modules/surveys/survey.routes.d.ts +2 -0
- package/dist/modules/surveys/survey.routes.js +153 -0
- package/dist/modules/surveys/survey.routes.js.map +1 -0
- package/dist/modules/tenants/tenant.model.d.ts +86 -0
- package/dist/modules/tenants/tenant.model.js +127 -0
- package/dist/modules/tenants/tenant.model.js.map +1 -0
- package/dist/modules/tenants/tenant.routes.d.ts +2 -0
- package/dist/modules/tenants/tenant.routes.js +65 -0
- package/dist/modules/tenants/tenant.routes.js.map +1 -0
- package/dist/modules/users/user.routes.d.ts +2 -0
- package/dist/modules/users/user.routes.js +106 -0
- package/dist/modules/users/user.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.d.ts +20 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.js +85 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.d.ts +11 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.js +360 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs.routes.d.ts +2 -0
- package/dist/modules/webhooks/elevenlabs.routes.js +34 -0
- package/dist/modules/webhooks/elevenlabs.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs.service.d.ts +6 -0
- package/dist/modules/webhooks/elevenlabs.service.js +512 -0
- package/dist/modules/webhooks/elevenlabs.service.js.map +1 -0
- package/dist/modules/webhooks/unipile.routes.d.ts +2 -0
- package/dist/modules/webhooks/unipile.routes.js +780 -0
- package/dist/modules/webhooks/unipile.routes.js.map +1 -0
- package/dist/modules/whatsapp/appointment.model.d.ts +27 -0
- package/dist/modules/whatsapp/appointment.model.js +58 -0
- package/dist/modules/whatsapp/appointment.model.js.map +1 -0
- package/dist/modules/whatsapp/operator-request.model.d.ts +29 -0
- package/dist/modules/whatsapp/operator-request.model.js +65 -0
- package/dist/modules/whatsapp/operator-request.model.js.map +1 -0
- package/dist/modules/whatsapp/stt-usage.model.d.ts +16 -0
- package/dist/modules/whatsapp/stt-usage.model.js +53 -0
- package/dist/modules/whatsapp/stt-usage.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.d.ts +23 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.js +55 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.d.ts +23 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.js +54 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-message.model.d.ts +30 -0
- package/dist/modules/whatsapp/whatsapp-message.model.js +52 -0
- package/dist/modules/whatsapp/whatsapp-message.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-session.model.d.ts +33 -0
- package/dist/modules/whatsapp/whatsapp-session.model.js +65 -0
- package/dist/modules/whatsapp/whatsapp-session.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp.routes.d.ts +2 -0
- package/dist/modules/whatsapp/whatsapp.routes.js +1237 -0
- package/dist/modules/whatsapp/whatsapp.routes.js.map +1 -0
- package/dist/plugins/cors.d.ts +3 -0
- package/dist/plugins/cors.js +11 -0
- package/dist/plugins/cors.js.map +1 -0
- package/dist/plugins/jwt.d.ts +3 -0
- package/dist/plugins/jwt.js +22 -0
- package/dist/plugins/jwt.js.map +1 -0
- package/dist/plugins/rawBody.d.ts +3 -0
- package/dist/plugins/rawBody.js +16 -0
- package/dist/plugins/rawBody.js.map +1 -0
- package/dist/plugins/rbac.d.ts +5 -0
- package/dist/plugins/rbac.js +29 -0
- package/dist/plugins/rbac.js.map +1 -0
- package/dist/routes/admin.routes.d.ts +2 -0
- package/dist/routes/admin.routes.js +169 -0
- package/dist/routes/admin.routes.js.map +1 -0
- package/dist/routes/health.routes.d.ts +2 -0
- package/dist/routes/health.routes.js +16 -0
- package/dist/routes/health.routes.js.map +1 -0
- package/dist/routes/webhook.routes.d.ts +2 -0
- package/dist/routes/webhook.routes.js +17 -0
- package/dist/routes/webhook.routes.js.map +1 -0
- package/dist/services/ai/base.ai.d.ts +19 -0
- package/dist/services/ai/base.ai.js +120 -0
- package/dist/services/ai/base.ai.js.map +1 -0
- package/dist/services/ai/gemini.service.d.ts +11 -0
- package/dist/services/ai/gemini.service.js +43 -0
- package/dist/services/ai/gemini.service.js.map +1 -0
- package/dist/services/ai/index.d.ts +2 -0
- package/dist/services/ai/index.js +26 -0
- package/dist/services/ai/index.js.map +1 -0
- package/dist/services/ai/openai.service.d.ts +11 -0
- package/dist/services/ai/openai.service.js +50 -0
- package/dist/services/ai/openai.service.js.map +1 -0
- package/dist/services/elevenlabs.service.d.ts +52 -0
- package/dist/services/elevenlabs.service.js +447 -0
- package/dist/services/elevenlabs.service.js.map +1 -0
- package/dist/services/google-calendar.service.d.ts +60 -0
- package/dist/services/google-calendar.service.js +494 -0
- package/dist/services/google-calendar.service.js.map +1 -0
- package/dist/services/inbound-call-schedule.service.d.ts +11 -0
- package/dist/services/inbound-call-schedule.service.js +162 -0
- package/dist/services/inbound-call-schedule.service.js.map +1 -0
- package/dist/services/netgsm.service.d.ts +41 -0
- package/dist/services/netgsm.service.js +89 -0
- package/dist/services/netgsm.service.js.map +1 -0
- package/dist/services/unipile.service.d.ts +41 -0
- package/dist/services/unipile.service.js +149 -0
- package/dist/services/unipile.service.js.map +1 -0
- package/dist/services/whatsapp-agent.service.d.ts +139 -0
- package/dist/services/whatsapp-agent.service.js +2055 -0
- package/dist/services/whatsapp-agent.service.js.map +1 -0
- package/dist/services/whatsapp.service.d.ts +26 -0
- package/dist/services/whatsapp.service.js +206 -0
- package/dist/services/whatsapp.service.js.map +1 -0
- package/dist/templates/index.d.ts +39 -0
- package/dist/templates/index.js +35 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/receptionist.d.ts +2 -0
- package/dist/templates/receptionist.js +39 -0
- package/dist/templates/receptionist.js.map +1 -0
- package/dist/templates/survey.d.ts +2 -0
- package/dist/templates/survey.js +41 -0
- package/dist/templates/survey.js.map +1 -0
- package/dist/types/index.d.ts +173 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/logger.js.map +1 -0
- package/docker-compose.nestjs.yml +89 -0
- package/docker-compose.yml +78 -0
- package/docs/AI_AGENT_ENHANCEMENT_PLAN.md +164 -0
- package/docs/API.md +1193 -0
- package/docs/API_ENDPOINTS.md +344 -0
- package/docs/ARCHITECTURE.md +305 -0
- package/docs/AUTH_API.md +252 -0
- package/docs/BILLING_SMS_ALERTS.md +94 -0
- package/docs/CHAT_ASSIGNMENT_SYSTEM.md +118 -0
- package/docs/CLIENT_TOOLS_AND_FEATURES.md +337 -0
- package/docs/ELEVENLABS_WEBHOOK_TOOLS.md +644 -0
- package/docs/FRONTEND_CHECKLIST.md +227 -0
- package/docs/IMPLEMENTATION_STATUS.md +470 -0
- package/docs/MIGRATION_GUIDE.md +96 -0
- package/docs/MISSINGS_REPORT.md +507 -0
- package/docs/NESTJS_MIGRATION_REFERENCE.md +5136 -0
- package/docs/PROJECT_DESCRIPTION.md +1038 -0
- package/docs/SCALING.md +148 -0
- package/docs/SESSION_SUMMARY_2026_03_17.md +135 -0
- package/docs/WHATSAPP_AGENT.md +1086 -0
- package/docs/architecture/00-SYSTEM-OVERVIEW.md +318 -0
- package/docs/architecture/01-DATABASE-SCHEMA.md +2564 -0
- package/docs/architecture/02-AUTHENTICATION.md +1040 -0
- package/docs/architecture/03-MULTI-CLINIC.md +742 -0
- package/docs/architecture/04-WHATSAPP-AGENT.md +608 -0
- package/docs/architecture/05-OPERATOR-WORKFLOW.md +444 -0
- package/docs/architecture/06-BAILEYS-MICROSERVICE.md +616 -0
- package/docs/architecture/07-APPOINTMENTS.md +849 -0
- package/docs/architecture/08-VOICE-CALLS.md +470 -0
- package/docs/architecture/09-OUTBOUND-CAMPAIGNS.md +542 -0
- package/docs/architecture/10-CLIENT-TOOLS.md +665 -0
- package/docs/architecture/11-BILLING.md +458 -0
- package/docs/architecture/12-SECURITY.md +216 -0
- package/docs/architecture/13-LOGGING-AUDIT.md +549 -0
- package/docs/architecture/14-META-BUSINESS-API.md +454 -0
- package/docs/architecture/15-AI-MODULE.md +479 -0
- package/docs/architecture/16-BACKGROUND-JOBS.md +469 -0
- package/docs/architecture/17-REALTIME-LIVECHAT.md +447 -0
- package/docs/architecture/18-FILE-STORAGE.md +410 -0
- package/docs/architecture/19-PATIENTS.md +1034 -0
- package/docs/architecture/20-TREATMENTS-AND-PLANS.md +774 -0
- package/docs/architecture/21-BEFORE-AFTER-PHOTOS.md +519 -0
- package/docs/database.md +456 -0
- package/docs/ornek-randevu-onay.csv +3 -0
- package/ecosystem.config.js +16 -0
- package/elevenlabs-convai-api-reference.md +1171 -0
- package/frontend/.dockerignore +2 -0
- package/frontend/Dockerfile +24 -0
- package/frontend/README.md +75 -0
- package/frontend/components.json +25 -0
- package/frontend/eslint.config.js +23 -0
- package/frontend/index.html +13 -0
- package/frontend/nginx.conf +37 -0
- package/frontend/package-lock.json +8709 -0
- package/frontend/package.json +71 -0
- package/frontend/public/favicon.svg +1 -0
- package/frontend/public/icons.svg +24 -0
- package/frontend/src/App.tsx +125 -0
- package/frontend/src/components/error-boundary.tsx +50 -0
- package/frontend/src/components/shared/activity-timeline.tsx +66 -0
- package/frontend/src/components/shared/appointment-calendar.css +80 -0
- package/frontend/src/components/shared/appointment-calendar.tsx +245 -0
- package/frontend/src/components/shared/chat-bubble.tsx +72 -0
- package/frontend/src/components/shared/combobox.tsx +119 -0
- package/frontend/src/components/shared/confirm-dialog.tsx +57 -0
- package/frontend/src/components/shared/data-table-pagination.tsx +97 -0
- package/frontend/src/components/shared/data-table-toolbar.tsx +39 -0
- package/frontend/src/components/shared/empty-state.tsx +27 -0
- package/frontend/src/components/shared/file-upload.tsx +140 -0
- package/frontend/src/components/shared/index.ts +20 -0
- package/frontend/src/components/shared/language-switcher.tsx +29 -0
- package/frontend/src/components/shared/notification-dropdown.tsx +115 -0
- package/frontend/src/components/shared/page-header.tsx +23 -0
- package/frontend/src/components/shared/stat-card.tsx +37 -0
- package/frontend/src/components/shared/status-badge.tsx +70 -0
- package/frontend/src/components/shared/status-dot.tsx +43 -0
- package/frontend/src/components/ui/alert-dialog.tsx +187 -0
- package/frontend/src/components/ui/alert.tsx +76 -0
- package/frontend/src/components/ui/avatar.tsx +109 -0
- package/frontend/src/components/ui/badge.tsx +52 -0
- package/frontend/src/components/ui/breadcrumb.tsx +125 -0
- package/frontend/src/components/ui/button.tsx +60 -0
- package/frontend/src/components/ui/calendar.tsx +219 -0
- package/frontend/src/components/ui/card.tsx +103 -0
- package/frontend/src/components/ui/chart.tsx +371 -0
- package/frontend/src/components/ui/checkbox.tsx +29 -0
- package/frontend/src/components/ui/collapsible.tsx +19 -0
- package/frontend/src/components/ui/command.tsx +194 -0
- package/frontend/src/components/ui/dialog.tsx +158 -0
- package/frontend/src/components/ui/dropdown-menu.tsx +268 -0
- package/frontend/src/components/ui/input-group.tsx +156 -0
- package/frontend/src/components/ui/input.tsx +20 -0
- package/frontend/src/components/ui/label.tsx +20 -0
- package/frontend/src/components/ui/pagination.tsx +130 -0
- package/frontend/src/components/ui/popover.tsx +90 -0
- package/frontend/src/components/ui/progress.tsx +83 -0
- package/frontend/src/components/ui/radio-group.tsx +36 -0
- package/frontend/src/components/ui/scroll-area.tsx +54 -0
- package/frontend/src/components/ui/select.tsx +199 -0
- package/frontend/src/components/ui/separator.tsx +23 -0
- package/frontend/src/components/ui/sheet.tsx +138 -0
- package/frontend/src/components/ui/sidebar.tsx +723 -0
- package/frontend/src/components/ui/skeleton.tsx +13 -0
- package/frontend/src/components/ui/sonner.tsx +47 -0
- package/frontend/src/components/ui/switch.tsx +30 -0
- package/frontend/src/components/ui/table.tsx +114 -0
- package/frontend/src/components/ui/tabs.tsx +82 -0
- package/frontend/src/components/ui/textarea.tsx +18 -0
- package/frontend/src/components/ui/toggle-group.tsx +89 -0
- package/frontend/src/components/ui/toggle.tsx +43 -0
- package/frontend/src/components/ui/tooltip.tsx +64 -0
- package/frontend/src/hooks/use-mobile.ts +19 -0
- package/frontend/src/i18n/index.ts +20 -0
- package/frontend/src/i18n/locales/en.json +786 -0
- package/frontend/src/i18n/locales/tr.json +786 -0
- package/frontend/src/index.css +134 -0
- package/frontend/src/layouts/admin-layout.tsx +111 -0
- package/frontend/src/layouts/app-header.tsx +130 -0
- package/frontend/src/layouts/app-layout.tsx +19 -0
- package/frontend/src/layouts/app-sidebar.tsx +212 -0
- package/frontend/src/layouts/auth-guard.tsx +31 -0
- package/frontend/src/layouts/mobile-sidebar.tsx +120 -0
- package/frontend/src/lib/api.ts +68 -0
- package/frontend/src/lib/hooks/use-appointments.ts +224 -0
- package/frontend/src/lib/hooks/use-availability-blocks.ts +83 -0
- package/frontend/src/lib/hooks/use-chats.ts +236 -0
- package/frontend/src/lib/hooks/use-clinic-detail.ts +171 -0
- package/frontend/src/lib/hooks/use-clinics.ts +37 -0
- package/frontend/src/lib/hooks/use-doctor-calendar.ts +80 -0
- package/frontend/src/lib/hooks/use-examinations.ts +89 -0
- package/frontend/src/lib/hooks/use-medical-history.ts +52 -0
- package/frontend/src/lib/hooks/use-patients.ts +296 -0
- package/frontend/src/lib/hooks/use-photo-sets.ts +118 -0
- package/frontend/src/lib/hooks/use-treatment-plans.ts +152 -0
- package/frontend/src/lib/hooks/use-treatments.ts +125 -0
- package/frontend/src/lib/hooks/use-users.ts +172 -0
- package/frontend/src/lib/utils.ts +6 -0
- package/frontend/src/main.tsx +17 -0
- package/frontend/src/pages/admin/agents/detail.tsx +774 -0
- package/frontend/src/pages/admin/agents/import.tsx +280 -0
- package/frontend/src/pages/admin/agents/index.tsx +245 -0
- package/frontend/src/pages/admin/ai-playground.tsx +543 -0
- package/frontend/src/pages/appointments/create-appointment-dialog.tsx +390 -0
- package/frontend/src/pages/appointments/index.tsx +860 -0
- package/frontend/src/pages/audit/index.tsx +24 -0
- package/frontend/src/pages/auth/login.tsx +194 -0
- package/frontend/src/pages/billing/index.tsx +24 -0
- package/frontend/src/pages/calendar/index.tsx +704 -0
- package/frontend/src/pages/calendar-connections/index.tsx +295 -0
- package/frontend/src/pages/calls/index.tsx +24 -0
- package/frontend/src/pages/campaigns/index.tsx +24 -0
- package/frontend/src/pages/chats/index.tsx +981 -0
- package/frontend/src/pages/clinics/index.tsx +224 -0
- package/frontend/src/pages/clinics/settings.tsx +412 -0
- package/frontend/src/pages/components-showcase.tsx +773 -0
- package/frontend/src/pages/connections/index.tsx +328 -0
- package/frontend/src/pages/dashboard.tsx +50 -0
- package/frontend/src/pages/leads/index.tsx +24 -0
- package/frontend/src/pages/my-schedule/index.tsx +496 -0
- package/frontend/src/pages/patients/create-patient-dialog.tsx +358 -0
- package/frontend/src/pages/patients/detail.tsx +1195 -0
- package/frontend/src/pages/patients/edit-patient-dialog.tsx +387 -0
- package/frontend/src/pages/patients/examinations-tab.tsx +460 -0
- package/frontend/src/pages/patients/index.tsx +381 -0
- package/frontend/src/pages/patients/medical-history-dialog.tsx +207 -0
- package/frontend/src/pages/patients/photo-sets-tab.tsx +616 -0
- package/frontend/src/pages/patients/timeline-tab.tsx +164 -0
- package/frontend/src/pages/patients/treatment-plans-tab.tsx +598 -0
- package/frontend/src/pages/phone-numbers/detail.tsx +427 -0
- package/frontend/src/pages/phone-numbers/index.tsx +455 -0
- package/frontend/src/pages/platform/index.tsx +454 -0
- package/frontend/src/pages/settings/index.tsx +126 -0
- package/frontend/src/pages/treatments/index.tsx +487 -0
- package/frontend/src/pages/users/doctor-profile.tsx +672 -0
- package/frontend/src/pages/users/edit.tsx +329 -0
- package/frontend/src/pages/users/index.tsx +407 -0
- package/frontend/src/pages/validation/index.tsx +24 -0
- package/frontend/src/stores/auth.store.ts +108 -0
- package/frontend/src/stores/ui.store.ts +41 -0
- package/frontend/tsconfig.app.json +32 -0
- package/frontend/tsconfig.json +13 -0
- package/frontend/tsconfig.node.json +26 -0
- package/frontend/vite.config.ts +29 -0
- package/nestjs/.dockerignore +5 -0
- package/nestjs/.env.docker +64 -0
- package/nestjs/.prettierrc +4 -0
- package/nestjs/Dockerfile +36 -0
- package/nestjs/README.md +98 -0
- package/nestjs/eslint.config.mjs +35 -0
- package/nestjs/nest-cli.json +8 -0
- package/nestjs/package-lock.json +13390 -0
- package/nestjs/package.json +114 -0
- package/nestjs/prisma/migrations/20260409161536_add_message_metadata_fields/migration.sql +1746 -0
- package/nestjs/prisma/migrations/20260410140436_add_agent_ai_fields/migration.sql +36 -0
- package/nestjs/prisma/migrations/20260410175519_add_agent_clinic_assignments/migration.sql +21 -0
- package/nestjs/prisma/migrations/20260412094344_make_agent_tenant_optional/migration.sql +2 -0
- package/nestjs/prisma/migrations/20260412110008_add_admin_chat_sessions/migration.sql +47 -0
- package/nestjs/prisma/migrations/migration_lock.toml +3 -0
- package/nestjs/prisma/schema.prisma +1843 -0
- package/nestjs/prisma/seed.ts +375 -0
- package/nestjs/prisma.config.ts +14 -0
- package/nestjs/src/admin/admin.controller.ts +27 -0
- package/nestjs/src/admin/admin.module.ts +16 -0
- package/nestjs/src/admin/admin.service.ts +91 -0
- package/nestjs/src/admin/ai-chat-session.service.ts +454 -0
- package/nestjs/src/admin/ai-test.controller.ts +191 -0
- package/nestjs/src/admin/superadmin.controller.ts +106 -0
- package/nestjs/src/agents/agents.controller.ts +262 -0
- package/nestjs/src/agents/agents.module.ts +13 -0
- package/nestjs/src/agents/agents.service.ts +733 -0
- package/nestjs/src/agents/dto/create-agent.dto.ts +99 -0
- package/nestjs/src/agents/dto/index.ts +2 -0
- package/nestjs/src/agents/dto/update-agent.dto.ts +148 -0
- package/nestjs/src/app.module.ts +115 -0
- package/nestjs/src/appointment-validation/appointment-validation.controller.ts +194 -0
- package/nestjs/src/appointment-validation/appointment-validation.module.ts +16 -0
- package/nestjs/src/appointment-validation/appointment-validation.service.ts +450 -0
- package/nestjs/src/appointment-validation/dto/create-batch.dto.ts +105 -0
- package/nestjs/src/appointment-validation/dto/index.ts +1 -0
- package/nestjs/src/appointment-validation/processors/validation-dispatch.processor.ts +26 -0
- package/nestjs/src/appointment-validation/processors/validation-sync.processor.ts +23 -0
- package/nestjs/src/appointments/appointments.controller.ts +268 -0
- package/nestjs/src/appointments/appointments.module.ts +13 -0
- package/nestjs/src/appointments/appointments.service.ts +773 -0
- package/nestjs/src/appointments/dto/create-appointment.dto.ts +72 -0
- package/nestjs/src/appointments/dto/index.ts +4 -0
- package/nestjs/src/appointments/dto/query-appointments.dto.ts +60 -0
- package/nestjs/src/appointments/dto/update-appointment.dto.ts +43 -0
- package/nestjs/src/appointments/dto/update-status.dto.ts +18 -0
- package/nestjs/src/appointments/processors/reminder.processor.ts +243 -0
- package/nestjs/src/audit/audit.controller.ts +84 -0
- package/nestjs/src/audit/audit.decorator.ts +20 -0
- package/nestjs/src/audit/audit.interceptor.ts +67 -0
- package/nestjs/src/audit/audit.interfaces.ts +15 -0
- package/nestjs/src/audit/audit.module.ts +12 -0
- package/nestjs/src/audit/audit.service.ts +177 -0
- package/nestjs/src/auth/auth.controller.ts +116 -0
- package/nestjs/src/auth/auth.module.ts +25 -0
- package/nestjs/src/auth/auth.service.ts +612 -0
- package/nestjs/src/auth/dto/change-password.dto.ts +13 -0
- package/nestjs/src/auth/dto/forgot-password.dto.ts +8 -0
- package/nestjs/src/auth/dto/index.ts +6 -0
- package/nestjs/src/auth/dto/login.dto.ts +13 -0
- package/nestjs/src/auth/dto/refresh.dto.ts +8 -0
- package/nestjs/src/auth/dto/register.dto.ts +28 -0
- package/nestjs/src/auth/dto/reset-password.dto.ts +13 -0
- package/nestjs/src/auth/permissions.config.ts +85 -0
- package/nestjs/src/availability-blocks/availability-blocks.controller.ts +83 -0
- package/nestjs/src/availability-blocks/availability-blocks.module.ts +10 -0
- package/nestjs/src/availability-blocks/availability-blocks.service.ts +202 -0
- package/nestjs/src/billing/billing.controller.ts +104 -0
- package/nestjs/src/billing/billing.module.ts +12 -0
- package/nestjs/src/billing/billing.service.ts +398 -0
- package/nestjs/src/billing/dto/index.ts +2 -0
- package/nestjs/src/billing/dto/query-billing.dto.ts +29 -0
- package/nestjs/src/billing/dto/record-payment.dto.ts +60 -0
- package/nestjs/src/billing/processors/alerts.processor.ts +216 -0
- package/nestjs/src/billing/processors/snapshot.processor.ts +181 -0
- package/nestjs/src/calls/calls.controller.ts +92 -0
- package/nestjs/src/calls/calls.module.ts +10 -0
- package/nestjs/src/calls/calls.service.ts +359 -0
- package/nestjs/src/calls/dto/index.ts +1 -0
- package/nestjs/src/calls/dto/query-calls.dto.ts +46 -0
- package/nestjs/src/clinics/clinics.controller.ts +226 -0
- package/nestjs/src/clinics/clinics.module.ts +11 -0
- package/nestjs/src/clinics/clinics.service.ts +203 -0
- package/nestjs/src/clinics/dto/create-clinic.dto.ts +40 -0
- package/nestjs/src/clinics/dto/create-meta-connection.dto.ts +44 -0
- package/nestjs/src/clinics/dto/index.ts +4 -0
- package/nestjs/src/clinics/dto/update-clinic.dto.ts +133 -0
- package/nestjs/src/clinics/dto/update-meta-connection.dto.ts +22 -0
- package/nestjs/src/clinics/meta-connections.service.ts +210 -0
- package/nestjs/src/common/decorators/accessible-clinics.decorator.ts +9 -0
- package/nestjs/src/common/decorators/current-user.decorator.ts +10 -0
- package/nestjs/src/common/decorators/features.decorator.ts +7 -0
- package/nestjs/src/common/decorators/index.ts +4 -0
- package/nestjs/src/common/decorators/permissions.decorator.ts +13 -0
- package/nestjs/src/common/gateways/events.gateway.ts +157 -0
- package/nestjs/src/common/guards/clinic-access.guard.ts +40 -0
- package/nestjs/src/common/guards/features.guard.ts +38 -0
- package/nestjs/src/common/guards/index.ts +5 -0
- package/nestjs/src/common/guards/jwt-auth.guard.ts +54 -0
- package/nestjs/src/common/guards/permissions.guard.ts +50 -0
- package/nestjs/src/common/guards/superadmin.guard.ts +35 -0
- package/nestjs/src/common/interceptors/request-context.interceptor.ts +32 -0
- package/nestjs/src/common/interceptors/superadmin-tenant.interceptor.ts +42 -0
- package/nestjs/src/common/interfaces/jwt-payload.interface.ts +11 -0
- package/nestjs/src/config/config.module.ts +13 -0
- package/nestjs/src/database/database.module.ts +9 -0
- package/nestjs/src/database/prisma.service.ts +20 -0
- package/nestjs/src/database/tenant-context.ts +27 -0
- package/nestjs/src/google-calendar/google-calendar.controller.ts +259 -0
- package/nestjs/src/google-calendar/google-calendar.module.ts +10 -0
- package/nestjs/src/google-calendar/google-calendar.service.ts +811 -0
- package/nestjs/src/integrations/ai/ai.interface.ts +74 -0
- package/nestjs/src/integrations/ai/ai.module.ts +11 -0
- package/nestjs/src/integrations/ai/ai.service.ts +148 -0
- package/nestjs/src/integrations/ai/providers/anthropic.provider.ts +146 -0
- package/nestjs/src/integrations/ai/providers/openai.provider.ts +158 -0
- package/nestjs/src/integrations/elevenlabs/elevenlabs.module.ts +8 -0
- package/nestjs/src/integrations/elevenlabs/elevenlabs.service.ts +226 -0
- package/nestjs/src/integrations/encryption/encryption.module.ts +9 -0
- package/nestjs/src/integrations/encryption/encryption.service.ts +31 -0
- package/nestjs/src/integrations/image/image.module.ts +9 -0
- package/nestjs/src/integrations/image/image.service.ts +61 -0
- package/nestjs/src/integrations/meta-business/meta-business.module.ts +10 -0
- package/nestjs/src/integrations/meta-business/meta-instagram.service.ts +94 -0
- package/nestjs/src/integrations/meta-business/meta-webhook.service.ts +52 -0
- package/nestjs/src/integrations/meta-business/meta-whatsapp.service.ts +254 -0
- package/nestjs/src/integrations/minio/minio.module.ts +9 -0
- package/nestjs/src/integrations/minio/minio.service.ts +88 -0
- package/nestjs/src/integrations/netgsm/netgsm.module.ts +8 -0
- package/nestjs/src/integrations/netgsm/netgsm.service.ts +17 -0
- package/nestjs/src/integrations/tektippay/tektippay.module.ts +8 -0
- package/nestjs/src/integrations/tektippay/tektippay.service.ts +23 -0
- package/nestjs/src/leads/dto/index.ts +2 -0
- package/nestjs/src/leads/dto/query-leads.dto.ts +50 -0
- package/nestjs/src/leads/dto/update-lead.dto.ts +26 -0
- package/nestjs/src/leads/leads.controller.ts +184 -0
- package/nestjs/src/leads/leads.module.ts +10 -0
- package/nestjs/src/leads/leads.service.ts +375 -0
- package/nestjs/src/logging/logging.controller.ts +82 -0
- package/nestjs/src/logging/logging.module.ts +11 -0
- package/nestjs/src/logging/logging.service.ts +180 -0
- package/nestjs/src/main.ts +86 -0
- package/nestjs/src/outbound-campaigns/dto/create-campaign.dto.ts +47 -0
- package/nestjs/src/outbound-campaigns/dto/index.ts +3 -0
- package/nestjs/src/outbound-campaigns/dto/update-campaign.dto.ts +31 -0
- package/nestjs/src/outbound-campaigns/dto/upload-entries.dto.ts +35 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.controller.ts +307 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.module.ts +13 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.service.ts +471 -0
- package/nestjs/src/outbound-campaigns/processors/campaign-dispatch.processor.ts +366 -0
- package/nestjs/src/patients/documents.service.ts +231 -0
- package/nestjs/src/patients/dto/create-examination.dto.ts +34 -0
- package/nestjs/src/patients/dto/create-note.dto.ts +14 -0
- package/nestjs/src/patients/dto/create-patient.dto.ts +86 -0
- package/nestjs/src/patients/dto/create-photo-set.dto.ts +32 -0
- package/nestjs/src/patients/dto/index.ts +10 -0
- package/nestjs/src/patients/dto/update-examination.dto.ts +29 -0
- package/nestjs/src/patients/dto/update-medical-history.dto.ts +47 -0
- package/nestjs/src/patients/dto/update-patient.dto.ts +87 -0
- package/nestjs/src/patients/dto/update-photo-set.dto.ts +28 -0
- package/nestjs/src/patients/dto/upload-document.dto.ts +31 -0
- package/nestjs/src/patients/dto/upload-photo.dto.ts +27 -0
- package/nestjs/src/patients/examinations.service.ts +271 -0
- package/nestjs/src/patients/medical-history.service.ts +149 -0
- package/nestjs/src/patients/notes.service.ts +172 -0
- package/nestjs/src/patients/patients.controller.ts +485 -0
- package/nestjs/src/patients/patients.module.ts +22 -0
- package/nestjs/src/patients/patients.service.ts +412 -0
- package/nestjs/src/patients/photo-sets.service.ts +389 -0
- package/nestjs/src/phone-numbers/dto/create-phone-number.dto.ts +57 -0
- package/nestjs/src/phone-numbers/dto/index.ts +3 -0
- package/nestjs/src/phone-numbers/dto/update-phone-number.dto.ts +38 -0
- package/nestjs/src/phone-numbers/dto/update-schedule.dto.ts +39 -0
- package/nestjs/src/phone-numbers/phone-numbers.controller.ts +125 -0
- package/nestjs/src/phone-numbers/phone-numbers.module.ts +10 -0
- package/nestjs/src/phone-numbers/phone-numbers.service.ts +209 -0
- package/nestjs/src/plans/dto/create-plan.dto.ts +70 -0
- package/nestjs/src/plans/dto/index.ts +2 -0
- package/nestjs/src/plans/dto/update-plan.dto.ts +80 -0
- package/nestjs/src/plans/plans.controller.ts +50 -0
- package/nestjs/src/plans/plans.module.ts +10 -0
- package/nestjs/src/plans/plans.service.ts +115 -0
- package/nestjs/src/platform/dto/create-tenant.dto.ts +36 -0
- package/nestjs/src/platform/dto/index.ts +2 -0
- package/nestjs/src/platform/dto/update-tenant-platform.dto.ts +44 -0
- package/nestjs/src/platform/platform.controller.ts +79 -0
- package/nestjs/src/platform/platform.module.ts +10 -0
- package/nestjs/src/platform/platform.service.ts +301 -0
- package/nestjs/src/queue/queue.module.ts +56 -0
- package/nestjs/src/redis/redis.module.ts +20 -0
- package/nestjs/src/tenants/dto/index.ts +1 -0
- package/nestjs/src/tenants/dto/update-tenant.dto.ts +15 -0
- package/nestjs/src/tenants/tenants.controller.ts +45 -0
- package/nestjs/src/tenants/tenants.module.ts +10 -0
- package/nestjs/src/tenants/tenants.service.ts +41 -0
- package/nestjs/src/tools/adapters/elevenlabs-http.adapter.ts +51 -0
- package/nestjs/src/tools/adapters/elevenlabs-ws.adapter.ts +59 -0
- package/nestjs/src/tools/handlers/calendar.tools.ts +441 -0
- package/nestjs/src/tools/handlers/notification.tools.ts +34 -0
- package/nestjs/src/tools/handlers/operator.tools.ts +303 -0
- package/nestjs/src/tools/handlers/patient.tools.ts +242 -0
- package/nestjs/src/tools/handlers/payment.tools.ts +43 -0
- package/nestjs/src/tools/handlers/validation.tools.ts +152 -0
- package/nestjs/src/tools/tool-registry.service.ts +221 -0
- package/nestjs/src/tools/tool.decorator.ts +16 -0
- package/nestjs/src/tools/tool.interfaces.ts +26 -0
- package/nestjs/src/tools/tools.module.ts +50 -0
- package/nestjs/src/treatments/dto/create-plan-item.dto.ts +27 -0
- package/nestjs/src/treatments/dto/create-treatment-plan.dto.ts +69 -0
- package/nestjs/src/treatments/dto/create-treatment.dto.ts +59 -0
- package/nestjs/src/treatments/dto/index.ts +6 -0
- package/nestjs/src/treatments/dto/update-plan-item.dto.ts +23 -0
- package/nestjs/src/treatments/dto/update-treatment-plan.dto.ts +22 -0
- package/nestjs/src/treatments/dto/update-treatment.dto.ts +70 -0
- package/nestjs/src/treatments/treatment-plans.service.ts +362 -0
- package/nestjs/src/treatments/treatments.controller.ts +265 -0
- package/nestjs/src/treatments/treatments.module.ts +14 -0
- package/nestjs/src/treatments/treatments.service.ts +165 -0
- package/nestjs/src/users/doctor-profiles.service.ts +202 -0
- package/nestjs/src/users/dto/index.ts +4 -0
- package/nestjs/src/users/dto/invite-user.dto.ts +52 -0
- package/nestjs/src/users/dto/update-clinic-assignments.dto.ts +9 -0
- package/nestjs/src/users/dto/update-doctor-profile.dto.ts +49 -0
- package/nestjs/src/users/dto/update-user.dto.ts +41 -0
- package/nestjs/src/users/users.controller.ts +142 -0
- package/nestjs/src/users/users.module.ts +11 -0
- package/nestjs/src/users/users.service.ts +250 -0
- package/nestjs/src/webhooks/elevenlabs-tool.controller.ts +66 -0
- package/nestjs/src/webhooks/elevenlabs-webhook.controller.ts +60 -0
- package/nestjs/src/webhooks/meta-webhook.controller.ts +178 -0
- package/nestjs/src/webhooks/processors/elevenlabs-webhook.processor.ts +28 -0
- package/nestjs/src/webhooks/webhooks.module.ts +17 -0
- package/nestjs/src/whatsapp/chat-context.service.ts +281 -0
- package/nestjs/src/whatsapp/dto/add-blacklist.dto.ts +13 -0
- package/nestjs/src/whatsapp/dto/index.ts +2 -0
- package/nestjs/src/whatsapp/dto/send-message.dto.ts +14 -0
- package/nestjs/src/whatsapp/listeners/meta-message.listener.ts +579 -0
- package/nestjs/src/whatsapp/meta-auth.controller.ts +268 -0
- package/nestjs/src/whatsapp/meta-instagram-auth.controller.ts +244 -0
- package/nestjs/src/whatsapp/operator.service.ts +692 -0
- package/nestjs/src/whatsapp/processors/cost-sweep.processor.ts +130 -0
- package/nestjs/src/whatsapp/processors/grace.processor.ts +191 -0
- package/nestjs/src/whatsapp/processors/message-buffer.processor.ts +138 -0
- package/nestjs/src/whatsapp/processors/message-cleanup.processor.ts +148 -0
- package/nestjs/src/whatsapp/processors/meta-token-refresh.processor.ts +114 -0
- package/nestjs/src/whatsapp/processors/operator-expiry.processor.ts +105 -0
- package/nestjs/src/whatsapp/processors/profile-update.processor.ts +234 -0
- package/nestjs/src/whatsapp/processors/session-cleanup.processor.ts +178 -0
- package/nestjs/src/whatsapp/processors/session-labels.processor.ts +248 -0
- package/nestjs/src/whatsapp/whatsapp-agent.service.ts +2506 -0
- package/nestjs/src/whatsapp/whatsapp-recovery.service.ts +117 -0
- package/nestjs/src/whatsapp/whatsapp.controller.ts +398 -0
- package/nestjs/src/whatsapp/whatsapp.module.ts +51 -0
- package/nestjs/src/whatsapp/whatsapp.service.ts +592 -0
- package/nestjs/test/app.e2e-spec.ts +25 -0
- package/nestjs/test/jest-e2e.json +9 -0
- package/nestjs/tsconfig.build.json +4 -0
- package/nestjs/tsconfig.json +25 -0
- package/nginx.example.conf +18 -0
- package/package.json +47 -0
- package/scripts/addRecipient.ts +48 -0
- package/scripts/listRecipients.ts +31 -0
- package/scripts/migrate-agent-ref.ts +86 -0
- package/scripts/migrate-sessions.ts +183 -0
- package/scripts/promote.ts +27 -0
- package/scripts/retrigger.ts +84 -0
- package/scripts/seed.ts +435 -0
- package/scripts/testSend.ts +63 -0
- package/src/app.ts +85 -0
- package/src/config/agentPrompts.json +19 -0
- package/src/config/database.ts +21 -0
- package/src/config/env.ts +94 -0
- package/src/config/index.ts +86 -0
- package/src/controllers/webhook.controller.ts +150 -0
- package/src/errors/index.ts +9 -0
- package/src/hooks/webhookValidator.ts +55 -0
- package/src/index.ts +68 -0
- package/src/migrations/001_session-architecture.ts +138 -0
- package/src/migrations/002_agent-ref.ts +65 -0
- package/src/migrations/003_shift-schedule.ts +55 -0
- package/src/migrations/004_invert-shift-to-clinic-hours.ts +30 -0
- package/src/migrations/005_composite-baileys-chat-ids.ts +60 -0
- package/src/migrations/migration.model.ts +27 -0
- package/src/migrations/migration.routes.ts +40 -0
- package/src/migrations/migration.runner.ts +112 -0
- package/src/models/ConversationClaim.ts +17 -0
- package/src/models/ConversationEvaluation.ts +91 -0
- package/src/models/Summary.ts +77 -0
- package/src/models/Transcription.ts +68 -0
- package/src/models/WhatsAppRecipient.ts +37 -0
- package/src/modules/admin/admin.routes.ts +2385 -0
- package/src/modules/admin/elevenlabs-test-chat.routes.ts +193 -0
- package/src/modules/admin/whatsapp-test-chat.routes.ts +244 -0
- package/src/modules/agents/agent.model.ts +93 -0
- package/src/modules/agents/agent.routes.ts +65 -0
- package/src/modules/appointment-validation/appointment-validation.model.ts +163 -0
- package/src/modules/appointment-validation/appointment-validation.routes.ts +275 -0
- package/src/modules/appointment-validation/appointment-validation.service.ts +1028 -0
- package/src/modules/auth/auth.model.ts +42 -0
- package/src/modules/auth/auth.routes.ts +199 -0
- package/src/modules/auth/auth.service.ts +210 -0
- package/src/modules/auth/refresh-token.model.ts +26 -0
- package/src/modules/billing/billing-alert.model.ts +28 -0
- package/src/modules/billing/billing-period-snapshot.model.ts +68 -0
- package/src/modules/billing/billing.model.ts +42 -0
- package/src/modules/billing/billing.routes.ts +67 -0
- package/src/modules/billing/billing.service.ts +562 -0
- package/src/modules/billing/payment.model.ts +42 -0
- package/src/modules/calls/call.model.ts +102 -0
- package/src/modules/calls/call.routes.ts +118 -0
- package/src/modules/campaigns/campaign.model.ts +111 -0
- package/src/modules/campaigns/campaign.routes.ts +402 -0
- package/src/modules/campaigns/campaign.service.ts +99 -0
- package/src/modules/google-calendar/google-calendar.routes.ts +31 -0
- package/src/modules/inbound-call/inbound-call-config.model.ts +49 -0
- package/src/modules/inbound-call/inbound-call.routes.ts +289 -0
- package/src/modules/leads/lead.model.ts +40 -0
- package/src/modules/leads/lead.routes.ts +246 -0
- package/src/modules/logs/log.model.ts +27 -0
- package/src/modules/logs/log.routes.ts +102 -0
- package/src/modules/plans/plan.model.ts +45 -0
- package/src/modules/surveys/survey.model.ts +70 -0
- package/src/modules/surveys/survey.routes.ts +187 -0
- package/src/modules/tenants/tenant.model.ts +181 -0
- package/src/modules/tenants/tenant.routes.ts +78 -0
- package/src/modules/users/user.routes.ts +126 -0
- package/src/modules/webhooks/elevenlabs-tool.routes.ts +94 -0
- package/src/modules/webhooks/elevenlabs-tool.service.ts +491 -0
- package/src/modules/webhooks/elevenlabs.routes.ts +34 -0
- package/src/modules/webhooks/elevenlabs.service.ts +565 -0
- package/src/modules/webhooks/unipile.routes.ts +917 -0
- package/src/modules/whatsapp/appointment.model.ts +47 -0
- package/src/modules/whatsapp/operator-request.model.ts +58 -0
- package/src/modules/whatsapp/stt-usage.model.ts +30 -0
- package/src/modules/whatsapp/whatsapp-chat.model.ts +39 -0
- package/src/modules/whatsapp/whatsapp-contact-profile.model.ts +41 -0
- package/src/modules/whatsapp/whatsapp-message.model.ts +41 -0
- package/src/modules/whatsapp/whatsapp-session.model.ts +60 -0
- package/src/modules/whatsapp/whatsapp.routes.ts +1435 -0
- package/src/plugins/cors.ts +7 -0
- package/src/plugins/jwt.ts +18 -0
- package/src/plugins/rawBody.ts +12 -0
- package/src/plugins/rbac.ts +24 -0
- package/src/routes/admin.routes.ts +208 -0
- package/src/routes/health.routes.ts +12 -0
- package/src/routes/webhook.routes.ts +12 -0
- package/src/services/ai/base.ai.ts +132 -0
- package/src/services/ai/gemini.service.ts +41 -0
- package/src/services/ai/index.ts +24 -0
- package/src/services/ai/openai.service.ts +48 -0
- package/src/services/elevenlabs.service.ts +532 -0
- package/src/services/google-calendar.service.ts +656 -0
- package/src/services/inbound-call-schedule.service.ts +174 -0
- package/src/services/netgsm.service.ts +128 -0
- package/src/services/unipile.service.ts +200 -0
- package/src/services/whatsapp-agent.service.ts +2479 -0
- package/src/services/whatsapp.service.ts +245 -0
- package/src/templates/index.ts +71 -0
- package/src/templates/receptionist.ts +44 -0
- package/src/templates/survey.ts +44 -0
- package/src/types/index.ts +218 -0
- package/src/utils/logger.ts +83 -0
- package/tsconfig.json +19 -0
- package/web/.dockerignore +4 -0
- package/web/Dockerfile +18 -0
- package/web/README.md +73 -0
- package/web/components.json +23 -0
- package/web/eslint.config.js +23 -0
- package/web/index.html +14 -0
- package/web/nginx.conf +18 -0
- package/web/package-lock.json +10292 -0
- package/web/package.json +48 -0
- package/web/public/favicon.ico +0 -0
- package/web/public/vite.svg +1 -0
- package/web/src/App.tsx +191 -0
- package/web/src/assets/react.svg +1 -0
- package/web/src/components/Layout.tsx +261 -0
- package/web/src/components/LeadConversation.tsx +251 -0
- package/web/src/components/ProtectedRoute.tsx +20 -0
- package/web/src/components/conversation-review/CardAudioPlayer.tsx +200 -0
- package/web/src/components/conversation-review/CardEvaluation.tsx +351 -0
- package/web/src/components/conversation-review/CardMetadata.tsx +44 -0
- package/web/src/components/conversation-review/ConversationCard.tsx +95 -0
- package/web/src/components/conversation-review/ReviewContainer.tsx +120 -0
- package/web/src/components/conversation-review/ReviewFilters.tsx +88 -0
- package/web/src/components/empty-state.tsx +15 -0
- package/web/src/components/page-header.tsx +19 -0
- package/web/src/components/page-loader.tsx +32 -0
- package/web/src/components/pagination.tsx +38 -0
- package/web/src/components/stat-card.tsx +27 -0
- package/web/src/components/status-badge.tsx +125 -0
- package/web/src/components/ui/alert.tsx +66 -0
- package/web/src/components/ui/badge.tsx +48 -0
- package/web/src/components/ui/button.tsx +64 -0
- package/web/src/components/ui/card.tsx +92 -0
- package/web/src/components/ui/checkbox.tsx +32 -0
- package/web/src/components/ui/dialog.tsx +158 -0
- package/web/src/components/ui/dropdown-menu.tsx +255 -0
- package/web/src/components/ui/input.tsx +21 -0
- package/web/src/components/ui/label.tsx +22 -0
- package/web/src/components/ui/progress.tsx +29 -0
- package/web/src/components/ui/select.tsx +188 -0
- package/web/src/components/ui/separator.tsx +28 -0
- package/web/src/components/ui/sheet.tsx +123 -0
- package/web/src/components/ui/skeleton.tsx +13 -0
- package/web/src/components/ui/sonner.tsx +35 -0
- package/web/src/components/ui/table.tsx +116 -0
- package/web/src/components/ui/tabs.tsx +89 -0
- package/web/src/components/ui/textarea.tsx +18 -0
- package/web/src/components/ui/tooltip.tsx +57 -0
- package/web/src/components/whatsapp/ChatDetail.tsx +417 -0
- package/web/src/components/whatsapp/ChatHeader.tsx +78 -0
- package/web/src/components/whatsapp/ChatList.tsx +107 -0
- package/web/src/components/whatsapp/ChatListItem.tsx +60 -0
- package/web/src/components/whatsapp/MessageBubble.tsx +46 -0
- package/web/src/components/whatsapp/MessageInput.tsx +63 -0
- package/web/src/components/whatsapp/MessageStream.tsx +135 -0
- package/web/src/components/whatsapp/SessionDivider.tsx +65 -0
- package/web/src/components/whatsapp/SessionHistory.tsx +119 -0
- package/web/src/components/whatsapp/settings/AiSettingsTab.tsx +268 -0
- package/web/src/components/whatsapp/settings/CalendarTab.tsx +339 -0
- package/web/src/components/whatsapp/settings/ConnectionTab.tsx +236 -0
- package/web/src/components/whatsapp/settings/NotificationsTab.tsx +109 -0
- package/web/src/components/whatsapp/settings/OperatorTab.tsx +303 -0
- package/web/src/contexts/AuthContext.tsx +103 -0
- package/web/src/index.css +130 -0
- package/web/src/lib/api.ts +92 -0
- package/web/src/lib/utils.ts +6 -0
- package/web/src/main.tsx +10 -0
- package/web/src/pages/AppointmentDetail.tsx +206 -0
- package/web/src/pages/AppointmentValidation.tsx +157 -0
- package/web/src/pages/AppointmentValidationDetail.tsx +617 -0
- package/web/src/pages/AppointmentValidationNew.tsx +1005 -0
- package/web/src/pages/Appointments.tsx +283 -0
- package/web/src/pages/Billing.tsx +126 -0
- package/web/src/pages/CallDetail.tsx +293 -0
- package/web/src/pages/CallSettings.tsx +313 -0
- package/web/src/pages/Calls.tsx +188 -0
- package/web/src/pages/CampaignDetail.tsx +216 -0
- package/web/src/pages/CampaignNew.tsx +277 -0
- package/web/src/pages/CampaignResults.tsx +185 -0
- package/web/src/pages/Campaigns.tsx +171 -0
- package/web/src/pages/Dashboard.tsx +336 -0
- package/web/src/pages/InboundSchedule.tsx +246 -0
- package/web/src/pages/LeadDetail.tsx +183 -0
- package/web/src/pages/Leads.tsx +258 -0
- package/web/src/pages/Login.tsx +99 -0
- package/web/src/pages/Register.tsx +129 -0
- package/web/src/pages/Settings.tsx +133 -0
- package/web/src/pages/SurveyDetail.tsx +232 -0
- package/web/src/pages/SurveyPreview.tsx +179 -0
- package/web/src/pages/Surveys.tsx +207 -0
- package/web/src/pages/Users.tsx +199 -0
- package/web/src/pages/WhatsApp.tsx +147 -0
- package/web/src/pages/WhatsAppSettings.tsx +215 -0
- package/web/src/pages/admin/AdminBaileysMessageLog.tsx +331 -0
- package/web/src/pages/admin/AdminBaileysRawMessages.tsx +318 -0
- package/web/src/pages/admin/AdminConversationReview.tsx +116 -0
- package/web/src/pages/admin/AdminCredits.tsx +467 -0
- package/web/src/pages/admin/AdminElevenLabsAgentDetail.tsx +332 -0
- package/web/src/pages/admin/AdminElevenLabsAgents.tsx +164 -0
- package/web/src/pages/admin/AdminElevenLabsBatchCallDetail.tsx +214 -0
- package/web/src/pages/admin/AdminElevenLabsBatchCalls.tsx +293 -0
- package/web/src/pages/admin/AdminElevenLabsConversationDetail.tsx +230 -0
- package/web/src/pages/admin/AdminElevenLabsConversations.tsx +228 -0
- package/web/src/pages/admin/AdminElevenLabsInboundCalls.tsx +293 -0
- package/web/src/pages/admin/AdminElevenLabsPhoneNumbers.tsx +506 -0
- package/web/src/pages/admin/AdminElevenLabsTestChat.tsx +258 -0
- package/web/src/pages/admin/AdminElevenLabsWebhooks.tsx +289 -0
- package/web/src/pages/admin/AdminEvaluationDashboard.tsx +520 -0
- package/web/src/pages/admin/AdminLeads.tsx +339 -0
- package/web/src/pages/admin/AdminLogs.tsx +283 -0
- package/web/src/pages/admin/AdminMigrations.tsx +247 -0
- package/web/src/pages/admin/AdminPlans.tsx +313 -0
- package/web/src/pages/admin/AdminSystem.tsx +391 -0
- package/web/src/pages/admin/AdminTenantBillableItems.tsx +464 -0
- package/web/src/pages/admin/AdminTenantDetail.tsx +1317 -0
- package/web/src/pages/admin/AdminTenants.tsx +274 -0
- package/web/src/pages/admin/AdminWhatsApp.tsx +618 -0
- package/web/src/pages/admin/AdminWhatsAppTestChat.tsx +328 -0
- package/web/src/types/index.ts +242 -0
- package/web/tsconfig.app.json +32 -0
- package/web/tsconfig.json +13 -0
- package/web/tsconfig.node.json +26 -0
- package/web/vite.config.ts +23 -0
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = unipileWebhookRoutes;
|
|
7
|
+
const config_1 = __importDefault(require("../../config"));
|
|
8
|
+
const logger_1 = __importDefault(require("../../utils/logger"));
|
|
9
|
+
const tenant_model_1 = __importDefault(require("../tenants/tenant.model"));
|
|
10
|
+
const agent_model_1 = __importDefault(require("../agents/agent.model"));
|
|
11
|
+
const whatsapp_chat_model_1 = __importDefault(require("../whatsapp/whatsapp-chat.model"));
|
|
12
|
+
const whatsapp_session_model_1 = __importDefault(require("../whatsapp/whatsapp-session.model"));
|
|
13
|
+
const whatsapp_message_model_1 = __importDefault(require("../whatsapp/whatsapp-message.model"));
|
|
14
|
+
const whatsapp_contact_profile_model_1 = __importDefault(require("../whatsapp/whatsapp-contact-profile.model"));
|
|
15
|
+
const whatsapp_agent_service_1 = __importDefault(require("../../services/whatsapp-agent.service"));
|
|
16
|
+
const unipile_service_1 = __importDefault(require("../../services/unipile.service"));
|
|
17
|
+
const elevenlabs_service_1 = __importDefault(require("../../services/elevenlabs.service"));
|
|
18
|
+
const operator_request_model_1 = __importDefault(require("../whatsapp/operator-request.model"));
|
|
19
|
+
const stt_usage_model_1 = __importDefault(require("../whatsapp/stt-usage.model"));
|
|
20
|
+
async function unipileWebhookRoutes(fastify) {
|
|
21
|
+
// Accept any content type Unipile might send
|
|
22
|
+
fastify.addContentTypeParser('*', { parseAs: 'string' }, (_request, payload, done) => {
|
|
23
|
+
try {
|
|
24
|
+
done(null, JSON.parse(payload));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
done(null, payload);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
fastify.post('/', async (request, reply) => {
|
|
31
|
+
// Validate shared secret — accept if it matches either provider's secret
|
|
32
|
+
const secret = request.headers['x-unipile-secret'];
|
|
33
|
+
if (secret) {
|
|
34
|
+
const validSecrets = [config_1.default.unipile.webhookSecret, config_1.default.baileys.webhookSecret].filter(Boolean);
|
|
35
|
+
if (validSecrets.length > 0 && !validSecrets.includes(secret)) {
|
|
36
|
+
logger_1.default.warn('Invalid webhook secret');
|
|
37
|
+
return reply.status(401).send({ error: 'Invalid secret' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const payload = request.body;
|
|
41
|
+
// Respond immediately
|
|
42
|
+
reply.status(200).send({ status: 'received' });
|
|
43
|
+
// Process in background
|
|
44
|
+
processUnipileWebhook(payload).catch(error => {
|
|
45
|
+
logger_1.default.error('Unipile webhook processing failed', {
|
|
46
|
+
error: error.message,
|
|
47
|
+
stack: error.stack,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function isValidPhoneNumber(phone) {
|
|
53
|
+
return /^\+?\d{10,15}$/.test(phone.replace(/[\s\-()]/g, ''));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a new WhatsAppSession for a chat and link it.
|
|
57
|
+
*/
|
|
58
|
+
async function createSession(chat, tenantId, gracePeriodSeconds) {
|
|
59
|
+
const deadline = new Date(Date.now() + gracePeriodSeconds * 1000);
|
|
60
|
+
const session = await whatsapp_session_model_1.default.create({
|
|
61
|
+
chat_id: chat._id,
|
|
62
|
+
tenant_id: tenantId,
|
|
63
|
+
status: 'waiting',
|
|
64
|
+
grace_deadline: deadline,
|
|
65
|
+
started_at: new Date(),
|
|
66
|
+
});
|
|
67
|
+
chat.active_session_id = session._id;
|
|
68
|
+
if (chat.is_closed)
|
|
69
|
+
chat.is_closed = false;
|
|
70
|
+
if (chat.deleted_at)
|
|
71
|
+
chat.deleted_at = null;
|
|
72
|
+
await chat.save();
|
|
73
|
+
// Increment profile session count
|
|
74
|
+
if (chat.contact_profile_id) {
|
|
75
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: chat.contact_profile_id }, { $inc: { total_sessions: 1 } });
|
|
76
|
+
}
|
|
77
|
+
return session;
|
|
78
|
+
}
|
|
79
|
+
const UNIPILE_MEDIA_PLACEHOLDER = '-- Unipile cannot display this type of message yet';
|
|
80
|
+
const FRIENDLY_MEDIA_TEXT = 'Bu mesajı görüntülemek için WhatsApp uygulamasını kontrol edin';
|
|
81
|
+
async function processUnipileWebhook(payload) {
|
|
82
|
+
logger_1.default.info('Unipile webhook payload', { payload: JSON.stringify(payload).substring(0, 1000) });
|
|
83
|
+
// Only handle message_received events
|
|
84
|
+
if (payload.event !== 'message_received') {
|
|
85
|
+
logger_1.default.debug('Skipping non-message Unipile event', { event: payload.event });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Only handle WhatsApp
|
|
89
|
+
if (payload.account_type !== 'WHATSAPP') {
|
|
90
|
+
logger_1.default.debug('Skipping non-WhatsApp Unipile event', { type: payload.account_type });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Skip empty messages
|
|
94
|
+
if (!payload.message?.trim()) {
|
|
95
|
+
logger_1.default.debug('Ignoring empty message');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// ─── System message detection (two layers) ───────────────
|
|
99
|
+
// Layer 1: Unipile structural fields (most reliable)
|
|
100
|
+
// Layer 2: Text pattern fallback (for event_type=0 / unsupported events)
|
|
101
|
+
// System messages are stored for visibility but never trigger AI or sessions.
|
|
102
|
+
const SYSTEM_MESSAGE_PATTERNS = [
|
|
103
|
+
// EN — Voice/video calls
|
|
104
|
+
/^(incoming|missed|declined|unanswered|group) (voice|video) call/i,
|
|
105
|
+
/^voice call(,| ended| answered)/i,
|
|
106
|
+
/^video call(,| ended| answered)/i,
|
|
107
|
+
// TR — Voice/video calls
|
|
108
|
+
/^(gelen|cevaps[ıi]z|reddedilen|cevaplanmayan|grup) (sesli|g[öo]r[üu]nt[üu]l[üu]) arama/i,
|
|
109
|
+
/^sesli arama (sona erdi|cevapland)/i,
|
|
110
|
+
/^g[öo]r[üu]nt[üu]l[üu] arama (sona erdi|cevapland)/i,
|
|
111
|
+
// EN — Message deletion
|
|
112
|
+
/^this message was deleted/i,
|
|
113
|
+
/^you deleted this message/i,
|
|
114
|
+
// TR — Message deletion
|
|
115
|
+
/^bu mesaj silindi/i,
|
|
116
|
+
/^bu mesaj[ıi] sildiniz/i,
|
|
117
|
+
/taraf[ıi]ndan silindi/i,
|
|
118
|
+
// EN — Encryption & security
|
|
119
|
+
/^messages and calls are end-to-end encrypted/i,
|
|
120
|
+
/security code.*(changed|verified)/i,
|
|
121
|
+
// TR — Encryption & security
|
|
122
|
+
/mesajlar ve aramalar u[çc]tan u[çc]a [şs]ifrelidir/i,
|
|
123
|
+
/g[üu]venlik kodu.* de[gğ]i[şs]ti/i,
|
|
124
|
+
// EN — Disappearing messages
|
|
125
|
+
/turned (on|off) disappearing messages/i,
|
|
126
|
+
/disappearing messages are (on|off)/i,
|
|
127
|
+
// TR — Disappearing messages
|
|
128
|
+
/s[üu]reli mesajlar[ıi] (a[çc]t|kapatt)/i,
|
|
129
|
+
/s[üu]reli mesajlar (a[çc][ıi]k|kapal[ıi])/i,
|
|
130
|
+
// EN — Waiting / pending
|
|
131
|
+
/^waiting for this message/i,
|
|
132
|
+
// TR — Waiting / pending
|
|
133
|
+
/^bu mesaj bekleniyor/i,
|
|
134
|
+
// EN — Live location
|
|
135
|
+
/is sharing live location/i,
|
|
136
|
+
/stopped sharing live location/i,
|
|
137
|
+
// TR — Live location
|
|
138
|
+
/canl[ıi] konum payla[şs]/i,
|
|
139
|
+
// EN — Payment
|
|
140
|
+
/^payment (sent|received)/i,
|
|
141
|
+
/^payment request (sent|received)/i,
|
|
142
|
+
// TR — Payment
|
|
143
|
+
/^[öo]deme (g[öo]nderildi|al[ıi]nd[ıi])/i,
|
|
144
|
+
/^[öo]deme iste[gğ]i (g[öo]nderildi|al[ıi]nd[ıi])/i,
|
|
145
|
+
// EN — Business account
|
|
146
|
+
/this chat is with a business account/i,
|
|
147
|
+
/uses a secure service from Meta/i,
|
|
148
|
+
// TR — Business account
|
|
149
|
+
/bu sohbet bir i[şs]letme hesab[ıi]yla/i,
|
|
150
|
+
// EN — Group management
|
|
151
|
+
/^you were (added|removed)/i,
|
|
152
|
+
/^you('re| are) now an admin/i,
|
|
153
|
+
/^you('re| are) no longer an admin/i,
|
|
154
|
+
/is now an admin$/i,
|
|
155
|
+
/is no longer an admin$/i,
|
|
156
|
+
/joined using this group's invite link/i,
|
|
157
|
+
/pinned a message$/i,
|
|
158
|
+
/unpinned a message$/i,
|
|
159
|
+
/changed the (subject|group description)/i,
|
|
160
|
+
/changed this group's icon/i,
|
|
161
|
+
/only admins can (send messages|edit)/i,
|
|
162
|
+
/all participants can (send messages|edit)/i,
|
|
163
|
+
// TR — Group management
|
|
164
|
+
/art[ıi]k bir y[öo]netici/i,
|
|
165
|
+
/art[ıi]k y[öo]netici de[gğ]il/i,
|
|
166
|
+
/grubun davet ba[gğ]lant[ıi]s[ıi]n[ıi] kullanarak kat[ıi]ld[ıi]/i,
|
|
167
|
+
/bir mesaj[ıi] sabitledi/i,
|
|
168
|
+
/sabitlenen mesaj[ıi] kald[ıi]rd[ıi]/i,
|
|
169
|
+
// EN — View once
|
|
170
|
+
/sent a view once (message|photo|video)/i,
|
|
171
|
+
// TR — View once
|
|
172
|
+
/bir kez g[öo]r[üu]nt[üu]lenen (mesaj|foto|video)/i,
|
|
173
|
+
// EN — Old version
|
|
174
|
+
/using an old(er)? version of WhatsApp/i,
|
|
175
|
+
/need(s)? to update WhatsApp/i,
|
|
176
|
+
];
|
|
177
|
+
const trimmedMessage = payload.message.trim();
|
|
178
|
+
const isSystemMessage = payload.is_event === true ||
|
|
179
|
+
payload.hidden === true ||
|
|
180
|
+
SYSTEM_MESSAGE_PATTERNS.some(pattern => pattern.test(trimmedMessage));
|
|
181
|
+
if (isSystemMessage) {
|
|
182
|
+
logger_1.default.info('System message detected — AI will be skipped', {
|
|
183
|
+
chat_id: payload.chat_id,
|
|
184
|
+
message: trimmedMessage.substring(0, 80),
|
|
185
|
+
is_event: payload.is_event,
|
|
186
|
+
event_type: payload.event_type,
|
|
187
|
+
hidden: payload.hidden,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// Detect Unipile media placeholder and replace with friendly text.
|
|
191
|
+
// Baileys now sends descriptive labels like "[Kullanıcı fotoğraf gönderdi]"
|
|
192
|
+
// which pass through to the AI. Only the legacy generic placeholder gets replaced.
|
|
193
|
+
const isLegacyMediaPlaceholder = payload.message === UNIPILE_MEDIA_PLACEHOLDER;
|
|
194
|
+
if (isLegacyMediaPlaceholder) {
|
|
195
|
+
payload.message = FRIENDLY_MEDIA_TEXT;
|
|
196
|
+
}
|
|
197
|
+
// Find tenant by unipile_account_id
|
|
198
|
+
const tenant = await tenant_model_1.default.findOne({
|
|
199
|
+
'settings.whatsapp_agent.unipile_account_id': payload.account_id,
|
|
200
|
+
enabled_features: 'whatsapp_agent',
|
|
201
|
+
});
|
|
202
|
+
if (!tenant) {
|
|
203
|
+
logger_1.default.warn('No tenant found for Unipile account', { account_id: payload.account_id });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// ─── Voice message transcription (STT) ──────────────────────
|
|
207
|
+
if (payload.content_type === 'audioMessage' &&
|
|
208
|
+
!payload.is_sender &&
|
|
209
|
+
tenant.enabled_features.includes('voice_transcription') &&
|
|
210
|
+
payload.message_id) {
|
|
211
|
+
try {
|
|
212
|
+
const provider = unipile_service_1.default.getProviderForTenant(tenant);
|
|
213
|
+
const { audio_base64 } = await unipile_service_1.default.downloadAudio(provider, payload.chat_id, payload.message_id);
|
|
214
|
+
const audioBuffer = Buffer.from(audio_base64, 'base64');
|
|
215
|
+
const { text: transcript, durationSeconds, detectedLanguage } = await elevenlabs_service_1.default.speechToText(audioBuffer);
|
|
216
|
+
if (transcript.trim()) {
|
|
217
|
+
payload.message = transcript;
|
|
218
|
+
payload.content_type = 'transcribedAudio';
|
|
219
|
+
logger_1.default.info('Voice message transcribed', {
|
|
220
|
+
chatId: payload.chat_id,
|
|
221
|
+
transcriptLength: transcript.length,
|
|
222
|
+
durationSeconds,
|
|
223
|
+
detectedLanguage,
|
|
224
|
+
preview: transcript.substring(0, 100),
|
|
225
|
+
});
|
|
226
|
+
// Log STT usage
|
|
227
|
+
stt_usage_model_1.default.create({
|
|
228
|
+
tenant_id: tenant._id,
|
|
229
|
+
chat_id: payload.chat_id,
|
|
230
|
+
message_id: payload.message_id,
|
|
231
|
+
duration_seconds: durationSeconds,
|
|
232
|
+
character_count: transcript.length,
|
|
233
|
+
language: detectedLanguage,
|
|
234
|
+
}).catch(() => { });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
logger_1.default.error('Voice message transcription failed, using label fallback', {
|
|
239
|
+
chatId: payload.chat_id,
|
|
240
|
+
error: err.message,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ─── Operator / forwarding reply detection ──────────────────
|
|
245
|
+
// Check for REF code from operator_phone or conversation_forwarding_phone
|
|
246
|
+
const operatorSettings = tenant.settings?.whatsapp_agent;
|
|
247
|
+
const knownOperatorPhones = [];
|
|
248
|
+
if (operatorSettings?.operator_workflow_enabled && operatorSettings.operator_phone) {
|
|
249
|
+
knownOperatorPhones.push(operatorSettings.operator_phone.replace(/[^0-9]/g, ''));
|
|
250
|
+
}
|
|
251
|
+
if (tenant.enabled_features.includes('conversation_forwarding') && operatorSettings?.conversation_forwarding_phone) {
|
|
252
|
+
const cfPhone = operatorSettings.conversation_forwarding_phone.replace(/[^0-9]/g, '');
|
|
253
|
+
if (!knownOperatorPhones.includes(cfPhone))
|
|
254
|
+
knownOperatorPhones.push(cfPhone);
|
|
255
|
+
}
|
|
256
|
+
if (knownOperatorPhones.length > 0) {
|
|
257
|
+
// Check for REF code in the message text OR in the quoted (replied-to) message
|
|
258
|
+
const refMatch = payload.message.trim().match(/\b([A-Z]-[A-Z0-9]{4})\b/)
|
|
259
|
+
|| (payload.quoted_text?.match(/\b([A-Z]-[A-Z0-9]{4})\b/) ?? null);
|
|
260
|
+
if (refMatch) {
|
|
261
|
+
// Scenario 1: incoming message from a known operator/forwarding phone
|
|
262
|
+
if (!payload.is_sender) {
|
|
263
|
+
const senderPhone = payload.attendees?.[0]?.attendee_specifics?.phone_number
|
|
264
|
+
|| payload.attendees?.[0]?.attendee_provider_id || '';
|
|
265
|
+
const normalizedSender = senderPhone.replace(/[^0-9]/g, '');
|
|
266
|
+
if (normalizedSender && knownOperatorPhones.includes(normalizedSender)) {
|
|
267
|
+
logger_1.default.info('Operator message detected (incoming)', { phone: senderPhone, message: payload.message.substring(0, 100) });
|
|
268
|
+
await handleOperatorReply(tenant, payload);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Scenario 2: self-sent message containing a REF code (operator uses same device)
|
|
273
|
+
if (payload.is_sender) {
|
|
274
|
+
// Verify this REF code exists and is pending or recently responded (for follow-up documents)
|
|
275
|
+
const pendingRequest = await operator_request_model_1.default.findOne({
|
|
276
|
+
ref_code: refMatch[1],
|
|
277
|
+
tenant_id: tenant._id,
|
|
278
|
+
status: { $in: ['pending', 'responded'] },
|
|
279
|
+
});
|
|
280
|
+
if (pendingRequest) {
|
|
281
|
+
logger_1.default.info('Operator message detected (self-sent)', { refCode: refMatch[1], message: payload.message.substring(0, 100) });
|
|
282
|
+
await handleOperatorReply(tenant, payload);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Determine contact info from the other party (attendees[0] is always the contact)
|
|
289
|
+
const contact = payload.attendees?.[0];
|
|
290
|
+
const contactName = contact?.attendee_name || 'Bilinmiyor';
|
|
291
|
+
const contactPhone = contact?.attendee_specifics?.phone_number
|
|
292
|
+
|| contact?.attendee_provider_id || '';
|
|
293
|
+
// Filter out group chats — only process valid phone numbers
|
|
294
|
+
if (!isValidPhoneNumber(contactPhone)) {
|
|
295
|
+
logger_1.default.debug('Ignoring non-phone chat (group?)', { chat_id: payload.chat_id, contactPhone });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
// Upsert contact profile
|
|
299
|
+
const profile = await whatsapp_contact_profile_model_1.default.findOneAndUpdate({ tenant_id: tenant._id, phone: contactPhone }, {
|
|
300
|
+
$set: { display_name: contactName, last_seen_at: new Date() },
|
|
301
|
+
$setOnInsert: { first_seen_at: new Date(), notes: '', tags: [], ai_summary: '', metadata: {} },
|
|
302
|
+
}, { upsert: true, new: true });
|
|
303
|
+
const gracePeriodSeconds = tenant.settings?.whatsapp_agent?.grace_period_seconds ?? 180;
|
|
304
|
+
const agentObjId = tenant.settings?.whatsapp_agent?.agent_id;
|
|
305
|
+
const agentDoc = agentObjId ? await agent_model_1.default.findById(agentObjId).lean() : null;
|
|
306
|
+
const agentId = (agentDoc && agentDoc.is_active) ? agentDoc.elevenlabs_agent_id : '';
|
|
307
|
+
const blacklist = tenant.settings?.whatsapp_agent?.blacklisted_numbers || [];
|
|
308
|
+
const isBlacklisted = blacklist.includes(contactPhone);
|
|
309
|
+
const skipAI = isBlacklisted || isSystemMessage || isLegacyMediaPlaceholder;
|
|
310
|
+
// Find or create WhatsAppChat
|
|
311
|
+
let chat = await whatsapp_chat_model_1.default.findOne({ unipile_chat_id: payload.chat_id });
|
|
312
|
+
// If not found by chat_id, try matching by phone (handles provider migration)
|
|
313
|
+
if (!chat && contactPhone) {
|
|
314
|
+
chat = await whatsapp_chat_model_1.default.findOne({ tenant_id: tenant._id, contact_phone: contactPhone, deleted_at: null });
|
|
315
|
+
if (chat) {
|
|
316
|
+
const oldChatId = chat.unipile_chat_id;
|
|
317
|
+
chat.unipile_chat_id = payload.chat_id;
|
|
318
|
+
chat.unipile_account_id = payload.account_id;
|
|
319
|
+
// Clear stale session from previous provider
|
|
320
|
+
if (chat.active_session_id) {
|
|
321
|
+
const staleSession = await whatsapp_session_model_1.default.findById(chat.active_session_id);
|
|
322
|
+
if (staleSession && staleSession.status !== 'resolved') {
|
|
323
|
+
staleSession.status = 'resolved';
|
|
324
|
+
staleSession.resolved_at = new Date();
|
|
325
|
+
await staleSession.save();
|
|
326
|
+
}
|
|
327
|
+
chat.active_session_id = null;
|
|
328
|
+
}
|
|
329
|
+
await chat.save();
|
|
330
|
+
logger_1.default.info('Relinked existing chat after provider migration', {
|
|
331
|
+
chatId: chat._id, oldChatId, newChatId: payload.chat_id,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// ─── New chat ──────────────────────────────────────────────
|
|
336
|
+
if (!chat) {
|
|
337
|
+
chat = await whatsapp_chat_model_1.default.create({
|
|
338
|
+
tenant_id: tenant._id,
|
|
339
|
+
unipile_chat_id: payload.chat_id,
|
|
340
|
+
unipile_account_id: payload.account_id,
|
|
341
|
+
contact_name: contactName,
|
|
342
|
+
contact_phone: contactPhone,
|
|
343
|
+
contact_profile_id: profile._id,
|
|
344
|
+
is_closed: false,
|
|
345
|
+
last_message_at: new Date(payload.timestamp),
|
|
346
|
+
last_message_preview: payload.message.substring(0, 100),
|
|
347
|
+
message_count: 0,
|
|
348
|
+
});
|
|
349
|
+
// Self-sent first message — our user initiated the chat
|
|
350
|
+
if (payload.is_sender) {
|
|
351
|
+
const session = await whatsapp_session_model_1.default.create({
|
|
352
|
+
chat_id: chat._id,
|
|
353
|
+
tenant_id: tenant._id,
|
|
354
|
+
status: 'active',
|
|
355
|
+
started_at: new Date(),
|
|
356
|
+
taken_over_at: new Date(),
|
|
357
|
+
});
|
|
358
|
+
chat.active_session_id = session._id;
|
|
359
|
+
await chat.save();
|
|
360
|
+
await whatsapp_message_model_1.default.create({
|
|
361
|
+
chat_id: chat._id,
|
|
362
|
+
session_id: session._id,
|
|
363
|
+
tenant_id: tenant._id,
|
|
364
|
+
unipile_message_id: payload.message_id || null,
|
|
365
|
+
sender: 'human',
|
|
366
|
+
sender_name: 'WhatsApp',
|
|
367
|
+
text: payload.message,
|
|
368
|
+
sent_via_unipile: false,
|
|
369
|
+
});
|
|
370
|
+
chat.message_count = 1;
|
|
371
|
+
session.message_count = 1;
|
|
372
|
+
await Promise.all([chat.save(), session.save()]);
|
|
373
|
+
logger_1.default.info('Created new chat from self-sent message (human mode)', {
|
|
374
|
+
chatId: chat._id,
|
|
375
|
+
sessionId: session._id,
|
|
376
|
+
contact: contactName,
|
|
377
|
+
});
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (skipAI) {
|
|
381
|
+
await whatsapp_message_model_1.default.create({
|
|
382
|
+
chat_id: chat._id,
|
|
383
|
+
session_id: null,
|
|
384
|
+
tenant_id: tenant._id,
|
|
385
|
+
unipile_message_id: payload.message_id || null,
|
|
386
|
+
sender: 'contact',
|
|
387
|
+
sender_name: contactName,
|
|
388
|
+
text: payload.message,
|
|
389
|
+
media_type: payload.content_type || null,
|
|
390
|
+
ad_context: payload.ad_context || null,
|
|
391
|
+
sent_via_unipile: false,
|
|
392
|
+
});
|
|
393
|
+
chat.message_count = 1;
|
|
394
|
+
await chat.save();
|
|
395
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
396
|
+
logger_1.default.info('Created new chat — AI skipped', {
|
|
397
|
+
chatId: chat._id,
|
|
398
|
+
contact: contactName,
|
|
399
|
+
reason: isSystemMessage ? 'system_message' : 'blacklisted',
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const session = await createSession(chat, tenant._id.toString(), gracePeriodSeconds);
|
|
404
|
+
await whatsapp_message_model_1.default.create({
|
|
405
|
+
chat_id: chat._id,
|
|
406
|
+
session_id: session._id,
|
|
407
|
+
tenant_id: tenant._id,
|
|
408
|
+
unipile_message_id: payload.message_id || null,
|
|
409
|
+
sender: 'contact',
|
|
410
|
+
sender_name: contactName,
|
|
411
|
+
text: payload.message,
|
|
412
|
+
media_type: payload.content_type || null,
|
|
413
|
+
ad_context: payload.ad_context || null,
|
|
414
|
+
sent_via_unipile: false,
|
|
415
|
+
});
|
|
416
|
+
chat.message_count = 1;
|
|
417
|
+
await chat.save();
|
|
418
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
419
|
+
session.message_count = 1;
|
|
420
|
+
await session.save();
|
|
421
|
+
logger_1.default.info('Created new WhatsApp chat with session', {
|
|
422
|
+
chatId: chat._id,
|
|
423
|
+
sessionId: session._id,
|
|
424
|
+
contact: contactName,
|
|
425
|
+
gracePeriodSeconds,
|
|
426
|
+
});
|
|
427
|
+
// Send fixed first message if enabled
|
|
428
|
+
const fixedMsg = tenant.settings?.whatsapp_agent?.fixed_first_message;
|
|
429
|
+
if (tenant.settings?.whatsapp_agent?.fixed_first_message_enabled && fixedMsg) {
|
|
430
|
+
const provider = unipile_service_1.default.getProviderForTenant(tenant);
|
|
431
|
+
const chatRef = chat;
|
|
432
|
+
unipile_service_1.default.sendMessage(provider, chatRef.unipile_chat_id, fixedMsg).then(async (result) => {
|
|
433
|
+
await whatsapp_message_model_1.default.create({
|
|
434
|
+
chat_id: chatRef._id,
|
|
435
|
+
session_id: session._id,
|
|
436
|
+
tenant_id: tenant._id,
|
|
437
|
+
unipile_message_id: result.message_id || null,
|
|
438
|
+
sender: 'system',
|
|
439
|
+
sender_name: 'AI',
|
|
440
|
+
text: fixedMsg,
|
|
441
|
+
sent_via_unipile: true,
|
|
442
|
+
});
|
|
443
|
+
chatRef.message_count += 1;
|
|
444
|
+
await chatRef.save();
|
|
445
|
+
}).catch(err => {
|
|
446
|
+
logger_1.default.error('Failed to send fixed first message', { chatId: chatRef._id, error: err.message });
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
await whatsapp_agent_service_1.default.startSession({
|
|
450
|
+
session,
|
|
451
|
+
gracePeriodSeconds,
|
|
452
|
+
agentId,
|
|
453
|
+
tenantId: tenant._id.toString(),
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Backfill profile link on existing chats
|
|
458
|
+
if (!chat.contact_profile_id) {
|
|
459
|
+
chat.contact_profile_id = profile._id;
|
|
460
|
+
await chat.save();
|
|
461
|
+
}
|
|
462
|
+
// Update contact name if changed
|
|
463
|
+
if (chat.contact_name !== contactName && contactName !== 'Bilinmiyor') {
|
|
464
|
+
chat.contact_name = contactName;
|
|
465
|
+
await chat.save();
|
|
466
|
+
}
|
|
467
|
+
// ─── Self-sent messages (is_sender=true) ───────────────────
|
|
468
|
+
if (payload.is_sender) {
|
|
469
|
+
// Look up active session
|
|
470
|
+
let session = chat.active_session_id
|
|
471
|
+
? await whatsapp_session_model_1.default.findById(chat.active_session_id)
|
|
472
|
+
: null;
|
|
473
|
+
if (session && session.status === 'resolved')
|
|
474
|
+
session = null;
|
|
475
|
+
// If session is waiting, human responded — cancel timer & resolve
|
|
476
|
+
let resolvedSessionId = null;
|
|
477
|
+
if (session && session.status === 'waiting') {
|
|
478
|
+
resolvedSessionId = session._id;
|
|
479
|
+
await whatsapp_agent_service_1.default.resolveSessionAsHuman(session);
|
|
480
|
+
session = null; // session is now resolved
|
|
481
|
+
}
|
|
482
|
+
// Check if we already stored this message (sent via our API)
|
|
483
|
+
// Primary dedup: by unipile_message_id (if Unipile returned one when we sent)
|
|
484
|
+
// Fallback dedup: by text + sent_via_unipile + recent timestamp (covers cases
|
|
485
|
+
// where Unipile didn't return a message_id in the send response)
|
|
486
|
+
if (payload.message_id) {
|
|
487
|
+
const exists = await whatsapp_message_model_1.default.findOne({ unipile_message_id: payload.message_id });
|
|
488
|
+
if (exists) {
|
|
489
|
+
logger_1.default.debug('Self-sent message already in DB (by message_id), skipping', { message_id: payload.message_id });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const recentCutoff = new Date(Date.now() - 60_000); // 1 minute window
|
|
494
|
+
const existsByContent = await whatsapp_message_model_1.default.findOne({
|
|
495
|
+
chat_id: chat._id,
|
|
496
|
+
sent_via_unipile: true,
|
|
497
|
+
text: payload.message,
|
|
498
|
+
createdAt: { $gte: recentCutoff },
|
|
499
|
+
});
|
|
500
|
+
if (existsByContent) {
|
|
501
|
+
// Update the stored message with the real unipile_message_id for future dedup
|
|
502
|
+
if (payload.message_id && !existsByContent.unipile_message_id) {
|
|
503
|
+
await whatsapp_message_model_1.default.updateOne({ _id: existsByContent._id }, { unipile_message_id: payload.message_id });
|
|
504
|
+
}
|
|
505
|
+
logger_1.default.debug('Self-sent message already in DB (by content), skipping', { message_id: payload.message_id });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Store the message — link to the session it resolved (if any) or the current active session
|
|
509
|
+
await whatsapp_message_model_1.default.create({
|
|
510
|
+
chat_id: chat._id,
|
|
511
|
+
session_id: resolvedSessionId || session?._id || null,
|
|
512
|
+
tenant_id: tenant._id,
|
|
513
|
+
unipile_message_id: payload.message_id,
|
|
514
|
+
sender: 'human',
|
|
515
|
+
sender_name: 'WhatsApp',
|
|
516
|
+
text: payload.message,
|
|
517
|
+
sent_via_unipile: false,
|
|
518
|
+
});
|
|
519
|
+
chat.last_message_at = new Date(payload.timestamp);
|
|
520
|
+
chat.last_message_preview = payload.message.substring(0, 100);
|
|
521
|
+
chat.message_count += 1;
|
|
522
|
+
await chat.save();
|
|
523
|
+
// If session is active with no takeover (AI mode), switch to human
|
|
524
|
+
if (session && session.status === 'active' && !session.taken_over_by && !session.taken_over_at) {
|
|
525
|
+
session.taken_over_at = new Date();
|
|
526
|
+
await session.save();
|
|
527
|
+
whatsapp_agent_service_1.default.closeConnection(chat.unipile_chat_id);
|
|
528
|
+
logger_1.default.info('Auto-switched to human mode (manual WhatsApp reply detected)', {
|
|
529
|
+
chatId: chat._id,
|
|
530
|
+
sessionId: session._id,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
logger_1.default.info('Stored self-sent message from WhatsApp', { chatId: chat._id });
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// ─── Incoming contact message (is_sender=false) ────────────
|
|
537
|
+
// Blacklisted numbers & system messages: store but never trigger session or AI
|
|
538
|
+
if (skipAI) {
|
|
539
|
+
await whatsapp_message_model_1.default.create({
|
|
540
|
+
chat_id: chat._id,
|
|
541
|
+
session_id: null,
|
|
542
|
+
tenant_id: tenant._id,
|
|
543
|
+
unipile_message_id: payload.message_id || null,
|
|
544
|
+
sender: 'contact',
|
|
545
|
+
sender_name: contactName,
|
|
546
|
+
text: payload.message,
|
|
547
|
+
media_type: payload.content_type || null,
|
|
548
|
+
ad_context: payload.ad_context || null,
|
|
549
|
+
sent_via_unipile: false,
|
|
550
|
+
});
|
|
551
|
+
chat.last_message_at = new Date(payload.timestamp);
|
|
552
|
+
chat.last_message_preview = payload.message.substring(0, 100);
|
|
553
|
+
chat.message_count += 1;
|
|
554
|
+
if (chat.is_closed)
|
|
555
|
+
chat.is_closed = false;
|
|
556
|
+
if (chat.deleted_at)
|
|
557
|
+
chat.deleted_at = null;
|
|
558
|
+
await chat.save();
|
|
559
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
560
|
+
logger_1.default.info('Message stored — AI skipped', {
|
|
561
|
+
chatId: chat._id,
|
|
562
|
+
reason: isSystemMessage ? 'system_message' : 'blacklisted',
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
// Look up active session
|
|
567
|
+
let session = chat.active_session_id
|
|
568
|
+
? await whatsapp_session_model_1.default.findById(chat.active_session_id)
|
|
569
|
+
: null;
|
|
570
|
+
if (session && session.status === 'resolved') {
|
|
571
|
+
session = null;
|
|
572
|
+
chat.active_session_id = null;
|
|
573
|
+
await chat.save();
|
|
574
|
+
}
|
|
575
|
+
// If session is waiting, just store the message (timer already running)
|
|
576
|
+
if (session && session.status === 'waiting') {
|
|
577
|
+
await whatsapp_message_model_1.default.create({
|
|
578
|
+
chat_id: chat._id,
|
|
579
|
+
session_id: session._id,
|
|
580
|
+
tenant_id: tenant._id,
|
|
581
|
+
unipile_message_id: payload.message_id || null,
|
|
582
|
+
sender: 'contact',
|
|
583
|
+
sender_name: contactName,
|
|
584
|
+
text: payload.message,
|
|
585
|
+
media_type: payload.content_type || null,
|
|
586
|
+
ad_context: payload.ad_context || null,
|
|
587
|
+
sent_via_unipile: false,
|
|
588
|
+
});
|
|
589
|
+
chat.last_message_at = new Date(payload.timestamp);
|
|
590
|
+
chat.last_message_preview = payload.message.substring(0, 100);
|
|
591
|
+
chat.message_count += 1;
|
|
592
|
+
await chat.save();
|
|
593
|
+
session.message_count += 1;
|
|
594
|
+
await session.save();
|
|
595
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
596
|
+
logger_1.default.info('Message stored during grace period', { chatId: chat._id, sessionId: session._id });
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
// If session is active, forward to agent service
|
|
600
|
+
if (session && session.status === 'active') {
|
|
601
|
+
await whatsapp_agent_service_1.default.handleIncomingMessage({
|
|
602
|
+
tenantId: tenant._id.toString(),
|
|
603
|
+
agentId,
|
|
604
|
+
chat,
|
|
605
|
+
session,
|
|
606
|
+
messageText: payload.message,
|
|
607
|
+
senderName: contactName,
|
|
608
|
+
unipileMessageId: payload.message_id,
|
|
609
|
+
contentType: payload.content_type,
|
|
610
|
+
});
|
|
611
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// No active session → create new one (closed or idle chat)
|
|
615
|
+
session = await createSession(chat, tenant._id.toString(), gracePeriodSeconds);
|
|
616
|
+
await whatsapp_message_model_1.default.create({
|
|
617
|
+
chat_id: chat._id,
|
|
618
|
+
session_id: session._id,
|
|
619
|
+
tenant_id: tenant._id,
|
|
620
|
+
unipile_message_id: payload.message_id || null,
|
|
621
|
+
sender: 'contact',
|
|
622
|
+
sender_name: contactName,
|
|
623
|
+
text: payload.message,
|
|
624
|
+
media_type: payload.content_type || null,
|
|
625
|
+
ad_context: payload.ad_context || null,
|
|
626
|
+
sent_via_unipile: false,
|
|
627
|
+
});
|
|
628
|
+
chat.last_message_at = new Date(payload.timestamp);
|
|
629
|
+
chat.last_message_preview = payload.message.substring(0, 100);
|
|
630
|
+
chat.message_count += 1;
|
|
631
|
+
await chat.save();
|
|
632
|
+
await whatsapp_contact_profile_model_1.default.updateOne({ _id: profile._id }, { $inc: { total_messages: 1 } });
|
|
633
|
+
session.message_count = 1;
|
|
634
|
+
await session.save();
|
|
635
|
+
await whatsapp_agent_service_1.default.startSession({
|
|
636
|
+
session,
|
|
637
|
+
gracePeriodSeconds,
|
|
638
|
+
agentId,
|
|
639
|
+
tenantId: tenant._id.toString(),
|
|
640
|
+
});
|
|
641
|
+
logger_1.default.info('Started new session for chat', {
|
|
642
|
+
chatId: chat._id,
|
|
643
|
+
sessionId: session._id,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
// ─── Operator Workflow Helpers ──────────────────────────────
|
|
647
|
+
/**
|
|
648
|
+
* Handle a reply from the operator (doctor) that contains a REF code.
|
|
649
|
+
* Matches the reply to the correct patient session and injects the response.
|
|
650
|
+
*/
|
|
651
|
+
async function handleOperatorReply(tenant, payload) {
|
|
652
|
+
const messageText = payload.message.trim();
|
|
653
|
+
const provider = unipile_service_1.default.getProviderForTenant(tenant);
|
|
654
|
+
// Extract REF code from the message text or quoted (replied-to) message
|
|
655
|
+
const refMatch = messageText.match(/\b([A-Z]-[A-Z0-9]{4})\b/)
|
|
656
|
+
|| (payload.quoted_text?.match(/\b([A-Z]-[A-Z0-9]{4})\b/) ?? null);
|
|
657
|
+
if (!refMatch) {
|
|
658
|
+
logger_1.default.info('Operator message without REF code — storing as normal', {
|
|
659
|
+
message: messageText.substring(0, 100),
|
|
660
|
+
});
|
|
661
|
+
// No REF code — this is just a normal message from the doctor, ignore it
|
|
662
|
+
// (could also store it as a chat message, but the doctor is messaging the clinic number)
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const refCode = refMatch[1];
|
|
666
|
+
// Match pending OR recently responded (estetisyen may send document after text reply)
|
|
667
|
+
const request = await operator_request_model_1.default.findOne({
|
|
668
|
+
ref_code: refCode,
|
|
669
|
+
tenant_id: tenant._id,
|
|
670
|
+
status: { $in: ['pending', 'responded'] },
|
|
671
|
+
}).sort({ createdAt: -1 });
|
|
672
|
+
if (!request) {
|
|
673
|
+
logger_1.default.warn('No matching OperatorRequest for REF code', { refCode, tenantId: tenant._id });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// Check if operator sent media (document/image) along with the reply
|
|
677
|
+
const hasMedia = !!payload.content_type && payload.content_type !== 'conversation' && payload.content_type !== 'extendedTextMessage';
|
|
678
|
+
logger_1.default.info('Operator reply media check', {
|
|
679
|
+
refCode,
|
|
680
|
+
content_type: payload.content_type,
|
|
681
|
+
message_id: payload.message_id,
|
|
682
|
+
chat_id: payload.chat_id,
|
|
683
|
+
hasMedia,
|
|
684
|
+
is_sender: payload.is_sender,
|
|
685
|
+
});
|
|
686
|
+
const alreadyResponded = request.status === 'responded';
|
|
687
|
+
// Update the operator request
|
|
688
|
+
request.status = 'responded';
|
|
689
|
+
if (!alreadyResponded) {
|
|
690
|
+
request.operator_response = messageText;
|
|
691
|
+
request.responded_at = new Date();
|
|
692
|
+
}
|
|
693
|
+
if (hasMedia) {
|
|
694
|
+
request.response_media_type = payload.content_type;
|
|
695
|
+
request.response_message_id = payload.message_id;
|
|
696
|
+
}
|
|
697
|
+
await request.save();
|
|
698
|
+
// If operator sent media, forward it directly to the patient
|
|
699
|
+
let mediaForwarded = false;
|
|
700
|
+
if (hasMedia) {
|
|
701
|
+
const chat = await whatsapp_chat_model_1.default.findById(request.chat_id).lean();
|
|
702
|
+
if (chat) {
|
|
703
|
+
try {
|
|
704
|
+
// Small delay to ensure Baileys worker has cached the media
|
|
705
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
706
|
+
await unipile_service_1.default.forwardMedia(provider, payload.chat_id, payload.message_id, chat.contact_phone, '');
|
|
707
|
+
mediaForwarded = true;
|
|
708
|
+
logger_1.default.info('Forwarded operator document to patient', {
|
|
709
|
+
refCode, mediaType: payload.content_type, patientPhone: chat.contact_phone,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
logger_1.default.error('Failed to forward operator document to patient', {
|
|
714
|
+
refCode, error: err.message,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (!alreadyResponded) {
|
|
720
|
+
// First reply: inject the operator's text response into the patient's AI session
|
|
721
|
+
await whatsapp_agent_service_1.default.injectOperatorResponse({
|
|
722
|
+
tenantId: tenant._id.toString(),
|
|
723
|
+
sessionId: request.session_id.toString(),
|
|
724
|
+
chatId: request.chat_id.toString(),
|
|
725
|
+
operatorResponse: messageText,
|
|
726
|
+
refCode,
|
|
727
|
+
hasDocument: mediaForwarded,
|
|
728
|
+
});
|
|
729
|
+
// Send confirmation to the estetisyen
|
|
730
|
+
try {
|
|
731
|
+
const accountId = tenant.settings.whatsapp_agent.unipile_account_id;
|
|
732
|
+
if (accountId) {
|
|
733
|
+
const operatorPhone = tenant.settings.whatsapp_agent.operator_phone;
|
|
734
|
+
const operatorJid = operatorPhone.replace(/[^0-9]/g, '') + '@s.whatsapp.net';
|
|
735
|
+
const operatorChatId = `${accountId}:${operatorJid}`;
|
|
736
|
+
const confirmMsg = mediaForwarded
|
|
737
|
+
? `✓ Yanıt ve belge hastaya iletildi. Ref: ${refCode}`
|
|
738
|
+
: `✓ Yanıt alındı. Ref: ${refCode} — Hastaya iletilecek.`;
|
|
739
|
+
await unipile_service_1.default.sendMessage(provider, operatorChatId, confirmMsg);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
logger_1.default.error('Failed to send confirmation to operator', { refCode, error: err.message });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
else if (mediaForwarded) {
|
|
747
|
+
// Follow-up: estetisyen sent document after initial text reply
|
|
748
|
+
// Notify AI about the document so it can tell the patient
|
|
749
|
+
await whatsapp_agent_service_1.default.injectOperatorResponse({
|
|
750
|
+
tenantId: tenant._id.toString(),
|
|
751
|
+
sessionId: request.session_id.toString(),
|
|
752
|
+
chatId: request.chat_id.toString(),
|
|
753
|
+
operatorResponse: '',
|
|
754
|
+
refCode,
|
|
755
|
+
hasDocument: true,
|
|
756
|
+
});
|
|
757
|
+
// Send confirmation
|
|
758
|
+
try {
|
|
759
|
+
const accountId = tenant.settings.whatsapp_agent.unipile_account_id;
|
|
760
|
+
if (accountId) {
|
|
761
|
+
const operatorPhone = tenant.settings.whatsapp_agent.operator_phone;
|
|
762
|
+
const operatorJid = operatorPhone.replace(/[^0-9]/g, '') + '@s.whatsapp.net';
|
|
763
|
+
const operatorChatId = `${accountId}:${operatorJid}`;
|
|
764
|
+
await unipile_service_1.default.sendMessage(provider, operatorChatId, `✓ Belge hastaya iletildi. Ref: ${refCode}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
logger_1.default.error('Failed to send confirmation to operator', { refCode, error: err.message });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
logger_1.default.info('Operator reply processed', {
|
|
772
|
+
refCode,
|
|
773
|
+
sessionId: request.session_id,
|
|
774
|
+
hasMedia,
|
|
775
|
+
mediaForwarded,
|
|
776
|
+
alreadyResponded,
|
|
777
|
+
response: messageText.substring(0, 100),
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
//# sourceMappingURL=unipile.routes.js.map
|