clinic-connect-widget 1.0.3 → 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 +310 -234
- 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
|
|
@@ -124,63 +124,65 @@ const collapsedHtml = `<div class="td-widget-fab" id="td-widget-fab">\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
|
-
|
|
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
|
|
305
260
|
</div>\r
|
|
306
|
-
<div style="margin-left: auto; color: var(--td-primary);">\r
|
|
307
|
-
<span class="material-symbols-outlined">verified_user</span>\r
|
|
308
|
-
</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
|
|
@@ -28019,29 +27972,44 @@ class LiveKitService {
|
|
|
28019
27972
|
constructor() {
|
|
28020
27973
|
this.room = null;
|
|
28021
27974
|
this.wrapper = null;
|
|
28022
|
-
this.
|
|
27975
|
+
this.isPermissionGranted = false;
|
|
27976
|
+
this.permissionPromise = null;
|
|
28023
27977
|
}
|
|
28024
27978
|
/**
|
|
28025
27979
|
* Request Camera & Mic permissions early
|
|
28026
27980
|
*/
|
|
28027
27981
|
async requestPermissions() {
|
|
28028
|
-
|
|
28029
|
-
console.log("[LiveKit]
|
|
28030
|
-
|
|
28031
|
-
this.preAcquiredTracks = stream.getTracks();
|
|
28032
|
-
console.log("[LiveKit] Permissions granted, tracks cached:", this.preAcquiredTracks.length);
|
|
28033
|
-
return true;
|
|
28034
|
-
} catch (error) {
|
|
28035
|
-
console.error("[LiveKit] Permission request failed:", error);
|
|
28036
|
-
console.log(`Không thể xin quyền: ${error.name} - ${error.message}`);
|
|
28037
|
-
return false;
|
|
27982
|
+
if (this.permissionPromise) {
|
|
27983
|
+
console.log("[LiveKit] Joining existing permission request...");
|
|
27984
|
+
return this.permissionPromise;
|
|
28038
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;
|
|
28039
28003
|
}
|
|
28040
28004
|
/**
|
|
28041
28005
|
* Check if permissions are already granted
|
|
28042
28006
|
* @returns {Promise<boolean>}
|
|
28043
28007
|
*/
|
|
28044
28008
|
async checkPermissions() {
|
|
28009
|
+
if (this.isPermissionGranted) {
|
|
28010
|
+
console.log("[LiveKit] Permission flag is true.");
|
|
28011
|
+
return true;
|
|
28012
|
+
}
|
|
28045
28013
|
if (!navigator.permissions || !navigator.permissions.query) {
|
|
28046
28014
|
return false;
|
|
28047
28015
|
}
|
|
@@ -28079,7 +28047,6 @@ class LiveKitService {
|
|
|
28079
28047
|
try {
|
|
28080
28048
|
await this.room.connect(url2, token);
|
|
28081
28049
|
console.log("[LiveKit] Connected to room:", this.room.name);
|
|
28082
|
-
await this.publishLocalTracks();
|
|
28083
28050
|
} catch (error) {
|
|
28084
28051
|
console.error("[LiveKit] Connection failed:", error);
|
|
28085
28052
|
}
|
|
@@ -28101,23 +28068,9 @@ class LiveKitService {
|
|
|
28101
28068
|
async publishLocalTracks() {
|
|
28102
28069
|
const localContainer = this.wrapper.querySelector(".td-pip");
|
|
28103
28070
|
try {
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
const videoTrack = this.preAcquiredTracks.find((t) => t.kind === "video");
|
|
28108
|
-
const audioTrack = this.preAcquiredTracks.find((t) => t.kind === "audio");
|
|
28109
|
-
if (videoTrack) {
|
|
28110
|
-
videoPub = await this.room.localParticipant.publishTrack(videoTrack, { source: "camera" });
|
|
28111
|
-
}
|
|
28112
|
-
if (audioTrack) {
|
|
28113
|
-
await this.room.localParticipant.publishTrack(audioTrack, { source: "microphone" });
|
|
28114
|
-
}
|
|
28115
|
-
this.preAcquiredTracks = [];
|
|
28116
|
-
} else {
|
|
28117
|
-
await this.room.localParticipant.setCameraEnabled(true);
|
|
28118
|
-
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
28119
|
-
videoPub = Array.from(this.room.localParticipant.videoTrackPublications.values()).find((p) => p.source === "camera");
|
|
28120
|
-
}
|
|
28071
|
+
await this.room.localParticipant.setCameraEnabled(true);
|
|
28072
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
28073
|
+
const videoPub = Array.from(this.room.localParticipant.videoTrackPublications.values()).find((p) => p.source === "camera");
|
|
28121
28074
|
if (videoPub && videoPub.track && localContainer) {
|
|
28122
28075
|
localContainer.innerHTML = "";
|
|
28123
28076
|
localContainer.style.backgroundImage = "none";
|
|
@@ -28134,9 +28087,9 @@ class LiveKitService {
|
|
|
28134
28087
|
} catch (e2) {
|
|
28135
28088
|
console.error("[LiveKit] Failed to publish tracks:", e2);
|
|
28136
28089
|
if (e2.name === "NotAllowedError") {
|
|
28137
|
-
|
|
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ư.");
|
|
28138
28091
|
} else {
|
|
28139
|
-
|
|
28092
|
+
console.log(`Lỗi Camera: ${e2.message}`);
|
|
28140
28093
|
}
|
|
28141
28094
|
}
|
|
28142
28095
|
}
|
|
@@ -28164,14 +28117,66 @@ class LiveKitService {
|
|
|
28164
28117
|
console.log("[LiveKit] Remote audio attached");
|
|
28165
28118
|
}
|
|
28166
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
|
+
}
|
|
28167
28132
|
handleTrackUnsubscribed(track, participant) {
|
|
28168
28133
|
track.detach().forEach((element) => element.remove());
|
|
28169
28134
|
}
|
|
28170
28135
|
disconnect() {
|
|
28136
|
+
var _a, _b;
|
|
28171
28137
|
if (this.room) {
|
|
28172
|
-
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
|
+
}
|
|
28173
28160
|
this.room = null;
|
|
28174
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
|
+
}
|
|
28175
28180
|
}
|
|
28176
28181
|
}
|
|
28177
28182
|
class ClinicWidget {
|
|
@@ -28187,9 +28192,11 @@ class ClinicWidget {
|
|
|
28187
28192
|
}
|
|
28188
28193
|
async init() {
|
|
28189
28194
|
var _a;
|
|
28195
|
+
this.videoToken = null;
|
|
28190
28196
|
this.mount();
|
|
28191
28197
|
this.unsubscribe = store.on(EventTypes.STATE_CHANGE, this.handleStateChange.bind(this));
|
|
28192
28198
|
store.setState({ status: WidgetStates.LOADING });
|
|
28199
|
+
this.startSocket();
|
|
28193
28200
|
try {
|
|
28194
28201
|
const response = await this.api.init(this.config.widgetId);
|
|
28195
28202
|
if (response.success) {
|
|
@@ -28208,7 +28215,6 @@ class ClinicWidget {
|
|
|
28208
28215
|
// Default open state
|
|
28209
28216
|
});
|
|
28210
28217
|
this.checkInlineTrigger();
|
|
28211
|
-
this.startSocket();
|
|
28212
28218
|
} else {
|
|
28213
28219
|
store.setState({ status: WidgetStates.IDLE, error: "Init failed" });
|
|
28214
28220
|
}
|
|
@@ -28303,6 +28309,14 @@ class ClinicWidget {
|
|
|
28303
28309
|
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28304
28310
|
});
|
|
28305
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
|
+
}
|
|
28306
28320
|
if (consultBtn) {
|
|
28307
28321
|
consultBtn.addEventListener("click", () => {
|
|
28308
28322
|
consultBtn.addEventListener("click", () => {
|
|
@@ -28318,6 +28332,14 @@ class ClinicWidget {
|
|
|
28318
28332
|
store.setState({ status: WidgetStates.EXPANDED });
|
|
28319
28333
|
});
|
|
28320
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
|
+
}
|
|
28321
28343
|
if (form) {
|
|
28322
28344
|
form.addEventListener("submit", async (e2) => {
|
|
28323
28345
|
e2.preventDefault();
|
|
@@ -28329,6 +28351,23 @@ class ClinicWidget {
|
|
|
28329
28351
|
});
|
|
28330
28352
|
this.triggerCallback("onConsultationStarted", { name, phone });
|
|
28331
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
|
+
}
|
|
28332
28371
|
});
|
|
28333
28372
|
}
|
|
28334
28373
|
} else if (status === WidgetStates.CONSULTATION) {
|
|
@@ -28337,28 +28376,41 @@ class ClinicWidget {
|
|
|
28337
28376
|
if (endBtn) {
|
|
28338
28377
|
endBtn.addEventListener("click", () => {
|
|
28339
28378
|
if (this.livekit) {
|
|
28340
|
-
|
|
28379
|
+
try {
|
|
28380
|
+
this.livekit.disconnect();
|
|
28381
|
+
} catch (err) {
|
|
28382
|
+
console.error("[Widget] Error disconnecting LiveKit:", err);
|
|
28383
|
+
}
|
|
28384
|
+
this.livekit = null;
|
|
28341
28385
|
}
|
|
28342
28386
|
store.setState({ status: WidgetStates.COMPLETED });
|
|
28343
28387
|
});
|
|
28344
28388
|
}
|
|
28345
|
-
const
|
|
28346
|
-
const
|
|
28347
|
-
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
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>`;
|
|
28352
28397
|
};
|
|
28353
|
-
if (
|
|
28354
|
-
|
|
28355
|
-
|
|
28356
|
-
|
|
28357
|
-
|
|
28358
|
-
if (e2.key === "Enter" && !e2.shiftKey) {
|
|
28359
|
-
e2.preventDefault();
|
|
28360
|
-
sendMessage();
|
|
28398
|
+
if (micBtn) {
|
|
28399
|
+
micBtn.addEventListener("click", () => {
|
|
28400
|
+
isMicOn = !isMicOn;
|
|
28401
|
+
if (this.livekit) {
|
|
28402
|
+
this.livekit.toggleMicrophone(isMicOn);
|
|
28361
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");
|
|
28362
28414
|
});
|
|
28363
28415
|
}
|
|
28364
28416
|
} else if (status === WidgetStates.COMPLETED) {
|
|
@@ -28368,6 +28420,14 @@ class ClinicWidget {
|
|
|
28368
28420
|
store.setState({ status: WidgetStates.COLLAPSED });
|
|
28369
28421
|
});
|
|
28370
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
|
+
}
|
|
28371
28431
|
}
|
|
28372
28432
|
}
|
|
28373
28433
|
triggerCallback(name, data) {
|
|
@@ -28449,28 +28509,44 @@ class ClinicWidget {
|
|
|
28449
28509
|
if (!this.livekit) {
|
|
28450
28510
|
this.livekit = new LiveKitService();
|
|
28451
28511
|
}
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
if (modal) {
|
|
28455
|
-
console.log("[Widget] LiveKit Params:", { url: CONFIG.LIVEKIT_URL, token: data.token });
|
|
28456
|
-
this.livekit.connect(CONFIG.LIVEKIT_URL, data.token, modal);
|
|
28457
|
-
} else {
|
|
28458
|
-
console.error("Consultation modal not found for video rendering");
|
|
28459
|
-
}
|
|
28460
|
-
};
|
|
28461
|
-
const granted = await this.livekit.checkPermissions();
|
|
28462
|
-
if (granted) {
|
|
28463
|
-
connectLiveKit();
|
|
28464
|
-
} else {
|
|
28465
|
-
this.showPermissionModal(connectLiveKit);
|
|
28466
|
-
}
|
|
28512
|
+
this.videoToken = data.token;
|
|
28513
|
+
this.attemptVideoConnection();
|
|
28467
28514
|
}
|
|
28468
28515
|
});
|
|
28469
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
|
+
}
|
|
28470
28545
|
joinVideoCall() {
|
|
28471
28546
|
const state = store.getState();
|
|
28472
28547
|
const { user, doctor } = state;
|
|
28473
28548
|
const userId = "20.183299.4158";
|
|
28549
|
+
this.videoToken = null;
|
|
28474
28550
|
if (this.socket) {
|
|
28475
28551
|
console.log("Joining video call room with user:", user == null ? void 0 : user.name);
|
|
28476
28552
|
this.socket.emit("register", {
|
|
@@ -28478,12 +28554,16 @@ class ClinicWidget {
|
|
|
28478
28554
|
userName: user == null ? void 0 : user.name,
|
|
28479
28555
|
role: "customer"
|
|
28480
28556
|
});
|
|
28481
|
-
|
|
28557
|
+
const joinPayload = {
|
|
28482
28558
|
participantId: userId,
|
|
28483
28559
|
participantName: user.name,
|
|
28484
28560
|
doctorId: doctor.id,
|
|
28561
|
+
productId: "147365",
|
|
28562
|
+
// Hardcoded as requested
|
|
28485
28563
|
type: "video"
|
|
28486
|
-
}
|
|
28564
|
+
};
|
|
28565
|
+
console.log("[Widget] Emitting customer:join with payload:", joinPayload);
|
|
28566
|
+
this.socket.emit("customer:join", joinPayload);
|
|
28487
28567
|
} else {
|
|
28488
28568
|
console.error("Socket not connected, cannot join video call");
|
|
28489
28569
|
}
|
|
@@ -28531,13 +28611,9 @@ class ClinicWidget {
|
|
|
28531
28611
|
if (!this.livekit) {
|
|
28532
28612
|
this.livekit = new LiveKitService();
|
|
28533
28613
|
}
|
|
28534
|
-
|
|
28535
|
-
if (
|
|
28536
|
-
|
|
28537
|
-
onSuccessCallback();
|
|
28538
|
-
}
|
|
28539
|
-
} else {
|
|
28540
|
-
console.warn("User denied permissions via browser prompt");
|
|
28614
|
+
await this.livekit.requestPermissions();
|
|
28615
|
+
if (typeof onSuccessCallback === "function") {
|
|
28616
|
+
onSuccessCallback();
|
|
28541
28617
|
}
|
|
28542
28618
|
};
|
|
28543
28619
|
if (closeBtn) closeBtn.addEventListener("click", closeModal);
|