Spaces:
Running
Running
| <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>مساعد صوتي ذكي - الميكروفون يعمل باستمرار</title> | |
| <style> | |
| * { margin:0; padding:0; box-sizing:border-box; font-family:'Segoe UI','Noto Sans Arabic',sans-serif; } | |
| body { background:#fff; display:flex; justify-content:center; align-items:center; min-height:100vh; flex-direction:column; color: rgb(11, 186, 131); transition: all 0.3s ease; overflow: hidden; position: relative; } | |
| body.night-mode { background: #1a202c; color: rgb(11, 186, 131); } | |
| .background-effect { | |
| position: absolute; top: 0; left: 0; width: 100%; height: 100%; | |
| z-index: -1; opacity: 0.1; | |
| background: radial-gradient(circle at 20% 50%, rgba(11, 186, 131, 0.4) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 80%, rgba(11, 186, 131, 0.3) 0%, transparent 40%); | |
| animation: backgroundMove 20s infinite alternate ease-in-out; | |
| } | |
| @keyframes backgroundMove { | |
| 0% { transform: scale(1) rotate(0deg); } | |
| 100% { transform: scale(1.2) rotate(5deg); } | |
| } | |
| .night-mode .background-effect { | |
| background: radial-gradient(circle at 20% 50%, rgba(11, 186, 131, 0.2) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 80%, rgba(11, 186, 131, 0.15) 0%, transparent 40%); | |
| } | |
| .circle-outer { | |
| width:220px; height:220px; border-radius:50%; | |
| background: rgba(11, 186, 131, 0.15); | |
| display:flex; justify-content:center; align-items:center; | |
| cursor:pointer; transition: all 0.3s ease; | |
| box-shadow: 0 15px 30px rgba(0,0,0,0.1); | |
| position: relative; overflow:hidden; | |
| animation: subtlePulse 3s infinite ease-in-out; | |
| z-index: 10; | |
| } | |
| @keyframes subtlePulse { | |
| 0% { transform: scale(1); box-shadow: 0 15px 30px rgba(0,0,0,0.1); } | |
| 50% { transform: scale(1.02); box-shadow: 0 20px 40px rgba(0,0,0,0.15); } | |
| 100% { transform: scale(1); box-shadow: 0 15px 30px rgba(0,0,0,0.1); } | |
| } | |
| .night-mode .circle-outer { | |
| background: rgba(11, 186, 131, 0.15); | |
| box-shadow: 0 15px 30px rgba(0,0,0,0.3); | |
| animation: subtlePulseNight 3s infinite ease-in-out; | |
| } | |
| @keyframes subtlePulseNight { | |
| 0% { transform: scale(1); box-shadow: 0 15px 30px rgba(0,0,0,0.3); } | |
| 50% { transform: scale(1.02); box-shadow: 0 20px 40px rgba(0,0,0,0.4); } | |
| 100% { transform: scale(1); box-shadow: 0 15px 30px rgba(0,0,0,0.3); } | |
| } | |
| .circle-middle { | |
| width:180px; height:180px; border-radius:50%; | |
| background: rgba(11, 186, 131, 0.25); | |
| display:flex; justify-content:center; align-items:center; | |
| transition: all 0.3s ease; | |
| animation: middlePulse 4s infinite ease-in-out; | |
| } | |
| @keyframes middlePulse { | |
| 0% { transform: scale(1); background: rgba(11, 186, 131, 0.25); } | |
| 50% { transform: scale(1.03); background: rgba(11, 186, 131, 0.3); } | |
| 100% { transform: scale(1); background: rgba(11, 186, 131, 0.25); } | |
| } | |
| .night-mode .circle-middle { | |
| background: rgba(11, 186, 131, 0.25); | |
| animation: middlePulseNight 4s infinite ease-in-out; | |
| } | |
| @keyframes middlePulseNight { | |
| 0% { transform: scale(1); background: rgba(11, 186, 131, 0.25); } | |
| 50% { transform: scale(1.03); background: rgba(11, 186, 131, 0.3); } | |
| 100% { transform: scale(1); background: rgba(11, 186, 131, 0.25); } | |
| } | |
| .circle-inner { | |
| width:140px; height:140px; border-radius:50%; | |
| background:white; | |
| display:flex; justify-content:center; align-items:center; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| transition: all 0.3s ease; | |
| animation: innerGlow 5s infinite alternate; | |
| } | |
| @keyframes innerGlow { | |
| 0% { box-shadow: 0 5px 15px rgba(0,0,0,0.1); } | |
| 100% { box-shadow: 0 5px 25px rgba(11, 186, 131, 0.3); } | |
| } | |
| .night-mode .circle-inner { | |
| background: #2d3748; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.3); | |
| animation: innerGlowNight 5s infinite alternate; | |
| } | |
| @keyframes innerGlowNight { | |
| 0% { box-shadow: 0 5px 15px rgba(0,0,0,0.3); } | |
| 100% { box-shadow: 0 5px 25px rgba(11, 186, 131, 0.4); } | |
| } | |
| .mic-svg { | |
| width:60px; height:60px; fill: rgb(11, 186, 131); | |
| transition: all 0.3s ease; | |
| animation: iconFloat 6s infinite ease-in-out; | |
| } | |
| @keyframes iconFloat { | |
| 0% { transform: translateY(0) scale(1); } | |
| 50% { transform: translateY(-5px) scale(1.05); } | |
| 100% { transform: translateY(0) scale(1); } | |
| } | |
| .night-mode .mic-svg { fill: rgb(11, 186, 131); } | |
| .circle-outer.listening { | |
| animation: rotatePulse 1.5s infinite linear, colorShift 3s infinite alternate; | |
| } | |
| @keyframes rotatePulse { | |
| 0% { transform: rotate(0deg) scale(1); } | |
| 25% { transform: rotate(5deg) scale(1.05); } | |
| 50% { transform: rotate(0deg) scale(1.1); } | |
| 75% { transform: rotate(-5deg) scale(1.05); } | |
| 100% { transform: rotate(0deg) scale(1); } | |
| } | |
| @keyframes colorShift { | |
| 0% { background: rgba(11, 186, 131, 0.15); } | |
| 25% { background: rgba(11, 186, 131, 0.25); } | |
| 50% { background: rgba(11, 186, 131, 0.35); } | |
| 75% { background: rgba(11, 186, 131, 0.25); } | |
| 100% { background: rgba(11, 186, 131, 0.15); } | |
| } | |
| .night-mode .circle-outer.listening { | |
| animation: rotatePulse 1.5s infinite linear, colorShiftNight 3s infinite alternate; | |
| } | |
| @keyframes colorShiftNight { | |
| 0% { background: rgba(11, 186, 131, 0.15); } | |
| 25% { background: rgba(11, 186, 131, 0.25); } | |
| 50% { background: rgba(11, 186, 131, 0.35); } | |
| 75% { background: rgba(11, 186, 131, 0.25); } | |
| 100% { background: rgba(11, 186, 131, 0.15); } | |
| } | |
| .circle-outer.speaking .mic-svg { | |
| animation: speakIcon 0.8s infinite alternate, glowMic 1.2s infinite alternate; | |
| } | |
| @keyframes speakIcon { | |
| 0% { transform: translateY(0) scale(1); } | |
| 25% { transform: translateY(-8px) scale(1.1); } | |
| 50% { transform: translateY(0) scale(1); } | |
| 75% { transform: translateY(8px) scale(1.1); } | |
| 100% { transform: translateY(0) scale(1); } | |
| } | |
| @keyframes glowMic { | |
| 0% { filter: drop-shadow(0 0 0 rgba(11, 186, 131, 0.4)); } | |
| 50% { filter: drop-shadow(0 0 15px rgba(11, 186, 131, 0.8)); } | |
| 100% { filter: drop-shadow(0 0 0 rgba(11, 186, 131, 0.4)); } | |
| } | |
| .night-mode .circle-outer.speaking .mic-svg { | |
| animation: speakIcon 0.8s infinite alternate, glowMicNight 1.2s infinite alternate; | |
| } | |
| @keyframes glowMicNight { | |
| 0% { filter: drop-shadow(0 0 0 rgba(11, 186, 131, 0.4)); } | |
| 50% { filter: drop-shadow(0 0 15px rgba(11, 186, 131, 0.8)); } | |
| 100% { filter: drop-shadow(0 0 0 rgba(11, 186, 131, 0.4)); } | |
| } | |
| .pulse-wave { | |
| position:absolute; border:2px solid rgba(11, 186, 131, 0.5); | |
| border-radius:50%; width:220px; height:220px; | |
| top:0; left:0; | |
| animation: pulse 3s infinite ease-in-out; | |
| opacity: 0.7; | |
| } | |
| .pulse-wave:nth-child(2) { animation-delay: 1s; } | |
| .pulse-wave:nth-child(3) { animation-delay: 2s; } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); opacity:0.5; } | |
| 50% { transform: scale(1.3); opacity:0.2; } | |
| 100% { transform: scale(1.6); opacity:0; } | |
| } | |
| .night-mode .pulse-wave { border:2px solid rgba(11, 186, 131, 0.5); } | |
| .wave { | |
| display:flex; justify-content:center; align-items:flex-end; | |
| height:50px; margin-top:15px; | |
| } | |
| .wave span { | |
| display:inline-block; width:5px; height:25px; | |
| background: rgb(11, 186, 131); margin:0 4px; | |
| border-radius:3px; | |
| animation: wave 1.2s infinite ease-in-out; | |
| } | |
| .wave span:nth-child(2){animation-delay:0.1s;} | |
| .wave span:nth-child(3){animation-delay:0.2s;} | |
| .wave span:nth-child(4){animation-delay:0.3s;} | |
| .wave span:nth-child(5){animation-delay:0.4s;} | |
| @keyframes wave { | |
| 0%,40%,100% { transform: scaleY(0.6); } | |
| 20% { transform: scaleY(1.5); } | |
| } | |
| .night-mode .wave span { background: rgb(11, 186, 131); } | |
| .hidden { display:none; } | |
| .status { | |
| text-align:center; margin-top:20px; | |
| font-size:18px; color:#4a5568; | |
| transition: all 0.3s ease; | |
| min-height: 30px; padding: 0 10px; | |
| } | |
| .night-mode .status { color: #a0aec0; } | |
| .theme-toggle { | |
| position: absolute; top: 20px; right: 20px; | |
| background: rgba(11, 186, 131, 0.1); | |
| border: none; border-radius: 50%; | |
| width: 50px; height: 50px; | |
| cursor: pointer; font-size: 24px; | |
| display: flex; justify-content: center; align-items: center; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| animation: buttonPulse 4s infinite ease-in-out; | |
| z-index: 100; | |
| } | |
| @keyframes buttonPulse { | |
| 0% { transform: scale(1); box-shadow: 0 2px 10px rgba(0,0,0,0.1); } | |
| 50% { transform: scale(1.05); box-shadow: 0 4px 15px rgba(0,0,0,0.2); } | |
| 100% { transform: scale(1); box-shadow: 0 2px 10px rgba(0,0,0,0.1); } | |
| } | |
| .night-mode .theme-toggle { | |
| background: rgba(11, 186, 131, 0.1); | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.3); | |
| } | |
| .theme-toggle:hover { transform: scale(1.1); animation: none; } | |
| .browser-warning { | |
| position: fixed; bottom: 20px; left: 50%; | |
| transform: translateX(-50%); | |
| background: #ff4757; color: white; | |
| padding: 10px 20px; border-radius: 5px; | |
| font-size: 14px; display: none; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| z-index: 1000; text-align: center; max-width: 90%; | |
| } | |
| .permission-notice { | |
| position: fixed; bottom: 80px; left: 50%; | |
| transform: translateX(-50%); | |
| background: #ffa500; color: white; | |
| padding: 10px 20px; border-radius: 5px; | |
| font-size: 14px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| z-index: 1000; text-align: center; max-width: 90%; | |
| display: none; | |
| } | |
| @keyframes textPulse { | |
| 0% { opacity: 0.8; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.8; } | |
| } | |
| .listening-text { animation: textPulse 1.5s infinite; font-weight: bold; } | |
| ::-webkit-scrollbar { width: 8px; } | |
| ::-webkit-scrollbar-track { background: rgba(11, 186, 131, 0.1); border-radius: 10px; } | |
| ::-webkit-scrollbar-thumb { background: rgba(11, 186, 131, 0.3); border-radius: 10px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(11, 186, 131, 0.5); } | |
| .night-mode ::-webkit-scrollbar-track { background: rgba(11, 186, 131, 0.05); } | |
| .night-mode ::-webkit-scrollbar-thumb { background: rgba(11, 186, 131, 0.2); } | |
| .night-mode ::-webkit-scrollbar-thumb:hover { background: rgba(11, 186, 131, 0.4); } | |
| .settings-panel { | |
| position: absolute; top: 80px; right: 20px; | |
| background: white; border-radius: 10px; | |
| padding: 15px; | |
| box-shadow: 0 5px 20px rgba(0,0,0,0.1); | |
| display: none; z-index: 100; min-width: 250px; | |
| } | |
| .night-mode .settings-panel { | |
| background: #2d3748; | |
| box-shadow: 0 5px 20px rgba(0,0,0,0.3); | |
| } | |
| .settings-panel.show { display: block; } | |
| .setting-item { margin: 10px 0; text-align: right; } | |
| .setting-item label { display: block; margin-bottom: 5px; color: #4a5568; font-size: 14px; } | |
| .night-mode .setting-item label { color: #a0aec0; } | |
| .setting-item select, .setting-item input[type=range] { | |
| width: 100%; padding: 5px; border-radius: 5px; | |
| border: 1px solid #e2e8f0; | |
| } | |
| .night-mode .setting-item select, .night-mode .setting-item input[type=range] { | |
| background: #4a5568; border-color: #718096; color: white; | |
| } | |
| .speed-value { display: inline-block; margin-right: 10px; font-size: 14px; } | |
| .auto-restart { | |
| position: fixed; bottom: 20px; right: 20px; | |
| background: rgba(11, 186, 131, 0.1); | |
| border: none; border-radius: 50%; | |
| width: 40px; height: 40px; | |
| cursor: pointer; font-size: 16px; | |
| display: flex; justify-content: center; align-items: center; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| z-index: 100; | |
| } | |
| .night-mode .auto-restart { background: rgba(11, 186, 131, 0.1); } | |
| .auto-restart.active { background: rgba(11, 186, 131, 0.3); color: white; } | |
| .permission-manager { | |
| position: fixed; bottom: 70px; right: 20px; | |
| background: rgba(11, 186, 131, 0.1); | |
| border: none; border-radius: 50%; | |
| width: 40px; height: 40px; | |
| cursor: pointer; font-size: 16px; | |
| display: flex; justify-content: center; align-items: center; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| z-index: 100; | |
| } | |
| .night-mode .permission-manager { background: rgba(11, 186, 131, 0.1); } | |
| .permission-manager.active { background: rgba(11, 186, 131, 0.3); color: white; } | |
| .permission-panel { | |
| position: absolute; bottom: 120px; right: 20px; | |
| background: white; border-radius: 10px; | |
| padding: 15px; | |
| box-shadow: 0 5px 20px rgba(0,0,0,0.1); | |
| display: none; z-index: 100; min-width: 250px; | |
| } | |
| .night-mode .permission-panel { | |
| background: #2d3748; | |
| box-shadow: 0 5px 20px rgba(0,0,0,0.3); | |
| } | |
| .permission-panel.show { display: block; } | |
| .permission-status { | |
| padding: 8px 12px; | |
| border-radius: 5px; | |
| margin-bottom: 10px; | |
| font-size: 14px; | |
| text-align: center; | |
| } | |
| .permission-granted { background: rgba(34, 197, 94, 0.2); color: #16a34a; } | |
| .permission-denied { background: rgba(239, 68, 68, 0.2); color: #dc2626; } | |
| .permission-prompt { background: rgba(245, 158, 11, 0.2); color: #d97706; } | |
| .btn { | |
| padding: 8px 16px; | |
| border: none; border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: all 0.3s ease; | |
| margin: 5px 0; | |
| width: 100%; | |
| } | |
| .btn-primary { | |
| background: rgb(11, 186, 131); | |
| color: white; | |
| } | |
| .btn-secondary { | |
| background: rgba(11, 186, 131, 0.1); | |
| color: rgb(11, 186, 131); | |
| } | |
| .btn:hover { opacity: 0.9; transform: translateY(-2px); } | |
| .auto-mode-selector { | |
| display: flex; | |
| margin: 10px 0; | |
| border-radius: 5px; | |
| overflow: hidden; | |
| border: 1px solid #e2e8f0; | |
| } | |
| .night-mode .auto-mode-selector { border-color: #4a5568; } | |
| .auto-mode-option { | |
| flex: 1; | |
| padding: 8px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| font-size: 12px; | |
| } | |
| .auto-mode-option.active { | |
| background: rgb(11, 186, 131); | |
| color: white; | |
| } | |
| .permission-history { | |
| margin-top: 15px; | |
| max-height: 150px; | |
| overflow-y: auto; | |
| border-top: 1px solid #e2e8f0; | |
| padding-top: 10px; | |
| } | |
| .night-mode .permission-history { border-color: #4a5568; } | |
| .history-item { | |
| padding: 5px; | |
| font-size: 12px; | |
| border-bottom: 1px solid #f1f5f9; | |
| } | |
| .night-mode .history-item { border-color: #4a5568; } | |
| .history-item:last-child { border-bottom: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="background-effect"></div> | |
| <!-- أزرار التحكم العلوية --> | |
| <button class="theme-toggle" id="themeToggle">🌙</button> | |
| <!-- أزرار التحكم السفلية --> | |
| <button class="auto-restart" id="autoRestart" title="التشغيل التلقائي بعد الرد">🔄</button> | |
| <button class="permission-manager" id="permissionManager" title="إدارة أذونات الميكروفون">🎤</button> | |
| <!-- لوحة الإعدادات --> | |
| <div class="settings-panel" id="settingsPanel"> | |
| <div class="setting-item"> | |
| <label for="voiceSelect">نوع الصوت:</label> | |
| <select id="voiceSelect"> | |
| <option value="alloy">alloy</option> | |
| </select> | |
| </div> | |
| <div class="setting-item"> | |
| <label for="speedRange">سرعة الكلام:</label> | |
| <input type="range" id="speedRange" min="0.5" max="2.0" step="0.1" value="1"> | |
| <span class="speed-value" id="speedValue">1.0</span> | |
| </div> | |
| </div> | |
| <!-- لوحة إدارة الأذونات --> | |
| <div class="permission-panel" id="permissionPanel"> | |
| <div class="permission-status" id="permissionStatus"> | |
| جاري التحقق من حالة الأذونات... | |
| </div> | |
| <div class="auto-mode-selector"> | |
| <div class="auto-mode-option active" data-mode="auto">تلقائي</div> | |
| <div class="auto-mode-option" data-mode="manual">يدوي</div> | |
| <div class="auto-mode-option" data-mode="smart">ذكي</div> | |
| </div> | |
| <button class="btn btn-primary" id="requestPermission">طلب إذن الميكروفون</button> | |
| <button class="btn btn-secondary" id="resetPermissions">إعادة تعيين الأذونات</button> | |
| <div class="permission-history" id="permissionHistory"> | |
| <!-- سيتم ملء السجل هنا --> | |
| </div> | |
| </div> | |
| <!-- الدائرة الرئيسية --> | |
| <div class="circle-outer" id="micCircle"> | |
| <div class="pulse-wave"></div> | |
| <div class="pulse-wave"></div> | |
| <div class="pulse-wave"></div> | |
| <div class="circle-middle"> | |
| <div class="circle-inner"> | |
| <svg class="mic-svg" focusable="false" viewBox="0 0 24 24" aria-hidden="true" role="img"> | |
| <title>API</title> | |
| <path d="M0 0h24v24H0z" fill="none"></path> | |
| <path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22 .5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- حالة النظام --> | |
| <div class="status" id="status">جاري تهيئة النظام...</div> | |
| <div class="wave hidden" id="wave"> | |
| <span></span><span></span><span></span><span></span><span></span> | |
| </div> | |
| <!-- رسائل التنبيه --> | |
| <div class="browser-warning" id="browserWarning"> | |
| عذرًا، متصفحك الحالي لا يدعم خاصية التعرف على الصوت. يرجى استخدام Chrome أو Edge للحصول على أفضل تجربة. | |
| </div> | |
| <div class="permission-notice" id="permissionNotice"> | |
| يرجى السماح باستخدام الميكروفون للبدء | |
| </div> | |
| <script> | |
| // العناصر الرئيسية | |
| const micCircle = document.getElementById('micCircle'); | |
| const status = document.getElementById('status'); | |
| const wave = document.getElementById('wave'); | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const browserWarning = document.getElementById('browserWarning'); | |
| const permissionNotice = document.getElementById('permissionNotice'); | |
| const settingsPanel = document.getElementById('settingsPanel'); | |
| const speedRange = document.getElementById('speedRange'); | |
| const speedValue = document.getElementById('speedValue'); | |
| const autoRestart = document.getElementById('autoRestart'); | |
| const permissionManager = document.getElementById('permissionManager'); | |
| const permissionPanel = document.getElementById('permissionPanel'); | |
| const permissionStatus = document.getElementById('permissionStatus'); | |
| const requestPermission = document.getElementById('requestPermission'); | |
| const resetPermissions = document.getElementById('resetPermissions'); | |
| const permissionHistory = document.getElementById('permissionHistory'); | |
| const autoModeOptions = document.querySelectorAll('.auto-mode-option'); | |
| // المتغيرات العامة | |
| let isNightMode = localStorage.getItem('nightMode') === 'true'; | |
| let autoRestartEnabled = localStorage.getItem('autoRestart') === 'true'; | |
| let autoMode = localStorage.getItem('autoMode') || 'smart'; | |
| let permissionHistoryLog = JSON.parse(localStorage.getItem('permissionHistory') || '[]'); | |
| let isConversationActive = false; | |
| let currentAudio = null; | |
| let hasMicrophonePermission = false; | |
| let recognition = null; | |
| let isListening = false; | |
| let restartTimeout = null; | |
| // تهيئة النظام | |
| function initSystem() { | |
| loadSettings(); | |
| initEventListeners(); | |
| checkBrowserSupport(); | |
| updatePermissionStatus(); | |
| loadPermissionHistory(); | |
| // بدء التحقق من الأذونات بعد تهيئة النظام | |
| setTimeout(() => { | |
| checkMicrophonePermission(); | |
| }, 1000); | |
| } | |
| // تحميل الإعدادات | |
| function loadSettings() { | |
| // الوضع الليلي | |
| if (isNightMode) { | |
| document.body.classList.add('night-mode'); | |
| themeToggle.textContent = '☀️'; | |
| } | |
| // التشغيل التلقائي | |
| if (autoRestartEnabled) { | |
| autoRestart.classList.add('active'); | |
| } | |
| // وضع التشغيل التلقائي | |
| autoModeOptions.forEach(option => { | |
| if (option.dataset.mode === autoMode) { | |
| option.classList.add('active'); | |
| } else { | |
| option.classList.remove('active'); | |
| } | |
| }); | |
| // سرعة الكلام | |
| speedRange.addEventListener('input', () => { | |
| speedValue.textContent = speedRange.value; | |
| }); | |
| } | |
| // إعداد مستمعي الأحداث | |
| function initEventListeners() { | |
| // تبديل الوضع الليلي | |
| themeToggle.addEventListener('click', toggleNightMode); | |
| // إدارة الأذونات | |
| permissionManager.addEventListener('click', togglePermissionPanel); | |
| requestPermission.addEventListener('click', requestMicrophonePermission); | |
| resetPermissions.addEventListener('click', resetPermissionSettings); | |
| // أوضاع التشغيل التلقائي | |
| autoModeOptions.forEach(option => { | |
| option.addEventListener('click', () => { | |
| autoModeOptions.forEach(opt => opt.classList.remove('active')); | |
| option.classList.add('active'); | |
| autoMode = option.dataset.mode; | |
| localStorage.setItem('autoMode', autoMode); | |
| addToHistory(`تم تغيير وضع التشغيل إلى: ${getModeName(autoMode)}`); | |
| updateStatus(`وضع التشغيل: ${getModeName(autoMode)}`); | |
| }); | |
| }); | |
| // التشغيل التلقائي | |
| autoRestart.addEventListener('click', toggleAutoRestart); | |
| // إعدادات إضافية | |
| themeToggle.addEventListener('contextmenu', function(e) { | |
| e.preventDefault(); | |
| settingsPanel.classList.toggle('show'); | |
| }); | |
| // إغلاق اللوحات بالنقر خارجها | |
| document.addEventListener('click', function(e) { | |
| if (!settingsPanel.contains(e.target) && e.target !== themeToggle) { | |
| settingsPanel.classList.remove('show'); | |
| } | |
| if (!permissionPanel.contains(e.target) && e.target !== permissionManager) { | |
| permissionPanel.classList.remove('show'); | |
| } | |
| }); | |
| } | |
| // التحقق من دعم المتصفح | |
| function checkBrowserSupport() { | |
| if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) { | |
| status.textContent = "متصفحك لا يدعم التعرف على الصوت"; | |
| browserWarning.style.display = 'block'; | |
| return false; | |
| } | |
| if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { | |
| status.textContent = "متصفحك لا يدعم الوصول إلى الميكروفون"; | |
| browserWarning.style.display = 'block'; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // تهيئة نظام التعرف على الصوت | |
| function initSpeechRecognition() { | |
| if (!checkBrowserSupport()) return null; | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| recognition = new SpeechRecognition(); | |
| recognition.lang = 'ar-SA'; | |
| recognition.continuous = true; // التغيير الأساسي: جعل التعرف مستمرًا | |
| recognition.interimResults = false; | |
| recognition.maxAlternatives = 1; | |
| // إعداد مستمعي الأحداث | |
| recognition.onstart = handleRecognitionStart; | |
| recognition.onresult = handleRecognitionResult; | |
| recognition.onend = handleRecognitionEnd; | |
| recognition.onerror = handleRecognitionError; | |
| return recognition; | |
| } | |
| // دوال التعرف على الصوت | |
| function handleRecognitionStart() { | |
| isListening = true; | |
| setListeningUI(); | |
| addToHistory("بدء الاستماع..."); | |
| } | |
| let isPlay=false; | |
| async function handleRecognitionResult(event) { | |
| const transcript = event.results[event.results.length - 1][0].transcript; | |
| status.textContent = `تم الاستماع إلى: "${transcript}"`; | |
| status.classList.remove('listening-text'); | |
| if(!isPlay) | |
| addToHistory(`تم الاستماع: "${transcript}"`); | |
| else | |
| addToHistory('معلق الاستماع '); | |
| try { | |
| isListening = false; | |
| if(transcript!=""&&!isPlay){ | |
| isPlay=true; | |
| const reply = await getGPTResponse(transcript); | |
| await speakResponse(reply); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| setIdleUI("حدث خطأ في المعالجة"); | |
| isConversationActive = false; | |
| addToHistory("خطأ في المعالجة"); | |
| } | |
| } | |
| function handleRecognitionEnd() { | |
| isListening = false; | |
| addToHistory("انتهاء جلسة الاستماع"); | |
| // إعادة التشغيل التلقائي إذا كانت المحادثة لا تزال نشطة | |
| if (isConversationActive && !isListening) { | |
| setTimeout(() => { | |
| if (isConversationActive && !isListening) { | |
| //restartListening(); | |
| } | |
| }, 500); | |
| } else if (!isConversationActive) { | |
| setIdleUI("انقر على الدائرة لبدء المحادثة"); | |
| } | |
| } | |
| function handleRecognitionError(event) { | |
| console.error('خطأ في التعرف على الصوت:', event.error); | |
| isListening = false; | |
| addToHistory(`خطأ في التعرف: ${event.error}`); | |
| if (event.error === 'no-speech' && isConversationActive) { | |
| // لا داعي لإعادة التشغيل لأن التعرف مستمر | |
| status.textContent = "أستمع إليك..."; | |
| // restartListening(); | |
| } else if (event.error === 'network' || event.error === 'not-allowed') { | |
| // أخطاء تتطلب تدخل المستخدم | |
| setIdleUI("حدث خطأ: " + event.error); | |
| isConversationActive = false; | |
| } else { | |
| // إعادة التشغيل للأخطاء الأخرى | |
| setTimeout(() => { | |
| if (isConversationActive && !isListening) { | |
| restartListening(); | |
| } | |
| }, 1000); | |
| } | |
| } | |
| // دوال التحكم في الواجهة | |
| function setIdleUI(message = "انقر على الدائرة لبدء المحادثة") { | |
| status.textContent = message; | |
| status.classList.remove('listening-text'); | |
| micCircle.classList.remove('listening', 'speaking'); | |
| // wave.classList.add('hidden'); | |
| } | |
| function setListeningUI() { | |
| status.textContent = "أستمع إليك..."; | |
| status.classList.add('listening-text'); | |
| micCircle.classList.remove('speaking'); | |
| micCircle.classList.add('listening'); | |
| wave.classList.remove('hidden'); | |
| } | |
| function setSpeakingUI() { | |
| status.textContent = "جاري الرد..."; | |
| micCircle.classList.remove('listening'); | |
| micCircle.classList.add('speaking'); | |
| } | |
| function updateStatus(message) { | |
| status.textContent = message; | |
| setTimeout(() => { | |
| if (!isConversationActive) { | |
| status.textContent = "انقر على الدائرة لبدء المحادثة"; | |
| } | |
| }, 3000); | |
| } | |
| // دوال إدارة الأذونات | |
| async function checkMicrophonePermission() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| status.textContent = "تم السماح باستخدام الميكروفون ✓"; | |
| permissionNotice.style.display = 'none'; | |
| hasMicrophonePermission = true; | |
| // إغلاق الستريم فورًا بعد التحقق | |
| stream.getTracks().forEach(track => track.stop()); | |
| updatePermissionStatus(); | |
| addToHistory("تم منح إذن الميكروفون"); | |
| return true; | |
| } catch (error) { | |
| console.error('خطأ في إذن الميكروفون:', error); | |
| status.textContent = "انقر على الدائرة للبدء (سيطلب السماح للميكروفون)"; | |
| permissionNotice.style.display = 'block'; | |
| hasMicrophonePermission = false; | |
| updatePermissionStatus(); | |
| addToHistory("تم رفض إذن الميكروفون"); | |
| return false; | |
| } | |
| } | |
| async function requestMicrophonePermission() { | |
| addToHistory("طلب إذن الميكروفون..."); | |
| const granted = await checkMicrophonePermission(); | |
| if (granted) { | |
| initSpeechRecognition(); | |
| } | |
| } | |
| function updatePermissionStatus() { | |
| if (hasMicrophonePermission) { | |
| permissionStatus.textContent = "✅ تم منح إذن الميكروفون"; | |
| permissionStatus.className = "permission-status permission-granted"; | |
| } else { | |
| permissionStatus.textContent = "❌ لم يتم منح إذن الميكروفون"; | |
| permissionStatus.className = "permission-status permission-denied"; | |
| } | |
| } | |
| function resetPermissionSettings() { | |
| localStorage.removeItem('permissionHistory'); | |
| permissionHistoryLog = []; | |
| loadPermissionHistory(); | |
| addToHistory("تم إعادة تعيين إعدادات الأذونات"); | |
| updateStatus("تم إعادة تعيين الإعدادات"); | |
| } | |
| // دوال السجل | |
| function addToHistory(message) { | |
| const timestamp = new Date().toLocaleTimeString('ar-SA'); | |
| permissionHistoryLog.unshift({ timestamp, message }); | |
| // الحفاظ على آخر 10 أحداث فقط | |
| if (permissionHistoryLog.length > 10) { | |
| permissionHistoryLog = permissionHistoryLog.slice(0, 10); | |
| } | |
| localStorage.setItem('permissionHistory', JSON.stringify(permissionHistoryLog)); | |
| loadPermissionHistory(); | |
| } | |
| function loadPermissionHistory() { | |
| permissionHistory.innerHTML = ''; | |
| permissionHistoryLog.forEach(entry => { | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| item.textContent = `${entry.timestamp} - ${entry.message}`; | |
| permissionHistory.appendChild(item); | |
| }); | |
| } | |
| // دوال التحكم | |
| function toggleNightMode() { | |
| isNightMode = !isNightMode; | |
| document.body.classList.toggle('night-mode'); | |
| themeToggle.textContent = isNightMode ? '☀️' : '🌙'; | |
| localStorage.setItem('nightMode', isNightMode.toString()); | |
| addToHistory(`تم تغيير الوضع إلى: ${isNightMode ? 'ليلي' : 'نهاري'}`); | |
| } | |
| function toggleAutoRestart() { | |
| autoRestartEnabled = !autoRestartEnabled; | |
| autoRestart.classList.toggle('active'); | |
| localStorage.setItem('autoRestart', autoRestartEnabled.toString()); | |
| const message = autoRestartEnabled ? | |
| "تم تفعيل التشغيل التلقائي بعد الرد ✓" : | |
| "تم إيقاف التشغيل التلقائي"; | |
| updateStatus(message); | |
| addToHistory(message); | |
| } | |
| function togglePermissionPanel() { | |
| permissionPanel.classList.toggle('show'); | |
| updatePermissionStatus(); | |
| } | |
| function getModeName(mode) { | |
| const modes = { | |
| 'auto': 'تلقائي', | |
| 'manual': 'يدوي', | |
| 'smart': 'ذكي' | |
| }; | |
| return modes[mode] || mode; | |
| } | |
| // دوال المساعد (النطق والحصول على الرد) | |
| async function speakResponse(text) { | |
| setSpeakingUI(); | |
| addToHistory("جاري الرد الصوتي..."); | |
| try { | |
| const voice = document.getElementById("voiceSelect").value; | |
| const speed = parseFloat(document.getElementById("speedRange").value); | |
| const response = await fetch("https://lahja-dev-resource.cognitiveservices.azure.com/openai/deployments/LAHJA-V1/audio/speech?api-version=2025-03-01-preview", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": "Bearer 4AwsIf87cyBIgaJVsy0phWUQdZFcbrJxpQBDQNzL4xjcP2MFzrrYJQQJ99BIACHYHv6XJ3w3AAAAACOGYrzM" | |
| }, | |
| body: JSON.stringify({ | |
| model: "LAHJA-V1", | |
| input: text, | |
| voice: voice, | |
| speed: 0.75 | |
| }) | |
| }); | |
| if (!response.ok) throw new Error(`TTS error! status: ${response.status}`); | |
| const audioBlob = await response.blob(); | |
| const audioUrl = URL.createObjectURL(audioBlob); | |
| currentAudio = new Audio(audioUrl); | |
| currentAudio.play().catch(e => { | |
| console.error('Play error:', e); | |
| setIdleUI("خطأ في تشغيل الصوت"); | |
| isConversationActive = false; | |
| isPlay=false; | |
| addToHistory("خطأ في تشغيل الصوت"); | |
| }); | |
| currentAudio.onended = () => { | |
| currentAudio = null; | |
| isPlay=false; | |
| addToHistory("انتهاء الرد الصوتي"); | |
| // التحكم التلقائي حسب الوضع المحدد | |
| handleAutoRestart(); | |
| }; | |
| } catch (error) { | |
| console.error('Error in speakResponse:', error); | |
| setIdleUI("خطأ في تحويل النص إلى صوت"); | |
| isConversationActive = false; | |
| addToHistory("خطأ في تحويل النص إلى صوت"); | |
| } | |
| } | |
| function handleAutoRestart() { | |
| if (!isConversationActive) { | |
| setIdleUI("انقر على الدائرة لبدء المحادثة"); | |
| return; | |
| } | |
| // التحكم في إعادة التشغيل حسب الوضع | |
| switch (autoMode) { | |
| case 'auto': | |
| // إعادة تشغيل تلقائية فورية | |
| restartListening(); | |
| break; | |
| case 'smart': | |
| // إعادة تشغيل ذكية بعد تأخير | |
| setTimeout(() => { | |
| if (isConversationActive) { | |
| restartListening(); | |
| } | |
| }, 1000); | |
| break; | |
| case 'manual': | |
| // انتظار النقر اليدوي | |
| setIdleUI("انقر على الدائرة للاستماع مجددًا"); | |
| break; | |
| } | |
| } | |
| function restartListening() { | |
| if (isConversationActive && recognition && !isListening) { | |
| try { | |
| status.textContent = "أستمع إليك مجددًا..."; | |
| recognition.start(); | |
| addToHistory("إعادة تشغيل الاستماع تلقائيًا"); | |
| } catch(e) { | |
| console.error("خطأ في إعادة تشغيل التعرف:", e); | |
| // إعادة المحاولة بعد تأخير في حالة الخطأ | |
| // setTimeout(() => { | |
| // if (isConversationActive && !isListening) { | |
| // restartListening(); | |
| // } | |
| // }, 1000); | |
| } | |
| } | |
| } | |
| async function getGPTResponse(text) { | |
| try { | |
| status.textContent = 'جاري التواصل مع المساعد...'; | |
| addToHistory("جاري الحصول على الرد من المساعد..."); | |
| const response = await fetch("https://lahja-dev-resource.cognitiveservices.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2025-01-01-preview", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": "Bearer 4AwsIf87cyBIgaJVsy0phWUQdZFcbrJxpQBDQNzL4xjcP2MFzrrYJQQJ99BIACHYHv6XJ3w3AAAAACOGYrzM" | |
| }, | |
| body: JSON.stringify({ | |
| messages: [ | |
| { | |
| "role": "system", | |
| "content": "انت نموذج اسمه (لهجة) مطور من قبل شركة أسس الذكاء الرقمي. رد باللهجة النجدية بطريقة ودية وطبيعية في جميع المحادثات." | |
| }, | |
| { | |
| "role": "system", | |
| "content": "دائما اجابتك تكون باللهجة النجدية ودائما اجابتك تكون مختصره جدا لا تتجاوز سطر " | |
| } | |
| , | |
| { | |
| "role": "user", | |
| "content": text | |
| } | |
| ], | |
| max_tokens: 4096, | |
| temperature: 0.8, | |
| top_p: 1, | |
| model: "gpt-4o" | |
| }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | |
| const data = await response.json(); | |
| addToHistory("تم استلام الرد بنجاح"); | |
| return data.choices[0].message.content; | |
| } catch (error) { | |
| console.error('Error getting GPT response:', error); | |
| addToHistory("خطأ في الحصول على الرد من المساعد"); | |
| return "الله يوفقك يا طيب! والله ما قدرت أفهم كلامك تمام، حاول مرة ثانية وخذ راحتك بالكلام."; | |
| } | |
| } | |
| // النقر على الدائرة الرئيسية | |
| micCircle.addEventListener('click', async function() { | |
| if (!isConversationActive) { | |
| // التحقق من الإذن أولاً | |
| if (!hasMicrophonePermission) { | |
| hasMicrophonePermission = await checkMicrophonePermission(); | |
| if (!hasMicrophonePermission) { | |
| status.textContent = "انقر مرة أخرى بعد السماح للميكروفون"; | |
| return; | |
| } | |
| } | |
| // تهيئة نظام التعرف على الصوت إذا لم يكن معديًا | |
| if (!recognition) { | |
| recognition = initSpeechRecognition(); | |
| if (!recognition) return; | |
| } | |
| // بدء المحادثة | |
| isConversationActive = true; | |
| try { | |
| recognition.start(); | |
| addToHistory("بدء المحادثة - الميكروفون يعمل باستمرار"); | |
| } catch (e) { | |
| console.error("خطأ في بدء التعرف على الصوت:", e); | |
| setIdleUI("حدث خطأ عند البدء"); | |
| isConversationActive = false; | |
| addToHistory("خطأ في بدء المحادثة"); | |
| } | |
| } else { | |
| // إيقاف المحادثة | |
| isConversationActive = false; | |
| if (recognition) { | |
| recognition.stop(); | |
| } | |
| if (currentAudio) { | |
| currentAudio.pause(); | |
| } | |
| setIdleUI("تم إيقاف المحادثة. انقر للبدء."); | |
| addToHistory("إيقاف المحادثة"); | |
| } | |
| }); | |
| // بدء تشغيل النظام | |
| document.addEventListener('DOMContentLoaded', initSystem); | |
| </script> | |
| </body> | |
| </html> |