الانتقال إلى المحتوى الرئيسي

دورة حياة تراكب الصوت (macOS)

الجمهور: مساهمو تطبيق macOS. الهدف: الحفاظ على قابلية التنبؤ بتراكب الصوت عند تداخل كلمة التنبيه والضغط للتحدث.

القصد الحالي

  • إذا كان التراكب مرئيًا بالفعل بسبب كلمة التنبيه وضغط المستخدم مفتاح الاختصار، فإن جلسة مفتاح الاختصار «تتبنّى» النص الموجود بدل إعادة تعيينه. يبقى التراكب ظاهرًا طوال فترة الضغط على المفتاح. عند الإفلات: يتم الإرسال إذا وُجد نص مُشذَّب، وإلا يتم الإخفاء.
  • كلمة التنبيه وحدها تستمر في الإرسال التلقائي عند الصمت؛ أمّا الضغط للتحدث فيُرسل فور الإفلات.

المُنفَّذ (9 ديسمبر 2025)

  • تحمل جلسات التراكب الآن رمزًا مميِّزًا لكل التقاط (كلمة تنبيه أو ضغط للتحدث). يتم إسقاط تحديثات الجزئي/النهائي/الإرسال/الإخفاء/المستوى عندما لا يتطابق الرمز، لتجنّب الاستدعاءات الراجعة القديمة.
  • يعتمد الضغط للتحدث أي نص مرئي في التراكب كبادئة (بحيث إن الضغط على مفتاح الاختصار أثناء ظهور تراكب كلمة التنبيه يحافظ على النص ويُلحق كلامًا جديدًا). ينتظر حتى 1.5 ثانية للحصول على تفريغ نهائي قبل الرجوع إلى النص الحالي.
  • يتم إصدار تسجيلات الجرس/التراكب عند info ضمن الفئات voicewake.overlay وvoicewake.ptt وvoicewake.chime (بدء الجلسة، الجزئي، النهائي، الإرسال، الإخفاء، سبب الجرس).

الخطوات التالية

  1. VoiceSessionCoordinator (actor)
    • يمتلك بالضبط VoiceSession واحدًا في كل مرة.
    • واجهة برمجة التطبيقات (قائمة على الرموز): beginWakeCapture، beginPushToTalk، updatePartial، endCapture، cancel، applyCooldown.
    • يُسقِط الاستدعاءات الراجعة التي تحمل رموزًا قديمة (يمنع المُعرِّفات القديمة من إعادة فتح التراكب).
  2. VoiceSession (model)
    • الحقول: token، source (wakeWord|pushToTalk)، نص مُلتزم/متطاير، أعلام الجرس، مؤقتات (إرسال تلقائي، خمول)، overlayMode (display|editing|sending)، موعد نهائي لفترة التهدئة.
  3. ربط التراكب
    • VoiceSessionPublisher (ObservableObject) يعكس الجلسة النشطة إلى SwiftUI.
    • VoiceWakeOverlayView يُجسِّد العرض عبر الناشر فقط؛ ولا يُغيِّر مفردات عامة مباشرة.
    • إجراءات المستخدم على التراكب (sendNow، dismiss، edit) تُعيد النداء إلى المُنسِّق مع رمز الجلسة.
  4. مسار إرسال موحَّد
    • عند endCapture: إذا كان النص المُشذَّب فارغًا → إخفاء؛ وإلا performSend(session:) (يشغّل جرس الإرسال مرة واحدة، يُمرِّر، ثم يُخفي).
    • الضغط للتحدث: بلا تأخير؛ كلمة التنبيه: تأخير اختياري للإرسال التلقائي.
    • تطبيق فترة تهدئة قصيرة على تشغيل كلمة التنبيه بعد انتهاء الضغط للتحدث حتى لا تُعاد الإطلاق فورًا.
  5. التسجيل
    • يُصدر المُنسِّق سجلات .info في النظام الفرعي bot.molt، ضمن الفئات voicewake.overlay وvoicewake.chime.
    • الأحداث الرئيسية: session_started، adopted_by_push_to_talk، partial، finalized، send، dismiss، cancel، cooldown.

قائمة التحقق لتصحيح الأخطاء

  • بث السجلات أثناء إعادة إنتاج تراكب عالق:
    sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
    
  • التحقق من وجود رمز جلسة نشط واحد فقط؛ يجب أن يُسقِط المُنسِّق الاستدعاءات الراجعة القديمة.
  • التأكد من أن إفلات الضغط للتحدث يستدعي دائمًا endCapture بالرمز النشط؛ إذا كان النص فارغًا، توقّع dismiss دون جرس أو إرسال.

خطوات الترحيل (مقترحة)

  1. إضافة VoiceSessionCoordinator وVoiceSession وVoiceSessionPublisher.
  2. إعادة هيكلة VoiceWakeRuntime لإنشاء/تحديث/إنهاء الجلسات بدل لمس VoiceWakeOverlayController مباشرةً.
  3. إعادة هيكلة VoicePushToTalk لتبنّي الجلسات الموجودة واستدعاء endCapture عند الإفلات؛ وتطبيق فترة تهدئة وقت التشغيل.
  4. ربط VoiceWakeOverlayController بالناشر؛ وإزالة الاستدعاءات المباشرة من وقت التشغيل/الضغط للتحدث.
  5. إضافة اختبارات تكامل لتبنّي الجلسات، وفترة التهدئة، وإخفاء النص الفارغ.