clinic-connect-widget 1.0.2 → 1.0.4
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/dist/clinic-widget.es.js +379 -213
- package/dist/clinic-widget.umd.js +1 -1
- package/package.json +1 -1
package/dist/clinic-widget.es.js
CHANGED
|
@@ -104,7 +104,7 @@ const CONFIG = {
|
|
|
104
104
|
// LiveKit Server URL
|
|
105
105
|
LIVEKIT_URL: "wss://livekit.longvan.vn"
|
|
106
106
|
};
|
|
107
|
-
const styles = ':host{--td-primary: #0e65f1;--td-primary-hover: #0b50c0;--td-bg-light: #ffffff;--td-bg-dark: #1a202c;--td-text-main: #0d131c;--td-text-sub: #49699c;--td-border: #e7ecf4;--td-success: #22c55e;--td-danger: #ef4444;--font-family: "Inter", system-ui, -apple-system, sans-serif;--z-index: 9999;font-family:var(--font-family);color:var(--td-text-main)}*{box-sizing:border-box;margin:0;padding:0}.hidden{display:none!important}.material-symbols-outlined{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}.td-widget-fab{position:fixed;bottom:24px;right:24px;display:flex;align-items:center;gap:12px;z-index:var(--z-index);cursor:pointer;font-family:var(--font-family)}.td-fab-card{background:var(--td-bg-light);border:1px solid var(--td-border);border-radius:12px;box-shadow:0 8px 30px #0000001f;display:flex;align-items:center;padding:6px 16px 6px 6px;transition:transform .2s ease,box-shadow .2s ease;max-width:320px;position:relative;overflow:hidden}.td-fab-card:hover{transform:translateY(-4px);box-shadow:0 20px 40px #00000026}.td-avatar-container{width:64px;height:64px;border-radius:8px;overflow:hidden;position:relative;flex-shrink:0}.td-avatar{width:100%;height:100%;background-size:cover;background-position:center}.td-status-dot{position:absolute;bottom:4px;right:4px;width:14px;height:14px;background:var(--td-success);border:2px solid #fff;border-radius:50%}.td-info{margin-left:12px;display:flex;flex-direction:column}.td-brand{display:flex;align-items:center;gap:4px;font-size:10px;font-weight:700;text-transform:uppercase;color:var(--td-primary);letter-spacing:.05em}.td-cta{font-size:15px;font-weight:700;color:var(--td-text-main);margin-top:2px}.td-sub{font-size:12px;color:var(--td-text-sub);margin-top:2px}.td-close-fab{position:absolute;top:4px;right:4px;opacity:0;cursor:pointer;background:none;border:none;color:#94a3b8;transition:opacity .2s;padding:4px}.td-fab-card:hover .td-close-fab{opacity:1}.td-widget-expanded{position:fixed;bottom:100px;right:24px;width:380px;max-height:80vh;background:var(--td-bg-light);border-radius:12px;box-shadow:0 25px 50px -12px #00000040;border:1px solid var(--td-border);display:flex;flex-direction:column;overflow:hidden;z-index:var(--z-index);animation:slideUp .3s cubic-bezier(.16,1,.3,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.td-header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid var(--td-border)}.td-header-title{display:flex;align-items:center;gap:12px;font-weight:700;font-size:18px}.td-icon-box{width:32px;height:32px;background:#0e65f11a;color:var(--td-primary);border-radius:8px;display:flex;align-items:center;justify-content:center}.td-close-btn{background:transparent;border:none;cursor:pointer;color:#64748b;padding:4px;border-radius:50%;display:flex;align-items:center;justify-content:center}.td-close-btn:hover{background:#f1f5f9}.td-content{flex:1;overflow-y:auto;padding:0}.td-doctor-profile{display:flex;flex-direction:column;align-items:center;padding:32px 24px 24px}.td-online-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;background:#f0fdf4;border:1px solid #dcfce7;border-radius:999px}.td-pulse{width:8px;height:8px;background:var(--td-success);border-radius:50%}.td-badge-text{font-size:12px;font-weight:600;color:#15803d;text-transform:uppercase}.td-trust-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:0 24px 24px}.td-trust-item{background:#f8f9fc;border:1px solid var(--td-border);border-radius:12px;padding:12px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}.td-trust-title{font-size:12px;font-weight:700}.td-actions{padding:0 24px 24px;display:flex;flex-direction:column;gap:12px}.td-btn{width:100%;height:48px;border-radius:8px;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;border:none}.td-btn-primary{background:var(--td-primary);color:#fff;box-shadow:0 10px 15px -3px #0e65f133}.td-btn-primary:hover{background:var(--td-primary-hover)}.td-btn-outline{background:transparent;border:1px solid var(--td-border);color:var(--td-text-main);font-size:14px}.td-btn-outline:hover{background:#f8f9fc}.td-footer{padding:12px;text-align:center;background:#f8f9fc;border-top:1px solid var(--td-border);font-size:12px;color:var(--td-text-sub)}.td-consultation-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90vw;height:90vh;max-width:1200px;max-height:800px;background:var(--td-bg-light);border-radius:16px;box-shadow:0 25px 50px -12px #00000080;border:1px solid var(--td-border);display:flex;overflow:hidden;z-index:var(--z-index);animation:fadeIn .3s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translate(-50%,-48%)}to{opacity:1;transform:translate(-50%,-50%)}}.td-video-panel{flex:1;background:#000;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden}.td-main-video{width:100%;height:100%;background-size:cover;background-position:center}.td-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to bottom,rgba(0,0,0,.3),transparent,rgba(0,0,0,.6))}.td-video-header{position:absolute;top:16px;left:16px;right:16px;display:flex;justify-content:space-between;z-index:10}.td-badge-glass{background:#0006;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);padding:6px 12px;border-radius:20px;display:flex;align-items:center;gap:8px;color:#fff;font-size:12px;font-weight:500}.td-pip{position:absolute;top:24px;right:24px;width:160px;aspect-ratio:16/9;background:#333;border-radius:8px;border:2px solid rgba(255,255,255,.2);overflow:hidden;z-index:20;background-size:cover;background-position:center;box-shadow:0 4px 6px #0000004d}.td-pip-label{position:absolute;bottom:4px;left:4px;background:#0009;color:#fff;font-size:10px;padding:2px 6px;border-radius:4px}.td-controls{position:absolute;bottom:32px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:16px;z-index:20}.td-ctrl-btn{width:48px;height:48px;border-radius:50%;background:#fff3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.td-ctrl-btn:hover{background:#fff;color:var(--td-primary)}.td-ctrl-end{height:48px;padding:0 24px;border-radius:24px;background:var(--td-danger);color:#fff;display:flex;align-items:center;gap:8px;font-weight:600;border:none;cursor:pointer}.td-ctrl-end:hover{background:#dc2626}.td-chat-panel{width:400px;background:var(--td-bg-light);border-left:1px solid var(--td-border);display:flex;flex-direction:column}.td-chat-header{padding:16px;border-bottom:1px solid var(--td-border)}.td-chat-doc-info{display:flex;gap:12px;align-items:center;margin-bottom:12px}.td-chat-avatar{width:48px;height:48px;border-radius:50%;background-size:cover;background-position:center}.td-chat-timer{background:#f8f9fc;padding:8px 12px;border-radius:8px;display:flex;justify-content:space-between;align-items:center}.td-timer-val{font-family:monospace;font-weight:700;font-size:16px}.td-chat-messages{flex:1;overflow-y:auto;padding:16px;background:#f8f9fc;display:flex;flex-direction:column;gap:16px}.td-msg-system{text-align:center;font-size:12px;color:var(--td-text-sub);background:#e2e8f0;padding:4px 12px;border-radius:12px;align-self:center;max-width:90%}.td-msg-row{display:flex;gap:8px}.td-msg-row.own{flex-direction:row-reverse}.td-msg-bubble{padding:10px 14px;border-radius:12px;font-size:14px;line-height:1.5;max-width:85%}.td-msg-row.doctor .td-msg-bubble{background:#fff;border:1px solid var(--td-border);border-top-left-radius:2px}.td-msg-row.own .td-msg-bubble{background:#e0f2fe;color:var(--td-text-main);border:1px solid #bae6fd;border-top-right-radius:2px}.td-msg-time{font-size:10px;color:var(--td-text-sub);margin-top:2px}.td-chat-input-area{padding:16px;border-top:1px solid var(--td-border);background:var(--td-bg-light)}.td-input-wrapper{display:flex;gap:8px;align-items:flex-end}.td-chat-input{flex:1;border:1px solid var(--td-border);border-radius:8px;padding:10px;font-family:inherit;font-size:14px;resize:none;height:48px}.td-send-btn{width:48px;height:48px;background:var(--td-primary);color:#fff;border:none;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center}.td-send-btn:hover{background:var(--td-primary-hover)}@media (max-width: 768px){.td-consultation-modal{width:100vw;height:100vh;border-radius:0;flex-direction:column}.td-video-panel{height:40vh}.td-chat-panel{width:100%;flex:1}}.td-form-container{display:flex;flex-direction:column;height:100%;padding:0 24px}.td-form-page-header{padding:24px 0 16px}.td-form-title{font-size:24px;font-weight:700;color:var(--td-text-main);margin-bottom:8px}.td-form-desc{font-size:14px;color:var(--td-text-sub);line-height:normal}.td-form{display:flex;flex-direction:column;gap:20px;padding-bottom:24px}.td-input-group{display:flex;flex-direction:column;gap:8px}.td-label{font-size:14px;font-weight:500;color:var(--td-text-main)}.td-input{width:100%;height:48px;padding:0 16px;border-radius:8px;border:1px solid var(--td-border);background:#f8f9fc;font-family:inherit;font-size:16px;color:var(--td-text-main);transition:all .2s}.td-input:focus{outline:none;border-color:var(--td-primary);box-shadow:0 0 0 2px #0e65f133}.td-textarea{width:100%;min-height:100px;padding:16px;border-radius:8px;border:1px solid var(--td-border);background:#f8f9fc;font-family:inherit;font-size:16px;color:var(--td-text-main);resize:none}.td-textarea:focus{outline:none;border-color:var(--td-primary)}.td-chips-group{display:flex;flex-wrap:wrap;gap:8px}.td-chip{padding:8px 16px;border-radius:99px;font-size:14px;font-weight:500;cursor:pointer;border:1px solid var(--td-border);background:#fff;color:var(--td-text-sub);transition:all .2s}.td-chip:hover{border-color:var(--td-primary);color:var(--td-primary);background:#0e65f10d}.td-chip.active{border-color:var(--td-primary);background:#0e65f11a;color:var(--td-primary);display:flex;align-items:center;gap:6px}.td-secure-note{display:flex;align-items:center;justify-content:center;gap:6px;font-size:12px;color:var(--td-text-sub);opacity:.8;margin-top:8px}.td-summary-container{display:flex;flex-direction:column;height:100%}.td-success-header{display:flex;flex-direction:column;align-items:center;padding:32px 24px;text-align:center}.td-success-icon{width:80px;height:80px;background:#0e65f11a;color:var(--td-primary);border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:16px}.td-summary-title{font-size:24px;font-weight:700;margin-bottom:8px}.td-summary-desc{font-size:14px;color:var(--td-text-sub);max-width:360px;margin:0 auto}.td-summary-card{margin:0 24px;padding:16px;border-radius:12px;border:1px solid var(--td-border);background:#f8f9fc;display:flex;align-items:center;gap:16px}.td-summary-details{padding:8px 24px}.td-detail-row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px dashed var(--td-border);font-size:14px}.td-note-box{margin:16px 24px;padding:16px;background:#0e65f10d;border:1px solid rgba(14,101,241,.1);border-radius:8px}.td-note-title{font-size:14px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px}.td-grid-actions{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}';
|
|
107
|
+
const styles = ':host{--td-primary: #0e65f1;--td-primary-hover: #0b50c0;--td-bg-light: #ffffff;--td-bg-dark: #1a202c;--td-text-main: #0d131c;--td-text-sub: #49699c;--td-border: #e7ecf4;--td-success: #22c55e;--td-danger: #ef4444;--font-family: "Inter", system-ui, -apple-system, sans-serif;--z-index: 9999;font-family:var(--font-family);color:var(--td-text-main)}*{box-sizing:border-box;margin:0;padding:0}.hidden{display:none!important}.material-symbols-outlined{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}.td-widget-fab{position:fixed;bottom:24px;right:24px;display:flex;align-items:center;gap:12px;z-index:var(--z-index);cursor:pointer;font-family:var(--font-family)}.td-fab-card{background:var(--td-bg-light);border:1px solid var(--td-border);border-radius:12px;box-shadow:0 8px 30px #0000001f;display:flex;align-items:center;padding:6px 16px 6px 6px;transition:transform .2s ease,box-shadow .2s ease;max-width:320px;position:relative;overflow:hidden}.td-fab-card:hover{transform:translateY(-4px);box-shadow:0 20px 40px #00000026}.td-avatar-container{width:64px;height:64px;border-radius:8px;overflow:hidden;position:relative;flex-shrink:0}.td-avatar{width:100%;height:100%;background-size:cover;background-position:center}.td-status-dot{position:absolute;bottom:4px;right:4px;width:14px;height:14px;background:var(--td-success);border:2px solid #fff;border-radius:50%}.td-info{margin-left:12px;display:flex;flex-direction:column}.td-brand{display:flex;align-items:center;gap:4px;font-size:10px;font-weight:700;text-transform:uppercase;color:var(--td-primary);letter-spacing:.05em}.td-cta{font-size:15px;font-weight:700;color:var(--td-text-main);margin-top:2px}.td-sub{font-size:12px;color:var(--td-text-sub);margin-top:2px}.td-close-fab{position:absolute;top:4px;right:4px;opacity:0;cursor:pointer;background:none;border:none;color:#94a3b8;transition:opacity .2s;padding:4px}.td-fab-card:hover .td-close-fab{opacity:1}.td-widget-expanded{position:fixed;bottom:100px;right:24px;width:380px;max-height:80vh;background:var(--td-bg-light);border-radius:12px;box-shadow:0 25px 50px -12px #00000040;border:1px solid var(--td-border);display:flex;flex-direction:column;overflow:hidden;z-index:var(--z-index);animation:slideUp .3s cubic-bezier(.16,1,.3,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.td-header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid var(--td-border)}.td-header-title{display:flex;align-items:center;gap:12px;font-weight:700;font-size:18px}.td-icon-box{width:32px;height:32px;background:#0e65f11a;color:var(--td-primary);border-radius:8px;display:flex;align-items:center;justify-content:center}.td-close-btn{background:transparent;border:none;cursor:pointer;color:#64748b;padding:4px;border-radius:50%;display:flex;align-items:center;justify-content:center}.td-close-btn:hover{background:#f1f5f9}.td-content{flex:1;overflow-y:auto;padding:0}.td-doctor-profile{display:flex;flex-direction:column;align-items:center;padding:32px 24px 24px}.td-online-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;background:#f0fdf4;border:1px solid #dcfce7;border-radius:999px}.td-pulse{width:8px;height:8px;background:var(--td-success);border-radius:50%}.td-badge-text{font-size:12px;font-weight:600;color:#15803d;text-transform:uppercase}.td-trust-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:0 24px 24px}.td-trust-item{background:#f8f9fc;border:1px solid var(--td-border);border-radius:12px;padding:12px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}.td-trust-title{font-size:12px;font-weight:700}.td-actions{padding:0 24px 24px;display:flex;flex-direction:column;gap:12px}.td-btn{width:100%;height:48px;border-radius:8px;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;border:none}.td-btn-primary{background:var(--td-primary);color:#fff;box-shadow:0 10px 15px -3px #0e65f133}.td-btn-primary:hover{background:var(--td-primary-hover)}.td-btn-outline{background:transparent;border:1px solid var(--td-border);color:var(--td-text-main);font-size:14px}.td-btn-outline:hover{background:#f8f9fc}.td-footer{padding:12px;text-align:center;background:#f8f9fc;border-top:1px solid var(--td-border);font-size:12px;color:var(--td-text-sub)}.td-consultation-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90vw;height:90vh;max-width:1200px;max-height:800px;background:var(--td-bg-light);border-radius:16px;box-shadow:0 25px 50px -12px #00000080;border:1px solid var(--td-border);display:flex;overflow:hidden;z-index:var(--z-index);animation:fadeIn .3s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translate(-50%,-48%)}to{opacity:1;transform:translate(-50%,-50%)}}.td-video-panel{flex:1;background:#000;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden}.td-main-video{width:100%;height:100%;background-size:cover;background-position:center}.td-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to bottom,rgba(0,0,0,.3),transparent,rgba(0,0,0,.6))}.td-video-header{position:absolute;top:16px;left:16px;right:16px;display:flex;justify-content:space-between;z-index:10}.td-badge-glass{background:#0006;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);padding:6px 12px;border-radius:20px;display:flex;align-items:center;gap:8px;color:#fff;font-size:12px;font-weight:500}.td-pip{position:absolute;top:24px;right:24px;width:160px;aspect-ratio:16/9;background:#333;border-radius:8px;border:2px solid rgba(255,255,255,.2);overflow:hidden;z-index:20;background-size:cover;background-position:center;box-shadow:0 4px 6px #0000004d}.td-pip-label{position:absolute;bottom:4px;left:4px;background:#0009;color:#fff;font-size:10px;padding:2px 6px;border-radius:4px}.td-controls{position:absolute;bottom:32px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:16px;z-index:20}.td-ctrl-btn{width:48px;height:48px;border-radius:50%;background:#fff3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.td-ctrl-btn:hover{background:#fff;color:var(--td-primary)}.td-ctrl-end{height:48px;padding:0 24px;border-radius:24px;background:var(--td-danger);color:#fff;display:flex;align-items:center;gap:8px;font-weight:600;border:none;cursor:pointer}.td-ctrl-end:hover{background:#dc2626}@media (max-width: 768px){.td-consultation-modal{width:100vw;height:100vh;border-radius:0;flex-direction:column}.td-video-panel{height:40vh}.td-chat-panel{width:100%;flex:1}}.td-overlay-wrapper{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:var(--z-index);display:flex;align-items:center;justify-content:center;animation:fadeInOpacity .3s ease-out}.td-modal-centered{background:var(--td-bg-light);width:90%;max-width:480px;border-radius:16px;box-shadow:0 25px 50px -12px #00000040;border:1px solid var(--td-border);display:flex;flex-direction:column;overflow:hidden;max-height:90vh;animation:scaleIn .3s cubic-bezier(.16,1,.3,1);position:relative}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.td-form-container{display:flex;flex-direction:column;height:100%;padding:0 24px}.td-form-page-header{padding:24px 0 16px}.td-form-title{font-size:24px;font-weight:700;color:var(--td-text-main);margin-bottom:8px}.td-form-desc{font-size:14px;color:var(--td-text-sub);line-height:normal}.td-form{display:flex;flex-direction:column;gap:20px;padding-bottom:24px}.td-input-group{display:flex;flex-direction:column;gap:8px}.td-label{font-size:14px;font-weight:500;color:var(--td-text-main)}.td-input{width:100%;height:48px;padding:0 16px;border-radius:8px;border:1px solid var(--td-border);background:#f8f9fc;font-family:inherit;font-size:16px;color:var(--td-text-main);transition:all .2s}.td-input:focus{outline:none;border-color:var(--td-primary);box-shadow:0 0 0 2px #0e65f133}.td-textarea{width:100%;min-height:100px;padding:16px;border-radius:8px;border:1px solid var(--td-border);background:#f8f9fc;font-family:inherit;font-size:16px;color:var(--td-text-main);resize:none}.td-textarea:focus{outline:none;border-color:var(--td-primary)}.td-chips-group{display:flex;flex-wrap:wrap;gap:8px}.td-chip{padding:8px 16px;border-radius:99px;font-size:14px;font-weight:500;cursor:pointer;border:1px solid var(--td-border);background:#fff;color:var(--td-text-sub);transition:all .2s}.td-chip:hover{border-color:var(--td-primary);color:var(--td-primary);background:#0e65f10d}.td-chip.active{border-color:var(--td-primary);background:#0e65f11a;color:var(--td-primary);display:flex;align-items:center;gap:6px}.td-secure-note{display:flex;align-items:center;justify-content:center;gap:6px;font-size:12px;color:var(--td-text-sub);opacity:.8;margin-top:8px}.td-summary-container{display:flex;flex-direction:column;height:100%}.td-success-header{display:flex;flex-direction:column;align-items:center;padding:32px 24px;text-align:center}.td-success-icon{width:80px;height:80px;background:#0e65f11a;color:var(--td-primary);border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:16px}.td-summary-avatar{width:56px;height:56px;border-radius:50%;background-size:cover;background-position:center;flex-shrink:0}.td-summary-title{font-size:24px;font-weight:700;margin-bottom:8px}.td-summary-desc{font-size:14px;color:var(--td-text-sub);max-width:360px;margin:0 auto}.td-summary-card{margin:0 24px;padding:16px;border-radius:12px;border:1px solid var(--td-border);background:#f8f9fc;display:flex;align-items:center;gap:16px}.td-summary-details{padding:8px 24px}.td-detail-row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px dashed var(--td-border);font-size:14px}.td-note-box{margin:16px 24px;padding:16px;background:#0e65f10d;border:1px solid rgba(14,101,241,.1);border-radius:8px}.td-note-title{font-size:14px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px}.td-grid-actions{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}';
|
|
108
108
|
const collapsedHtml = `<div class="td-widget-fab" id="td-widget-fab">\r
|
|
109
109
|
<div class="td-fab-card">\r
|
|
110
110
|
<button class="td-close-fab" id="td-fab-close">\r
|
|
@@ -117,70 +117,72 @@ const collapsedHtml = `<div class="td-widget-fab" id="td-widget-fab">\r
|
|
|
117
117
|
<div class="td-info">\r
|
|
118
118
|
<div class="td-brand">\r
|
|
119
119
|
<span class="material-symbols-outlined" style="font-size: 14px;">local_hospital</span>\r
|
|
120
|
-
<span>
|
|
120
|
+
<span>Tư vấn ngay</span>\r
|
|
121
121
|
</div>\r
|
|
122
122
|
<h3 class="td-cta">Khám online ngay</h3>\r
|
|
123
123
|
<p class="td-sub">Bác sĩ đang Online</p>\r
|
|
124
124
|
</div>\r
|
|
125
125
|
</div>\r
|
|
126
126
|
</div>`;
|
|
127
|
-
const expandedHtml = `<div class="td-
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
<div class="td-
|
|
131
|
-
<
|
|
127
|
+
const expandedHtml = `<div class="td-overlay-wrapper">\r
|
|
128
|
+
<div class="td-modal-centered" id="td-widget-expanded">\r
|
|
129
|
+
<header class="td-header">\r
|
|
130
|
+
<div class="td-header-title">\r
|
|
131
|
+
<div class="td-icon-box">\r
|
|
132
|
+
<span class="material-symbols-outlined">medical_services</span>\r
|
|
133
|
+
</div>\r
|
|
134
|
+
<span>Tư vấn trực tuyến</span>\r
|
|
132
135
|
</div>\r
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</button>\r
|
|
138
|
-
</header>\r
|
|
136
|
+
<button class="td-close-btn" id="td-expanded-close">\r
|
|
137
|
+
<span class="material-symbols-outlined">close</span>\r
|
|
138
|
+
</button>\r
|
|
139
|
+
</header>\r
|
|
139
140
|
\r
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
<div class="td-content">\r
|
|
142
|
+
<div class="td-doctor-profile">\r
|
|
143
|
+
<div class="td-doctor-avatar" style="background-image: url('{{doctorAvatar}}');">\r
|
|
144
|
+
<!-- <div class="td-status-dot"\r
|
|
145
|
+
style="width: 20px; height: 20px; border-width: 3px; bottom: 4px; right: 4px; background: #22c55e;">\r
|
|
146
|
+
</div> -->\r
|
|
145
147
|
</div>\r
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<p class="td-doctor-specialty">{{doctorSpecialty}}</p>\r
|
|
148
|
+
<h1 class="td-doctor-name">{{doctorName}}</h1>\r
|
|
149
|
+
<p class="td-doctor-specialty">{{doctorSpecialty}}</p>\r
|
|
149
150
|
\r
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
<div class="td-online-badge">\r
|
|
152
|
+
<span class="td-pulse"></span>\r
|
|
153
|
+
<span class="td-badge-text">Đang trực tuyến</span>\r
|
|
154
|
+
</div>\r
|
|
153
155
|
</div>\r
|
|
154
|
-
</div>\r
|
|
155
156
|
\r
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
<div class="td-trust-grid">\r
|
|
158
|
+
<div class="td-trust-item">\r
|
|
159
|
+
<div class="td-icon-box">\r
|
|
160
|
+
<span class="material-symbols-outlined">verified</span>\r
|
|
161
|
+
</div>\r
|
|
162
|
+
<h3 class="td-trust-title">Bác sĩ được xác thực</h3>\r
|
|
160
163
|
</div>\r
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<
|
|
164
|
+
<div class="td-trust-item">\r
|
|
165
|
+
<div class="td-icon-box">\r
|
|
166
|
+
<span class="material-symbols-outlined">security</span>\r
|
|
167
|
+
</div>\r
|
|
168
|
+
<h3 class="td-trust-title">Bảo mật thông tin</h3>\r
|
|
166
169
|
</div>\r
|
|
167
|
-
<h3 class="td-trust-title">Bảo mật thông tin</h3>\r
|
|
168
170
|
</div>\r
|
|
169
|
-
</div>\r
|
|
170
171
|
\r
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
172
|
+
<div class="td-actions">\r
|
|
173
|
+
<button class="td-btn td-btn-primary" id="td-btn-consult">\r
|
|
174
|
+
<span class="material-symbols-outlined" style="margin-right: 8px;">videocam</span>\r
|
|
175
|
+
Bắt đầu tư vấn ngay\r
|
|
176
|
+
</button>\r
|
|
177
|
+
<button class="td-btn td-btn-outline" id="td-btn-schedule">\r
|
|
178
|
+
Đặt lịch hẹn sau\r
|
|
179
|
+
</button>\r
|
|
180
|
+
</div>\r
|
|
179
181
|
</div>\r
|
|
180
|
-
</div>\r
|
|
181
182
|
\r
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
<div class="td-footer">\r
|
|
184
|
+
Powered by <b>Clinic Connect</b>\r
|
|
185
|
+
</div>\r
|
|
184
186
|
</div>\r
|
|
185
187
|
</div>`;
|
|
186
188
|
const consultationHtml = `<div class="td-consultation-modal">\r
|
|
@@ -192,15 +194,15 @@ const consultationHtml = `<div class="td-consultation-modal">\r
|
|
|
192
194
|
<div class="td-overlay"></div>\r
|
|
193
195
|
\r
|
|
194
196
|
<div class="td-video-header">\r
|
|
195
|
-
<div class="td-badge-glass">\r
|
|
197
|
+
<!-- <div class="td-badge-glass">\r
|
|
196
198
|
<span class="material-symbols-outlined"\r
|
|
197
199
|
style="color: #ef4444; font-size: 16px;">radio_button_checked</span>\r
|
|
198
200
|
REC\r
|
|
199
|
-
</div
|
|
200
|
-
<div class="td-badge-glass">\r
|
|
201
|
+
</div> -->\r
|
|
202
|
+
<!-- <div class="td-badge-glass">\r
|
|
201
203
|
<span class="material-symbols-outlined" style="font-size: 16px;">signal_cellular_alt</span>\r
|
|
202
204
|
HD Quality\r
|
|
203
|
-
</div
|
|
205
|
+
</div> -->\r
|
|
204
206
|
</div>\r
|
|
205
207
|
\r
|
|
206
208
|
<div class="td-pip"\r
|
|
@@ -209,10 +211,10 @@ const consultationHtml = `<div class="td-consultation-modal">\r
|
|
|
209
211
|
</div>\r
|
|
210
212
|
\r
|
|
211
213
|
<div class="td-controls">\r
|
|
212
|
-
<button class="td-ctrl-btn">\r
|
|
214
|
+
<button class="td-ctrl-btn" id="td-btn-mic">\r
|
|
213
215
|
<span class="material-symbols-outlined">mic</span>\r
|
|
214
216
|
</button>\r
|
|
215
|
-
<button class="td-ctrl-btn">\r
|
|
217
|
+
<button class="td-ctrl-btn" id="td-btn-cam">\r
|
|
216
218
|
<span class="material-symbols-outlined">videocam</span>\r
|
|
217
219
|
</button>\r
|
|
218
220
|
<button class="td-ctrl-end" id="td-consult-end">\r
|
|
@@ -225,127 +227,78 @@ const consultationHtml = `<div class="td-consultation-modal">\r
|
|
|
225
227
|
</div>\r
|
|
226
228
|
</div>\r
|
|
227
229
|
\r
|
|
228
|
-
<!-- Right: Chat Panel -->\r
|
|
229
|
-
<div class="td-chat-panel">\r
|
|
230
|
-
<div class="td-chat-header">\r
|
|
231
|
-
<div class="td-chat-doc-info">\r
|
|
232
|
-
<div class="td-chat-avatar" style="background-image: url('{{doctorAvatar}}');"></div>\r
|
|
233
|
-
<div>\r
|
|
234
|
-
<h3 style="font-size: 16px; font-weight: 700;">{{doctorName}}</h3>\r
|
|
235
|
-
<p style="font-size: 12px; color: var(--td-text-sub);">Online • Verified</p>\r
|
|
236
|
-
</div>\r
|
|
237
|
-
</div>\r
|
|
238
|
-
<div class="td-chat-timer">\r
|
|
239
|
-
<div style="display: flex; align-items: center; gap: 4px; font-size: 12px;">\r
|
|
240
|
-
<span class="material-symbols-outlined"\r
|
|
241
|
-
style="font-size: 16px; color: var(--td-primary);">timer</span>\r
|
|
242
|
-
SESSION\r
|
|
243
|
-
</div>\r
|
|
244
|
-
<div class="td-timer-val">00:03:12</div>\r
|
|
245
|
-
</div>\r
|
|
246
|
-
</div>\r
|
|
247
|
-
\r
|
|
248
|
-
<div class="td-chat-messages">\r
|
|
249
|
-
<div class="td-msg-system">\r
|
|
250
|
-
Consultation started\r
|
|
251
|
-
</div>\r
|
|
252
|
-
<div class="td-msg-row doctor">\r
|
|
253
|
-
<div class="td-msg-bubble">\r
|
|
254
|
-
Xin chào. Tôi có thể giúp gì cho bạn hôm nay?\r
|
|
255
|
-
<div class="td-msg-time">14:01</div>\r
|
|
256
|
-
</div>\r
|
|
257
|
-
</div>\r
|
|
258
|
-
<div class="td-msg-row own">\r
|
|
259
|
-
<div class="td-msg-bubble">\r
|
|
260
|
-
Chào bác sĩ, tôi bị đau đầu...\r
|
|
261
|
-
<div class="td-msg-time">14:02</div>\r
|
|
262
|
-
</div>\r
|
|
263
|
-
</div>\r
|
|
264
|
-
</div>\r
|
|
265
|
-
\r
|
|
266
|
-
<div class="td-chat-input-area">\r
|
|
267
|
-
<div class="td-input-wrapper">\r
|
|
268
|
-
<textarea class="td-chat-input" placeholder="Nhập tin nhắn..."></textarea>\r
|
|
269
|
-
<button class="td-send-btn">\r
|
|
270
|
-
<span class="material-symbols-outlined">send</span>\r
|
|
271
|
-
</button>\r
|
|
272
|
-
</div>\r
|
|
273
|
-
<div style="text-align: center; margin-top: 8px; font-size: 10px; color: var(--td-text-sub);">\r
|
|
274
|
-
<span class="material-symbols-outlined" style="font-size: 10px; vertical-align: middle;">lock</span>\r
|
|
275
|
-
Mã hóa đầu cuối\r
|
|
276
|
-
</div>\r
|
|
277
|
-
</div>\r
|
|
278
|
-
</div>\r
|
|
279
230
|
</div>`;
|
|
280
|
-
const patientFormHtml = '<div class="td-
|
|
281
|
-
const postConsultationHtml = `<div class="td-
|
|
282
|
-
<
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
231
|
+
const patientFormHtml = '<div class="td-overlay-wrapper">\r\n <div class="td-modal-centered" id="td-patient-form">\r\n <header class="td-header">\r\n <div class="td-header-title">\r\n <div class="td-icon-box">\r\n <span class="material-symbols-outlined">medical_services</span>\r\n </div>\r\n <span>Tư vấn trực tuyến</span>\r\n </div>\r\n <button class="td-close-btn" id="td-form-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-form-container">\r\n <div class="td-form-page-header">\r\n <h1 class="td-form-title">Kết nối với Bác sĩ</h1>\r\n <p class="td-form-desc">Vui lòng nhập thông tin để bắt đầu tư vấn trực tuyến.</p>\r\n </div>\r\n\r\n <form class="td-form" id="td-info-form">\r\n <div class="td-input-group">\r\n <label class="td-label">Họ và tên</label>\r\n <input type="text" class="td-input" name="name" placeholder="VD: Nguyễn Văn A" required />\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Số điện thoại</label>\r\n <div style="position: relative;">\r\n <input type="tel" class="td-input" name="phone" placeholder="VD: 0912 345 678" required />\r\n <!-- <div\r\n style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--td-primary);">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">smartphone</span>\r\n </div> -->\r\n </div>\r\n </div>\r\n\r\n <!-- <div class="td-input-group">\r\n <label class="td-label">Triệu chứng của bạn?</label>\r\n <div class="td-chips-group">\r\n <button type="button" class="td-chip active">Sốt <span class="material-symbols-outlined"\r\n style="font-size: 16px;">check</span></button>\r\n <button type="button" class="td-chip">Ho / Cảm cúm</button>\r\n <button type="button" class="td-chip">Đau đầu</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n </div>\r\n </div> -->\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Mô tả thêm <span\r\n style="color: var(--td-text-sub); font-weight: 400;">(Tùy\r\n chọn)</span></label>\r\n <textarea class="td-textarea" name="symptoms"\r\n placeholder="Bạn đang cảm thấy như thế nào?"></textarea>\r\n </div>\r\n\r\n <div style="margin-top: 8px;">\r\n <button type="submit" class="td-btn td-btn-primary" id="td-form-submit">\r\n Bắt đầu tư vấn\r\n <span class="material-symbols-outlined" style="margin-left: 8px;">arrow_forward</span>\r\n </button>\r\n </div>\r\n\r\n <div class="td-secure-note">\r\n <span class="material-symbols-outlined" style="font-size: 14px;">lock</span>\r\n Thông tin y tế được bảo mật 100%\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </div>\r\n</div>';
|
|
232
|
+
const postConsultationHtml = `<div class="td-overlay-wrapper">\r
|
|
233
|
+
<div class="td-modal-centered" id="td-summary-view">\r
|
|
234
|
+
<header class="td-header">\r
|
|
235
|
+
<h2 class="td-header-title" style="font-size: 16px;">Tổng kết tư vấn</h2>\r
|
|
236
|
+
<button class="td-close-btn" id="td-summary-close">\r
|
|
237
|
+
<span class="material-symbols-outlined">close</span>\r
|
|
238
|
+
</button>\r
|
|
239
|
+
</header>\r
|
|
288
240
|
\r
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
241
|
+
<div class="td-content">\r
|
|
242
|
+
<div class="td-summary-container">\r
|
|
243
|
+
<div class="td-success-header">\r
|
|
244
|
+
<div class="td-success-icon">\r
|
|
245
|
+
<span class="material-symbols-outlined" style="font-size: 48px;">check_circle</span>\r
|
|
246
|
+
</div>\r
|
|
247
|
+
<h3 class="td-summary-title">Tư vấn Hoàn tất</h3>\r
|
|
248
|
+
<p class="td-summary-desc">Cuộc hẹn đã được ghi nhận vào hồ sơ sức khỏe của bạn.</p>\r
|
|
294
249
|
</div>\r
|
|
295
|
-
<h3 class="td-summary-title">Tư vấn Hoàn tất</h3>\r
|
|
296
|
-
<p class="td-summary-desc">Cuộc hẹn đã được ghi nhận vào hồ sơ sức khỏe của bạn.</p>\r
|
|
297
|
-
</div>\r
|
|
298
250
|
\r
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
251
|
+
<div class="td-summary-card">\r
|
|
252
|
+
<div class="td-summary-avatar" style="background-image: url('{{doctorAvatar}}');"></div>\r
|
|
253
|
+
<div>\r
|
|
254
|
+
<div style="font-weight: 700;">{{doctorName}}</div>\r
|
|
255
|
+
<div style="color: var(--td-text-sub); font-size: 14px;">{{doctorSpecialty}}</div>\r
|
|
256
|
+
</div>\r
|
|
257
|
+
<div style="margin-left: auto; color: var(--td-primary);">\r
|
|
258
|
+
<span class="material-symbols-outlined">verified_user</span>\r
|
|
259
|
+
</div>\r
|
|
308
260
|
</div>\r
|
|
309
|
-
</div>\r
|
|
310
261
|
\r
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
262
|
+
<div class="td-summary-details">\r
|
|
263
|
+
<div class="td-detail-row">\r
|
|
264
|
+
<span style="color: var(--td-text-sub); display: flex; gap: 6px;">\r
|
|
265
|
+
<span class="material-symbols-outlined" style="font-size: 18px;">tag</span> Mã tư vấn\r
|
|
266
|
+
</span>\r
|
|
267
|
+
<span style="font-weight: 600;">#TD-93821</span>\r
|
|
268
|
+
</div>\r
|
|
269
|
+
<div class="td-detail-row">\r
|
|
270
|
+
<span style="color: var(--td-text-sub); display: flex; gap: 6px;">\r
|
|
271
|
+
<span class="material-symbols-outlined" style="font-size: 18px;">schedule</span> Thời gian\r
|
|
272
|
+
</span>\r
|
|
273
|
+
<span style="font-weight: 600;">10:00 AM, Today</span>\r
|
|
274
|
+
</div>\r
|
|
317
275
|
</div>\r
|
|
318
|
-
<div class="td-detail-row">\r
|
|
319
|
-
<span style="color: var(--td-text-sub); display: flex; gap: 6px;">\r
|
|
320
|
-
<span class="material-symbols-outlined" style="font-size: 18px;">schedule</span> Thời gian\r
|
|
321
|
-
</span>\r
|
|
322
|
-
<span style="font-weight: 600;">10:00 AM, Today</span>\r
|
|
323
|
-
</div>\r
|
|
324
|
-
</div>\r
|
|
325
276
|
\r
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
277
|
+
<div class="td-note-box">\r
|
|
278
|
+
<div class="td-note-title">\r
|
|
279
|
+
<span class="material-symbols-outlined"\r
|
|
280
|
+
style="font-size: 16px; color: var(--td-primary);">clinical_notes</span>\r
|
|
281
|
+
Ghi chú bác sĩ\r
|
|
282
|
+
</div>\r
|
|
283
|
+
<p style="font-size: 14px; line-height: 1.5;">Bệnh nhân cần nghỉ ngơi và uống nhiều nước. Hạn chế\r
|
|
284
|
+
vận\r
|
|
285
|
+
động mạnh trong 2 ngày tới.</p>\r
|
|
331
286
|
</div>\r
|
|
332
|
-
<p style="font-size: 14px; line-height: 1.5;">Bệnh nhân cần nghỉ ngơi và uống nhiều nước. Hạn chế vận\r
|
|
333
|
-
động mạnh trong 2 ngày tới.</p>\r
|
|
334
|
-
</div>\r
|
|
335
287
|
\r
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
288
|
+
<div class="td-actions">\r
|
|
289
|
+
<div class="td-grid-actions">\r
|
|
290
|
+
<button class="td-btn td-btn-outline" style="font-size: 12px; height: 40px; margin-bottom: 0;">\r
|
|
291
|
+
Xem kết luận\r
|
|
292
|
+
</button>\r
|
|
293
|
+
<button class="td-btn td-btn-outline" style="font-size: 12px; height: 40px; margin-bottom: 0;">\r
|
|
294
|
+
Tải đơn thuốc\r
|
|
295
|
+
</button>\r
|
|
296
|
+
</div>\r
|
|
297
|
+
<button class="td-btn td-btn-primary">\r
|
|
298
|
+
<span class="material-symbols-outlined" style="margin-right: 8px;">calendar_month</span>\r
|
|
299
|
+
Đặt lịch tái khám\r
|
|
343
300
|
</button>\r
|
|
344
301
|
</div>\r
|
|
345
|
-
<button class="td-btn td-btn-primary">\r
|
|
346
|
-
<span class="material-symbols-outlined" style="margin-right: 8px;">calendar_month</span>\r
|
|
347
|
-
Đặt lịch tái khám\r
|
|
348
|
-
</button>\r
|
|
349
302
|
</div>\r
|
|
350
303
|
</div>\r
|
|
351
304
|
</div>\r
|
|
@@ -433,7 +386,7 @@ const inlineTriggerHtml = `<style>\r
|
|
|
433
386
|
<span class="td-inline-text">Bác sĩ đang Online</span>\r
|
|
434
387
|
</div>\r
|
|
435
388
|
\r
|
|
436
|
-
<button id="td-inline-btn" style="\r
|
|
389
|
+
<button id="td-inline-btn" type="button" style="\r
|
|
437
390
|
background: #0066ff; \r
|
|
438
391
|
color: white; \r
|
|
439
392
|
border: none; \r
|
|
@@ -454,6 +407,7 @@ const inlineTriggerHtml = `<style>\r
|
|
|
454
407
|
</button>\r
|
|
455
408
|
</div>\r
|
|
456
409
|
</div>`;
|
|
410
|
+
const permissionModalHtml = '<div class="modalOverlay" id="td-permission-modal">\r\n <div class="modal">\r\n <div class="modalHead">\r\n <h2>Cho phép truy cập Microphone và Camera</h2>\r\n <button id="td-perm-close" class="btn btn-icon" aria-label="Đóng">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">close</span>\r\n </button>\r\n </div>\r\n <div class="modalBody">\r\n Website sắp yêu cầu quyền <strong>Microphone</strong> và <strong>Camera</strong> để:\r\n <ul>\r\n <li>Gọi video trong phiên tư vấn.</li>\r\n <li>Kiểm tra thiết bị trước khi bắt đầu.</li>\r\n </ul>\r\n <div class="note">\r\n Sau khi bấm “Tiếp tục”, trình duyệt sẽ hiện thông báo hệ thống. Vui lòng chọn <strong>Allow</strong>.\r\n </div>\r\n </div>\r\n <div class="modalFoot">\r\n <button id="td-perm-cancel" class="btn">Hủy</button>\r\n <button id="td-perm-allow" class="btn primary">Tiếp tục</button>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<style>\r\n #td-permission-modal {\r\n --bg: var(--td-bg-light, #ffffff);\r\n --text: var(--td-text-main, #0d131c);\r\n --muted: var(--td-text-sub, #49699c);\r\n --border: var(--td-border, #e7ecf4);\r\n --primary: var(--td-primary, #0e65f1);\r\n --primary-hover: var(--td-primary-hover, #0b50c0);\r\n --shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\r\n --radius: 12px;\r\n font-family: var(--font-family, system-ui, -apple-system, sans-serif);\r\n z-index: 2147483647;\r\n }\r\n\r\n /* Modal Overlay */\r\n .modalOverlay {\r\n position: fixed;\r\n inset: 0;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background: rgba(0, 0, 0, .5);\r\n backdrop-filter: blur(2px);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 18px;\r\n z-index: 2147483647;\r\n }\r\n\r\n .modal {\r\n width: min(500px, 94vw);\r\n border-radius: var(--radius);\r\n border: 1px solid var(--border);\r\n background: var(--bg);\r\n box-shadow: var(--shadow);\r\n overflow: hidden;\r\n color: var(--text);\r\n font-size: 14px;\r\n line-height: 1.5;\r\n }\r\n\r\n .modalHead {\r\n padding: 16px 20px;\r\n border-bottom: 1px solid var(--border);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 10px;\r\n background: #f8f9fc;\r\n }\r\n\r\n .modalHead h2 {\r\n margin: 0;\r\n font-size: 16px;\r\n font-weight: 700;\r\n color: var(--text);\r\n }\r\n\r\n .modalBody {\r\n padding: 20px;\r\n color: var(--text);\r\n }\r\n\r\n .modalBody ul {\r\n margin: 12px 0 16px 0;\r\n padding-left: 20px;\r\n color: var(--muted);\r\n }\r\n\r\n .modalBody li {\r\n margin-bottom: 6px;\r\n }\r\n\r\n .modalFoot {\r\n padding: 16px 20px;\r\n display: flex;\r\n gap: 12px;\r\n justify-content: flex-end;\r\n border-top: 1px solid var(--border);\r\n background: #f8f9fc;\r\n }\r\n\r\n .note {\r\n margin-top: 12px;\r\n color: var(--muted);\r\n font-size: 13px;\r\n background: rgba(14, 101, 241, 0.05);\r\n padding: 10px;\r\n border-radius: 8px;\r\n border: 1px dashed var(--primary);\r\n }\r\n\r\n .note strong {\r\n color: var(--primary);\r\n }\r\n\r\n /* Buttons */\r\n .btn {\r\n appearance: none;\r\n border: 1px solid var(--border);\r\n background: white;\r\n color: var(--text);\r\n padding: 10px 16px;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s;\r\n font-weight: 600;\r\n font-family: inherit;\r\n font-size: 14px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n\r\n .btn:hover {\r\n background: #f1f5f9;\r\n border-color: #cbd5e1;\r\n }\r\n\r\n .btn-icon {\r\n padding: 4px;\r\n border: none;\r\n background: transparent;\r\n color: #64748b;\r\n border-radius: 50%;\r\n }\r\n\r\n .btn-icon:hover {\r\n background: rgba(0, 0, 0, 0.05);\r\n border-color: transparent;\r\n color: var(--text);\r\n }\r\n\r\n .btn.primary {\r\n background: var(--primary);\r\n border-color: var(--primary);\r\n color: white;\r\n }\r\n\r\n .btn.primary:hover {\r\n background: var(--primary-hover);\r\n border-color: var(--primary-hover);\r\n }\r\n</style>';
|
|
457
411
|
const render = (template, data) => {
|
|
458
412
|
var _a, _b, _c;
|
|
459
413
|
let output = template;
|
|
@@ -471,6 +425,7 @@ const getConsultationHtml = (data) => render(consultationHtml, data);
|
|
|
471
425
|
const getPatientFormHtml = (data) => render(patientFormHtml, data);
|
|
472
426
|
const getPostConsultationHtml = (data) => render(postConsultationHtml, data);
|
|
473
427
|
const getInlineTriggerHtml = (data) => render(inlineTriggerHtml, data);
|
|
428
|
+
const getPermissionModalHtml = (data) => render(permissionModalHtml, data);
|
|
474
429
|
const PACKET_TYPES = /* @__PURE__ */ Object.create(null);
|
|
475
430
|
PACKET_TYPES["open"] = "0";
|
|
476
431
|
PACKET_TYPES["close"] = "1";
|
|
@@ -28017,6 +27972,56 @@ class LiveKitService {
|
|
|
28017
27972
|
constructor() {
|
|
28018
27973
|
this.room = null;
|
|
28019
27974
|
this.wrapper = null;
|
|
27975
|
+
this.isPermissionGranted = false;
|
|
27976
|
+
this.permissionPromise = null;
|
|
27977
|
+
}
|
|
27978
|
+
/**
|
|
27979
|
+
* Request Camera & Mic permissions early
|
|
27980
|
+
*/
|
|
27981
|
+
async requestPermissions() {
|
|
27982
|
+
if (this.permissionPromise) {
|
|
27983
|
+
console.log("[LiveKit] Joining existing permission request...");
|
|
27984
|
+
return this.permissionPromise;
|
|
27985
|
+
}
|
|
27986
|
+
this.permissionPromise = (async () => {
|
|
27987
|
+
try {
|
|
27988
|
+
console.log("[LiveKit] Requesting early permissions...");
|
|
27989
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
27990
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
27991
|
+
this.isPermissionGranted = true;
|
|
27992
|
+
console.log("[LiveKit] Permissions primed successfully.");
|
|
27993
|
+
return true;
|
|
27994
|
+
} catch (error) {
|
|
27995
|
+
console.error("[LiveKit] Permission request failed:", error);
|
|
27996
|
+
console.log(`Không thể xin quyền: ${error.name} - ${error.message}`);
|
|
27997
|
+
return false;
|
|
27998
|
+
} finally {
|
|
27999
|
+
this.permissionPromise = null;
|
|
28000
|
+
}
|
|
28001
|
+
})();
|
|
28002
|
+
return this.permissionPromise;
|
|
28003
|
+
}
|
|
28004
|
+
/**
|
|
28005
|
+
* Check if permissions are already granted
|
|
28006
|
+
* @returns {Promise<boolean>}
|
|
28007
|
+
*/
|
|
28008
|
+
async checkPermissions() {
|
|
28009
|
+
if (this.isPermissionGranted) {
|
|
28010
|
+
console.log("[LiveKit] Permission flag is true.");
|
|
28011
|
+
return true;
|
|
28012
|
+
}
|
|
28013
|
+
if (!navigator.permissions || !navigator.permissions.query) {
|
|
28014
|
+
return false;
|
|
28015
|
+
}
|
|
28016
|
+
try {
|
|
28017
|
+
const cam = await navigator.permissions.query({ name: "camera" });
|
|
28018
|
+
const mic = await navigator.permissions.query({ name: "microphone" });
|
|
28019
|
+
console.log("[LiveKit] Permissions check:", { cam: cam.state, mic: mic.state });
|
|
28020
|
+
return cam.state === "granted" && mic.state === "granted";
|
|
28021
|
+
} catch (error) {
|
|
28022
|
+
console.warn("[LiveKit] Permission check not supported or failed:", error);
|
|
28023
|
+
return false;
|
|
28024
|
+
}
|
|
28020
28025
|
}
|
|
28021
28026
|
/**
|
|
28022
28027
|
* Connect to LiveKit Room
|
|
@@ -28042,7 +28047,6 @@ class LiveKitService {
|
|
|
28042
28047
|
try {
|
|
28043
28048
|
await this.room.connect(url2, token);
|
|
28044
28049
|
console.log("[LiveKit] Connected to room:", this.room.name);
|
|
28045
|
-
await this.publishLocalTracks();
|
|
28046
28050
|
} catch (error) {
|
|
28047
28051
|
console.error("[LiveKit] Connection failed:", error);
|
|
28048
28052
|
}
|
|
@@ -28065,6 +28069,7 @@ class LiveKitService {
|
|
|
28065
28069
|
const localContainer = this.wrapper.querySelector(".td-pip");
|
|
28066
28070
|
try {
|
|
28067
28071
|
await this.room.localParticipant.setCameraEnabled(true);
|
|
28072
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
28068
28073
|
const videoPub = Array.from(this.room.localParticipant.videoTrackPublications.values()).find((p) => p.source === "camera");
|
|
28069
28074
|
if (videoPub && videoPub.track && localContainer) {
|
|
28070
28075
|
localContainer.innerHTML = "";
|
|
@@ -28076,24 +28081,15 @@ class LiveKitService {
|
|
|
28076
28081
|
videoEl.style.transform = "scale(-1, 1)";
|
|
28077
28082
|
localContainer.appendChild(videoEl);
|
|
28078
28083
|
console.log("[LiveKit] Local video attached");
|
|
28084
|
+
} else {
|
|
28085
|
+
console.warn("[LiveKit] No Video Publication found to attach!");
|
|
28079
28086
|
}
|
|
28080
28087
|
} catch (e2) {
|
|
28081
|
-
console.error("[LiveKit] Failed to
|
|
28082
|
-
if (e2.name === "NotAllowedError") {
|
|
28083
|
-
alert("Không thể mở Camera. Hãy kiểm tra icon ổ khóa trên thanh địa chỉ hoặc cài đặt Quyền Riêng Tư (Privacy) của máy tính.");
|
|
28084
|
-
}
|
|
28085
|
-
}
|
|
28086
|
-
try {
|
|
28087
|
-
const devices = await Room.getLocalDevices("audioinput");
|
|
28088
|
-
console.log("[LiveKit] Audio Input Devices:", devices);
|
|
28089
|
-
if (devices.length === 0) {
|
|
28090
|
-
alert("Không tìm thấy Micro nào trên thiết bị của bạn!");
|
|
28091
|
-
}
|
|
28092
|
-
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
28093
|
-
} catch (e2) {
|
|
28094
|
-
console.error("[LiveKit] Failed to enable Microphone:", e2);
|
|
28088
|
+
console.error("[LiveKit] Failed to publish tracks:", e2);
|
|
28095
28089
|
if (e2.name === "NotAllowedError") {
|
|
28096
|
-
|
|
28090
|
+
console.log("Không thể mở Camera/Microphone. Hãy kiểm tra icon ổ khóa trên thanh địa chỉ hoặc cài đặt quyền riêng tư.");
|
|
28091
|
+
} else {
|
|
28092
|
+
console.log(`Lỗi Camera: ${e2.message}`);
|
|
28097
28093
|
}
|
|
28098
28094
|
}
|
|
28099
28095
|
}
|
|
@@ -28121,14 +28117,66 @@ class LiveKitService {
|
|
|
28121
28117
|
console.log("[LiveKit] Remote audio attached");
|
|
28122
28118
|
}
|
|
28123
28119
|
}
|
|
28120
|
+
toggleMicrophone(enabled) {
|
|
28121
|
+
if (this.room && this.room.localParticipant) {
|
|
28122
|
+
this.room.localParticipant.setMicrophoneEnabled(enabled);
|
|
28123
|
+
console.log(`[LiveKit] Microphone ${enabled ? "enabled" : "disabled"}`);
|
|
28124
|
+
}
|
|
28125
|
+
}
|
|
28126
|
+
toggleCamera(enabled) {
|
|
28127
|
+
if (this.room && this.room.localParticipant) {
|
|
28128
|
+
this.room.localParticipant.setCameraEnabled(enabled);
|
|
28129
|
+
console.log(`[LiveKit] Camera ${enabled ? "enabled" : "disabled"}`);
|
|
28130
|
+
}
|
|
28131
|
+
}
|
|
28124
28132
|
handleTrackUnsubscribed(track, participant) {
|
|
28125
28133
|
track.detach().forEach((element) => element.remove());
|
|
28126
28134
|
}
|
|
28127
28135
|
disconnect() {
|
|
28136
|
+
var _a, _b;
|
|
28128
28137
|
if (this.room) {
|
|
28129
|
-
this.room.
|
|
28138
|
+
if (this.room.localParticipant) {
|
|
28139
|
+
try {
|
|
28140
|
+
const allTracks = [
|
|
28141
|
+
...((_a = this.room.localParticipant.videoTrackPublications) == null ? void 0 : _a.values()) || [],
|
|
28142
|
+
...((_b = this.room.localParticipant.audioTrackPublications) == null ? void 0 : _b.values()) || []
|
|
28143
|
+
];
|
|
28144
|
+
allTracks.forEach((publication) => {
|
|
28145
|
+
if (publication.track) {
|
|
28146
|
+
publication.track.stop();
|
|
28147
|
+
publication.track.detach().forEach((el) => el.remove());
|
|
28148
|
+
console.log(`[LiveKit] Stopped local track: ${publication.kind}`);
|
|
28149
|
+
}
|
|
28150
|
+
});
|
|
28151
|
+
} catch (err) {
|
|
28152
|
+
console.error("[LiveKit] Error stopping tracks:", err);
|
|
28153
|
+
}
|
|
28154
|
+
}
|
|
28155
|
+
try {
|
|
28156
|
+
this.room.disconnect();
|
|
28157
|
+
} catch (err) {
|
|
28158
|
+
console.error("[LiveKit] Error disconnecting room:", err);
|
|
28159
|
+
}
|
|
28130
28160
|
this.room = null;
|
|
28131
28161
|
}
|
|
28162
|
+
if (this.preAcquiredTracks) {
|
|
28163
|
+
this.preAcquiredTracks.forEach((t) => {
|
|
28164
|
+
try {
|
|
28165
|
+
t.stop();
|
|
28166
|
+
} catch (e2) {
|
|
28167
|
+
}
|
|
28168
|
+
});
|
|
28169
|
+
this.preAcquiredTracks = [];
|
|
28170
|
+
}
|
|
28171
|
+
if (this.wrapper) {
|
|
28172
|
+
const localContainer = this.wrapper.querySelector(".td-pip");
|
|
28173
|
+
const remoteContainer = this.wrapper.querySelector(".td-main-video");
|
|
28174
|
+
if (localContainer) localContainer.innerHTML = "";
|
|
28175
|
+
if (remoteContainer) {
|
|
28176
|
+
remoteContainer.innerHTML = "";
|
|
28177
|
+
remoteContainer.style.backgroundImage = "";
|
|
28178
|
+
}
|
|
28179
|
+
}
|
|
28132
28180
|
}
|
|
28133
28181
|
}
|
|
28134
28182
|
class ClinicWidget {
|
|
@@ -28144,9 +28192,11 @@ class ClinicWidget {
|
|
|
28144
28192
|
}
|
|
28145
28193
|
async init() {
|
|
28146
28194
|
var _a;
|
|
28195
|
+
this.videoToken = null;
|
|
28147
28196
|
this.mount();
|
|
28148
28197
|
this.unsubscribe = store.on(EventTypes.STATE_CHANGE, this.handleStateChange.bind(this));
|
|
28149
28198
|
store.setState({ status: WidgetStates.LOADING });
|
|
28199
|
+
this.startSocket();
|
|
28150
28200
|
try {
|
|
28151
28201
|
const response = await this.api.init(this.config.widgetId);
|
|
28152
28202
|
if (response.success) {
|
|
@@ -28165,7 +28215,6 @@ class ClinicWidget {
|
|
|
28165
28215
|
// Default open state
|
|
28166
28216
|
});
|
|
28167
28217
|
this.checkInlineTrigger();
|
|
28168
|
-
this.startSocket();
|
|
28169
28218
|
} else {
|
|
28170
28219
|
store.setState({ status: WidgetStates.IDLE, error: "Init failed" });
|
|
28171
28220
|
}
|
|
@@ -28260,9 +28309,19 @@ class ClinicWidget {
|
|
|
28260
28309
|
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28261
28310
|
});
|
|
28262
28311
|
}
|
|
28312
|
+
const overlay = this.shadowRoot.querySelector(".td-overlay-wrapper");
|
|
28313
|
+
if (overlay) {
|
|
28314
|
+
overlay.addEventListener("click", (e2) => {
|
|
28315
|
+
if (e2.target === overlay) {
|
|
28316
|
+
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28317
|
+
}
|
|
28318
|
+
});
|
|
28319
|
+
}
|
|
28263
28320
|
if (consultBtn) {
|
|
28264
28321
|
consultBtn.addEventListener("click", () => {
|
|
28265
|
-
|
|
28322
|
+
consultBtn.addEventListener("click", () => {
|
|
28323
|
+
store.setState({ status: WidgetStates.PATIENT_FORM });
|
|
28324
|
+
});
|
|
28266
28325
|
});
|
|
28267
28326
|
}
|
|
28268
28327
|
} else if (status === WidgetStates.PATIENT_FORM) {
|
|
@@ -28273,17 +28332,42 @@ class ClinicWidget {
|
|
|
28273
28332
|
store.setState({ status: WidgetStates.EXPANDED });
|
|
28274
28333
|
});
|
|
28275
28334
|
}
|
|
28335
|
+
const overlay = this.shadowRoot.querySelector(".td-overlay-wrapper");
|
|
28336
|
+
if (overlay) {
|
|
28337
|
+
overlay.addEventListener("click", (e2) => {
|
|
28338
|
+
if (e2.target === overlay) {
|
|
28339
|
+
store.setState({ status: WidgetStates.EXPANDED });
|
|
28340
|
+
}
|
|
28341
|
+
});
|
|
28342
|
+
}
|
|
28276
28343
|
if (form) {
|
|
28277
|
-
form.addEventListener("submit", (e2) => {
|
|
28344
|
+
form.addEventListener("submit", async (e2) => {
|
|
28278
28345
|
e2.preventDefault();
|
|
28279
28346
|
const formData = new FormData(form);
|
|
28280
28347
|
const name = formData.get("name");
|
|
28281
28348
|
const phone = formData.get("phone");
|
|
28282
|
-
this.triggerCallback("onConsultationStarted", { name, phone });
|
|
28283
28349
|
store.setState({
|
|
28284
|
-
user: { name, phone }
|
|
28285
|
-
status: WidgetStates.CONSULTATION
|
|
28350
|
+
user: { name, phone }
|
|
28286
28351
|
});
|
|
28352
|
+
this.triggerCallback("onConsultationStarted", { name, phone });
|
|
28353
|
+
store.setState({ status: WidgetStates.CONSULTATION });
|
|
28354
|
+
if (!this.livekit) {
|
|
28355
|
+
this.livekit = new LiveKitService();
|
|
28356
|
+
}
|
|
28357
|
+
const alreadyGranted = await this.livekit.checkPermissions();
|
|
28358
|
+
if (alreadyGranted) {
|
|
28359
|
+
console.log("[Widget] Permissions already granted, warming up...");
|
|
28360
|
+
await this.livekit.requestPermissions();
|
|
28361
|
+
if (this.videoToken) {
|
|
28362
|
+
this.livekit.publishLocalTracks();
|
|
28363
|
+
}
|
|
28364
|
+
} else {
|
|
28365
|
+
this.showPermissionModal(async () => {
|
|
28366
|
+
if (this.videoToken) {
|
|
28367
|
+
await this.livekit.publishLocalTracks();
|
|
28368
|
+
}
|
|
28369
|
+
});
|
|
28370
|
+
}
|
|
28287
28371
|
});
|
|
28288
28372
|
}
|
|
28289
28373
|
} else if (status === WidgetStates.CONSULTATION) {
|
|
@@ -28291,27 +28375,42 @@ class ClinicWidget {
|
|
|
28291
28375
|
const endBtn = this.shadowRoot.getElementById("td-consult-end");
|
|
28292
28376
|
if (endBtn) {
|
|
28293
28377
|
endBtn.addEventListener("click", () => {
|
|
28294
|
-
this.
|
|
28378
|
+
if (this.livekit) {
|
|
28379
|
+
try {
|
|
28380
|
+
this.livekit.disconnect();
|
|
28381
|
+
} catch (err) {
|
|
28382
|
+
console.error("[Widget] Error disconnecting LiveKit:", err);
|
|
28383
|
+
}
|
|
28384
|
+
this.livekit = null;
|
|
28385
|
+
}
|
|
28295
28386
|
store.setState({ status: WidgetStates.COMPLETED });
|
|
28296
28387
|
});
|
|
28297
28388
|
}
|
|
28298
|
-
const
|
|
28299
|
-
const
|
|
28300
|
-
|
|
28301
|
-
|
|
28302
|
-
|
|
28303
|
-
|
|
28304
|
-
|
|
28389
|
+
const micBtn = this.shadowRoot.getElementById("td-btn-mic");
|
|
28390
|
+
const camBtn = this.shadowRoot.getElementById("td-btn-cam");
|
|
28391
|
+
let isMicOn = true;
|
|
28392
|
+
let isCamOn = true;
|
|
28393
|
+
const updateMediaBtn = (btn, isOn, iconOn, iconOff) => {
|
|
28394
|
+
if (!btn) return;
|
|
28395
|
+
btn.style.backgroundColor = isOn ? "rgba(255, 255, 255, 0.2)" : "#ef4444";
|
|
28396
|
+
btn.innerHTML = `<span class="material-symbols-outlined">${isOn ? iconOn : iconOff}</span>`;
|
|
28305
28397
|
};
|
|
28306
|
-
if (
|
|
28307
|
-
|
|
28308
|
-
|
|
28309
|
-
|
|
28310
|
-
|
|
28311
|
-
if (e2.key === "Enter" && !e2.shiftKey) {
|
|
28312
|
-
e2.preventDefault();
|
|
28313
|
-
sendMessage();
|
|
28398
|
+
if (micBtn) {
|
|
28399
|
+
micBtn.addEventListener("click", () => {
|
|
28400
|
+
isMicOn = !isMicOn;
|
|
28401
|
+
if (this.livekit) {
|
|
28402
|
+
this.livekit.toggleMicrophone(isMicOn);
|
|
28314
28403
|
}
|
|
28404
|
+
updateMediaBtn(micBtn, isMicOn, "mic", "mic_off");
|
|
28405
|
+
});
|
|
28406
|
+
}
|
|
28407
|
+
if (camBtn) {
|
|
28408
|
+
camBtn.addEventListener("click", () => {
|
|
28409
|
+
isCamOn = !isCamOn;
|
|
28410
|
+
if (this.livekit) {
|
|
28411
|
+
this.livekit.toggleCamera(isCamOn);
|
|
28412
|
+
}
|
|
28413
|
+
updateMediaBtn(camBtn, isCamOn, "videocam", "videocam_off");
|
|
28315
28414
|
});
|
|
28316
28415
|
}
|
|
28317
28416
|
} else if (status === WidgetStates.COMPLETED) {
|
|
@@ -28321,6 +28420,14 @@ class ClinicWidget {
|
|
|
28321
28420
|
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28322
28421
|
});
|
|
28323
28422
|
}
|
|
28423
|
+
const overlay = this.shadowRoot.querySelector(".td-overlay-wrapper");
|
|
28424
|
+
if (overlay) {
|
|
28425
|
+
overlay.addEventListener("click", (e2) => {
|
|
28426
|
+
if (e2.target === overlay) {
|
|
28427
|
+
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28428
|
+
}
|
|
28429
|
+
});
|
|
28430
|
+
}
|
|
28324
28431
|
}
|
|
28325
28432
|
}
|
|
28326
28433
|
triggerCallback(name, data) {
|
|
@@ -28353,7 +28460,9 @@ class ClinicWidget {
|
|
|
28353
28460
|
const btn = el.querySelector("#td-inline-btn");
|
|
28354
28461
|
if (btn) {
|
|
28355
28462
|
btn.addEventListener("click", () => {
|
|
28356
|
-
|
|
28463
|
+
btn.addEventListener("click", () => {
|
|
28464
|
+
store.setState({ status: WidgetStates.PATIENT_FORM });
|
|
28465
|
+
});
|
|
28357
28466
|
});
|
|
28358
28467
|
}
|
|
28359
28468
|
} else {
|
|
@@ -28394,26 +28503,50 @@ class ClinicWidget {
|
|
|
28394
28503
|
store.setState({ doctorOnline: isOnline });
|
|
28395
28504
|
}
|
|
28396
28505
|
});
|
|
28397
|
-
this.socket.on("customer:token", (data) => {
|
|
28506
|
+
this.socket.on("customer:token", async (data) => {
|
|
28398
28507
|
console.log("Received token from server:", data);
|
|
28399
28508
|
if (data.token) {
|
|
28400
28509
|
if (!this.livekit) {
|
|
28401
28510
|
this.livekit = new LiveKitService();
|
|
28402
28511
|
}
|
|
28403
|
-
|
|
28404
|
-
|
|
28405
|
-
console.log("[Widget] LiveKit Params:", { url: CONFIG.LIVEKIT_URL, token: data.token });
|
|
28406
|
-
this.livekit.connect(CONFIG.LIVEKIT_URL, data.token, modal);
|
|
28407
|
-
} else {
|
|
28408
|
-
console.error("Consultation modal not found for video rendering");
|
|
28409
|
-
}
|
|
28512
|
+
this.videoToken = data.token;
|
|
28513
|
+
this.attemptVideoConnection();
|
|
28410
28514
|
}
|
|
28411
28515
|
});
|
|
28412
28516
|
}
|
|
28517
|
+
async attemptVideoConnection() {
|
|
28518
|
+
if (!this.videoToken || !this.livekit) {
|
|
28519
|
+
return;
|
|
28520
|
+
}
|
|
28521
|
+
const modal = this.shadowRoot.querySelector(".td-consultation-modal");
|
|
28522
|
+
if (!modal) {
|
|
28523
|
+
console.error("[Widget] Modal not found, retrying...");
|
|
28524
|
+
setTimeout(() => this.attemptVideoConnection(), 500);
|
|
28525
|
+
return;
|
|
28526
|
+
}
|
|
28527
|
+
if (!this.livekit.room || this.livekit.room.state === "disconnected") {
|
|
28528
|
+
console.log("[Widget] Connecting to LiveKit Room immediately...");
|
|
28529
|
+
this.livekit.connect(CONFIG.LIVEKIT_URL, this.videoToken, modal);
|
|
28530
|
+
}
|
|
28531
|
+
this.ensureLocalMedia();
|
|
28532
|
+
}
|
|
28533
|
+
async ensureLocalMedia() {
|
|
28534
|
+
const granted = await this.livekit.checkPermissions();
|
|
28535
|
+
if (granted) {
|
|
28536
|
+
console.log("[Widget] Permissions OK, publishing tracks...");
|
|
28537
|
+
await this.livekit.publishLocalTracks();
|
|
28538
|
+
} else {
|
|
28539
|
+
console.log("[Widget] Permissions missing, showing modal...");
|
|
28540
|
+
this.showPermissionModal(async () => {
|
|
28541
|
+
await this.livekit.publishLocalTracks();
|
|
28542
|
+
});
|
|
28543
|
+
}
|
|
28544
|
+
}
|
|
28413
28545
|
joinVideoCall() {
|
|
28414
28546
|
const state = store.getState();
|
|
28415
28547
|
const { user, doctor } = state;
|
|
28416
28548
|
const userId = "20.183299.4158";
|
|
28549
|
+
this.videoToken = null;
|
|
28417
28550
|
if (this.socket) {
|
|
28418
28551
|
console.log("Joining video call room with user:", user == null ? void 0 : user.name);
|
|
28419
28552
|
this.socket.emit("register", {
|
|
@@ -28421,12 +28554,16 @@ class ClinicWidget {
|
|
|
28421
28554
|
userName: user == null ? void 0 : user.name,
|
|
28422
28555
|
role: "customer"
|
|
28423
28556
|
});
|
|
28424
|
-
|
|
28557
|
+
const joinPayload = {
|
|
28425
28558
|
participantId: userId,
|
|
28426
28559
|
participantName: user.name,
|
|
28427
28560
|
doctorId: doctor.id,
|
|
28561
|
+
productId: "147365",
|
|
28562
|
+
// Hardcoded as requested
|
|
28428
28563
|
type: "video"
|
|
28429
|
-
}
|
|
28564
|
+
};
|
|
28565
|
+
console.log("[Widget] Emitting customer:join with payload:", joinPayload);
|
|
28566
|
+
this.socket.emit("customer:join", joinPayload);
|
|
28430
28567
|
} else {
|
|
28431
28568
|
console.error("Socket not connected, cannot join video call");
|
|
28432
28569
|
}
|
|
@@ -28454,6 +28591,35 @@ class ClinicWidget {
|
|
|
28454
28591
|
this.socket = null;
|
|
28455
28592
|
}
|
|
28456
28593
|
}
|
|
28594
|
+
showPermissionModal(onSuccessCallback) {
|
|
28595
|
+
if (this.shadowRoot.getElementById("td-permission-modal")) return;
|
|
28596
|
+
const state = store.getState();
|
|
28597
|
+
const div = document.createElement("div");
|
|
28598
|
+
div.innerHTML = getPermissionModalHtml(state);
|
|
28599
|
+
Array.from(div.children).forEach((child) => {
|
|
28600
|
+
this.shadowRoot.appendChild(child);
|
|
28601
|
+
});
|
|
28602
|
+
const modalEl = this.shadowRoot.getElementById("td-permission-modal");
|
|
28603
|
+
const closeBtn = modalEl.querySelector("#td-perm-close");
|
|
28604
|
+
const cancelBtn = modalEl.querySelector("#td-perm-cancel");
|
|
28605
|
+
const allowBtn = modalEl.querySelector("#td-perm-allow");
|
|
28606
|
+
const closeModal = () => {
|
|
28607
|
+
modalEl.remove();
|
|
28608
|
+
};
|
|
28609
|
+
const onAllow = async () => {
|
|
28610
|
+
closeModal();
|
|
28611
|
+
if (!this.livekit) {
|
|
28612
|
+
this.livekit = new LiveKitService();
|
|
28613
|
+
}
|
|
28614
|
+
await this.livekit.requestPermissions();
|
|
28615
|
+
if (typeof onSuccessCallback === "function") {
|
|
28616
|
+
onSuccessCallback();
|
|
28617
|
+
}
|
|
28618
|
+
};
|
|
28619
|
+
if (closeBtn) closeBtn.addEventListener("click", closeModal);
|
|
28620
|
+
if (cancelBtn) cancelBtn.addEventListener("click", closeModal);
|
|
28621
|
+
if (allowBtn) allowBtn.addEventListener("click", onAllow);
|
|
28622
|
+
}
|
|
28457
28623
|
}
|
|
28458
28624
|
(function() {
|
|
28459
28625
|
const currentScript = document.querySelector("script[data-widget-id]");
|