@stv/page-test 1.0.0 → 1.0.2

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.
@@ -1,1229 +0,0 @@
1
- (function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("/* Recording-specific styles for RecordingPanel */\n/* These styles are extracted from @page-agent/ui/panel/Panel.module.css */\n\n._recordingButton_1yqua_4 {\n position: relative;\n transition: all 0.2s ease;\n}\n\n/* Placeholder - moved below controlButton definition for proper cascade */\n\n._eventCounter_1yqua_11 {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.7);\n min-width: 16px;\n text-align: center;\n padding: 0 4px;\n}\n\n@keyframes _recordingPulse_1yqua_1 {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.1);\n }\n}\n\n._exportOverlay_1yqua_31 {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: _overlayFadeIn_1yqua_1 0.2s ease;\n}\n\n@keyframes _overlayFadeIn_1yqua_1 {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n._exportDialog_1yqua_52 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n animation: _dialogSlideIn_1yqua_1 0.3s ease;\n}\n\n@keyframes _dialogSlideIn_1yqua_1 {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n._exportDialogHeader_1yqua_74 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._exportDialogHeader_1yqua_74 h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._closeButton_1yqua_89 {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n font-size: 24px;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n transition: all 0.2s;\n}\n\n._closeButton_1yqua_89:hover {\n background: rgba(255, 255, 255, 0.1);\n color: white;\n}\n\n._exportDialogContent_1yqua_110 {\n padding: 24px;\n}\n\n._exportDialogContent_1yqua_110 p {\n margin: 0 0 20px 0;\n color: rgba(255, 255, 255, 0.8);\n font-size: 14px;\n}\n\n._exportOptions_1yqua_120 {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n._exportOptionButton_1yqua_126 {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 20px 16px;\n background: rgba(255, 255, 255, 0.05);\n border: 2px solid rgba(255, 255, 255, 0.1);\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s;\n color: white;\n font-family: inherit;\n}\n\n._exportOptionButton_1yqua_126:hover {\n background: rgba(255, 255, 255, 0.1);\n border-color: rgba(255, 255, 255, 0.2);\n transform: translateY(-2px);\n}\n\n._exportOptionIcon_1yqua_147 {\n font-size: 28px;\n opacity: 0.9;\n}\n\n._exportOptionLabel_1yqua_152 {\n font-size: 13px;\n font-weight: 500;\n color: white;\n}\n\n/* Need to re-export controlButton and controls styles from Panel for the recording button to work */\n._controlButton_1yqua_159 {\n background: rgba(255, 255, 255, 0.1);\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 8px;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 36px;\n height: 36px;\n font-size: 14px;\n}\n\n._controlButton_1yqua_159:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n._controls_1yqua_179 {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n/* Recording state - use !important to ensure priority over base styles */\n._recordingButton_1yqua_4._recording_1yqua_4 {\n color: rgb(34, 197, 94) !important;\n animation: _recordingPulse_1yqua_1 1.5s ease-in-out infinite;\n}\n\n/* ========== Phase 4: Task Cards ========== */\n\n._taskCard_1yqua_193 {\n background: rgba(30, 41, 59, 0.8);\n backdrop-filter: blur(12px);\n border: 1px solid rgba(148, 163, 184, 0.1);\n border-radius: 12px;\n padding: 16px;\n margin-bottom: 12px;\n transition: all 0.2s ease;\n}\n\n._taskCard_1yqua_193:hover {\n border-color: rgba(59, 130, 246, 0.3);\n transform: translateY(-2px);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);\n}\n\n._taskCardHeader_1yqua_209 {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n._taskIcon_1yqua_216 {\n font-size: 24px;\n}\n\n._taskName_1yqua_220 {\n flex: 1;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: white;\n}\n\n._confidenceBadge_1yqua_228 {\n background: linear-gradient(135deg, #10b981, #059669);\n color: white;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n}\n\n._taskStats_1yqua_237 {\n display: flex;\n gap: 16px;\n margin-bottom: 12px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n}\n\n._taskParameters_1yqua_245 {\n margin-bottom: 16px;\n padding: 12px;\n background: rgba(0, 0, 0, 0.2);\n border-radius: 8px;\n}\n\n._taskParameters_1yqua_245 h4 {\n margin: 0 0 8px 0;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._parameterItem_1yqua_258 {\n display: flex;\n gap: 8px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n margin-bottom: 4px;\n}\n\n._paramKey_1yqua_266 {\n font-weight: 600;\n color: rgba(255, 255, 255, 0.9);\n}\n\n._paramValue_1yqua_271 {\n color: rgba(255, 255, 255, 0.7);\n}\n\n._taskActions_1yqua_275 {\n display: flex;\n gap: 8px;\n}\n\n._actionButton_1yqua_280 {\n flex: 1;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n._btnPrimary_1yqua_290 {\n background: linear-gradient(135deg, #3b82f6, #2563eb);\n color: white;\n border: none;\n}\n\n._btnPrimary_1yqua_290:hover {\n background: linear-gradient(135deg, #2563eb, #1d4ed8);\n transform: translateY(-1px);\n}\n\n._btnSecondary_1yqua_301 {\n background: rgba(255, 255, 255, 0.1);\n color: white;\n border: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n._btnSecondary_1yqua_301:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n/* ========== Phase 4: Task History ========== */\n\n._historyHeader_1yqua_313 {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 16px;\n}\n\n._historyHeader_1yqua_313 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._searchInput_1yqua_327 {\n flex: 1;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 8px;\n color: white;\n font-size: 14px;\n}\n\n._searchInput_1yqua_327::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n\n._filterTabs_1yqua_341 {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n._filterTab_1yqua_341 {\n padding: 6px 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n._filterTab_1yqua_341:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n._filterTab_1yqua_341._active_1yqua_362 {\n background: rgba(59, 130, 246, 0.3);\n border-color: rgba(59, 130, 246, 0.5);\n color: white;\n}\n\n._taskList_1yqua_368 {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-height: 400px;\n overflow-y: auto;\n}\n\n._emptyState_1yqua_376 {\n text-align: center;\n padding: 32px;\n color: rgba(255, 255, 255, 0.5);\n font-size: 14px;\n}\n\n/* ========== Phase 4: Replay Panel ========== */\n\n._replayPanel_1yqua_385 {\n background: rgba(30, 41, 59, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n}\n\n._replayHeader_1yqua_397 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._replayIcon_1yqua_405 {\n font-size: 24px;\n}\n\n._replayHeader_1yqua_397 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._replayStatus_1yqua_416 {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n}\n\n._replayStatus_1yqua_416._success_1yqua_421 {\n color: #10b981;\n}\n\n._replayStatus_1yqua_416._failed_1yqua_425 {\n color: #ef4444;\n}\n\n/* Event List */\n\n._eventList_1yqua_431 {\n padding: 16px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n._eventItem_1yqua_437 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px;\n border-radius: 6px;\n margin-bottom: 4px;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.8);\n transition: all 0.2s;\n}\n\n._eventItem_1yqua_437._statusPending_1yqua_449 {\n background: rgba(255, 255, 255, 0.05);\n}\n\n._eventItem_1yqua_437._statusRunning_1yqua_453 {\n background: rgba(59, 130, 246, 0.2);\n animation: _pulse_1yqua_1 1s infinite;\n}\n\n._eventItem_1yqua_437._statusSuccess_1yqua_458 {\n background: rgba(16, 185, 129, 0.2);\n}\n\n._eventItem_1yqua_437._statusFailed_1yqua_462 {\n background: rgba(239, 68, 68, 0.2);\n}\n\n._eventStatusIcon_1yqua_466 {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n._eventDescription_1yqua_472 {\n flex: 1;\n}\n\n._eventDuration_1yqua_476 {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.5);\n}\n\n@keyframes _pulse_1yqua_1 {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n/* ========== Phase 4: Error Handler ========== */\n\n._errorDialog_1yqua_493 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(239, 68, 68, 0.3);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n}\n\n._errorHeader_1yqua_503 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._errorIcon_1yqua_511 {\n font-size: 24px;\n}\n\n._errorHeader_1yqua_503 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._errorDetails_1yqua_522 {\n padding: 20px;\n color: rgba(255, 255, 255, 0.8);\n font-size: 14px;\n}\n\n._errorDetails_1yqua_522 p {\n margin: 0 0 12px 0;\n}\n\n._errorDetails_1yqua_522 strong {\n color: white;\n}\n\n._errorCauses_1yqua_536 {\n padding: 0 20px 20px;\n}\n\n._errorCauses_1yqua_536 h4 {\n margin: 0 0 12px 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._errorCauses_1yqua_536 ul {\n margin: 0;\n padding-left: 20px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 13px;\n}\n\n._errorCauses_1yqua_536 li {\n margin-bottom: 6px;\n}\n\n._errorActions_1yqua_557 {\n display: flex;\n gap: 8px;\n padding: 20px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Replay Report ========== */\n\n._replayReport_1yqua_566 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n}\n\n._replayReport_1yqua_566._success_1yqua_421 {\n border-color: rgba(16, 185, 129, 0.3);\n}\n\n._replayReport_1yqua_566._failure_1yqua_580 {\n border-color: rgba(239, 68, 68, 0.3);\n}\n\n._reportHeader_1yqua_584 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._reportIcon_1yqua_592 {\n font-size: 24px;\n}\n\n._reportHeader_1yqua_584 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._reportStats_1yqua_603 {\n display: flex;\n justify-content: space-around;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._statItem_1yqua_610 {\n text-align: center;\n}\n\n._statLabel_1yqua_614 {\n display: block;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n margin-bottom: 4px;\n}\n\n._statValue_1yqua_621 {\n display: block;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._reportDetails_1yqua_628 {\n padding: 20px;\n max-height: 200px;\n overflow-y: auto;\n}\n\n._reportDetails_1yqua_628 h4 {\n margin: 0 0 12px 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._eventResult_1yqua_640 {\n padding: 8px;\n margin-bottom: 4px;\n border-radius: 6px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._eventResult_1yqua_640._statusSuccess_1yqua_458 {\n background: rgba(16, 185, 129, 0.2);\n}\n\n._eventResult_1yqua_640._statusFailed_1yqua_462 {\n background: rgba(239, 68, 68, 0.2);\n}\n\n._reportActions_1yqua_656 {\n display: flex;\n gap: 8px;\n padding: 20px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Loading States ========== */\n\n._loadingContent_1yqua_665 {\n text-align: center;\n padding: 32px;\n color: white;\n}\n\n._loadingSpinner_1yqua_671 {\n width: 40px;\n height: 40px;\n margin: 0 auto 16px;\n border: 3px solid rgba(255, 255, 255, 0.2);\n border-top-color: white;\n border-radius: 50%;\n animation: _spin_1yqua_1 1s linear infinite;\n}\n\n@keyframes _spin_1yqua_1 {\n to {\n transform: rotate(360deg);\n }\n}\n\n._loadingContent_1yqua_665 p {\n margin: 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n/* ========== Phase 4: Task Card Dialog ========== */\n\n._taskCardDialog_1yqua_695 {\n padding: 20px;\n}\n\n/* ========== Settings ========== */\n\n._settingsOverlay_1yqua_701 {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: _overlayFadeIn_1yqua_1 0.2s ease;\n}\n\n._settingsDialog_1yqua_713 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n animation: _dialogSlideIn_1yqua_1 0.3s ease;\n}\n\n._settingsDialogHeader_1yqua_724 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._settingsDialogHeader_1yqua_724 h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._settingsDialogContent_1yqua_739 {\n padding: 24px;\n}\n\n._settingsField_1yqua_743 {\n margin-bottom: 20px;\n}\n\n._settingsLabel_1yqua_747 {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.9);\n}\n\n._settingsInput_1yqua_755 {\n width: 100%;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 8px;\n color: white;\n font-size: 14px;\n font-family: inherit;\n box-sizing: border-box;\n transition: all 0.2s;\n}\n\n._settingsInput_1yqua_755::placeholder {\n color: rgba(255, 255, 255, 0.4);\n}\n\n._settingsInput_1yqua_755:focus {\n outline: none;\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(59, 130, 246, 0.5);\n}\n\n._settingsInput_1yqua_755[type='password'] {\n -webkit-text-security: disc;\n}")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
2
- (function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("/* Recording-specific styles for RecordingPanel */\n/* These styles are extracted from @page-agent/ui/panel/Panel.module.css */\n\n._recordingButton_1yqua_4 {\n position: relative;\n transition: all 0.2s ease;\n}\n\n/* Placeholder - moved below controlButton definition for proper cascade */\n\n._eventCounter_1yqua_11 {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.7);\n min-width: 16px;\n text-align: center;\n padding: 0 4px;\n}\n\n@keyframes _recordingPulse_1yqua_1 {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.1);\n }\n}\n\n._exportOverlay_1yqua_31 {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: _overlayFadeIn_1yqua_1 0.2s ease;\n}\n\n@keyframes _overlayFadeIn_1yqua_1 {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n._exportDialog_1yqua_52 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n animation: _dialogSlideIn_1yqua_1 0.3s ease;\n}\n\n@keyframes _dialogSlideIn_1yqua_1 {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n._exportDialogHeader_1yqua_74 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._exportDialogHeader_1yqua_74 h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._closeButton_1yqua_89 {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n font-size: 24px;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n transition: all 0.2s;\n}\n\n._closeButton_1yqua_89:hover {\n background: rgba(255, 255, 255, 0.1);\n color: white;\n}\n\n._exportDialogContent_1yqua_110 {\n padding: 24px;\n}\n\n._exportDialogContent_1yqua_110 p {\n margin: 0 0 20px 0;\n color: rgba(255, 255, 255, 0.8);\n font-size: 14px;\n}\n\n._exportOptions_1yqua_120 {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n._exportOptionButton_1yqua_126 {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 20px 16px;\n background: rgba(255, 255, 255, 0.05);\n border: 2px solid rgba(255, 255, 255, 0.1);\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s;\n color: white;\n font-family: inherit;\n}\n\n._exportOptionButton_1yqua_126:hover {\n background: rgba(255, 255, 255, 0.1);\n border-color: rgba(255, 255, 255, 0.2);\n transform: translateY(-2px);\n}\n\n._exportOptionIcon_1yqua_147 {\n font-size: 28px;\n opacity: 0.9;\n}\n\n._exportOptionLabel_1yqua_152 {\n font-size: 13px;\n font-weight: 500;\n color: white;\n}\n\n/* Need to re-export controlButton and controls styles from Panel for the recording button to work */\n._controlButton_1yqua_159 {\n background: rgba(255, 255, 255, 0.1);\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 8px;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 36px;\n height: 36px;\n font-size: 14px;\n}\n\n._controlButton_1yqua_159:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n._controls_1yqua_179 {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n/* Recording state - use !important to ensure priority over base styles */\n._recordingButton_1yqua_4._recording_1yqua_4 {\n color: rgb(34, 197, 94) !important;\n animation: _recordingPulse_1yqua_1 1.5s ease-in-out infinite;\n}\n\n/* ========== Phase 4: Task Cards ========== */\n\n._taskCard_1yqua_193 {\n background: rgba(30, 41, 59, 0.8);\n backdrop-filter: blur(12px);\n border: 1px solid rgba(148, 163, 184, 0.1);\n border-radius: 12px;\n padding: 16px;\n margin-bottom: 12px;\n transition: all 0.2s ease;\n}\n\n._taskCard_1yqua_193:hover {\n border-color: rgba(59, 130, 246, 0.3);\n transform: translateY(-2px);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);\n}\n\n._taskCardHeader_1yqua_209 {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n._taskIcon_1yqua_216 {\n font-size: 24px;\n}\n\n._taskName_1yqua_220 {\n flex: 1;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: white;\n}\n\n._confidenceBadge_1yqua_228 {\n background: linear-gradient(135deg, #10b981, #059669);\n color: white;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n}\n\n._taskStats_1yqua_237 {\n display: flex;\n gap: 16px;\n margin-bottom: 12px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n}\n\n._taskParameters_1yqua_245 {\n margin-bottom: 16px;\n padding: 12px;\n background: rgba(0, 0, 0, 0.2);\n border-radius: 8px;\n}\n\n._taskParameters_1yqua_245 h4 {\n margin: 0 0 8px 0;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._parameterItem_1yqua_258 {\n display: flex;\n gap: 8px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n margin-bottom: 4px;\n}\n\n._paramKey_1yqua_266 {\n font-weight: 600;\n color: rgba(255, 255, 255, 0.9);\n}\n\n._paramValue_1yqua_271 {\n color: rgba(255, 255, 255, 0.7);\n}\n\n._taskActions_1yqua_275 {\n display: flex;\n gap: 8px;\n}\n\n._actionButton_1yqua_280 {\n flex: 1;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n._btnPrimary_1yqua_290 {\n background: linear-gradient(135deg, #3b82f6, #2563eb);\n color: white;\n border: none;\n}\n\n._btnPrimary_1yqua_290:hover {\n background: linear-gradient(135deg, #2563eb, #1d4ed8);\n transform: translateY(-1px);\n}\n\n._btnSecondary_1yqua_301 {\n background: rgba(255, 255, 255, 0.1);\n color: white;\n border: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n._btnSecondary_1yqua_301:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n/* ========== Phase 4: Task History ========== */\n\n._historyHeader_1yqua_313 {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 16px;\n}\n\n._historyHeader_1yqua_313 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._searchInput_1yqua_327 {\n flex: 1;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 8px;\n color: white;\n font-size: 14px;\n}\n\n._searchInput_1yqua_327::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n\n._filterTabs_1yqua_341 {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n._filterTab_1yqua_341 {\n padding: 6px 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n._filterTab_1yqua_341:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n._filterTab_1yqua_341._active_1yqua_362 {\n background: rgba(59, 130, 246, 0.3);\n border-color: rgba(59, 130, 246, 0.5);\n color: white;\n}\n\n._taskList_1yqua_368 {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-height: 400px;\n overflow-y: auto;\n}\n\n._emptyState_1yqua_376 {\n text-align: center;\n padding: 32px;\n color: rgba(255, 255, 255, 0.5);\n font-size: 14px;\n}\n\n/* ========== Phase 4: Replay Panel ========== */\n\n._replayPanel_1yqua_385 {\n background: rgba(30, 41, 59, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n}\n\n._replayHeader_1yqua_397 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._replayIcon_1yqua_405 {\n font-size: 24px;\n}\n\n._replayHeader_1yqua_397 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._replayStatus_1yqua_416 {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n}\n\n._replayStatus_1yqua_416._success_1yqua_421 {\n color: #10b981;\n}\n\n._replayStatus_1yqua_416._failed_1yqua_425 {\n color: #ef4444;\n}\n\n/* Event List */\n\n._eventList_1yqua_431 {\n padding: 16px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n._eventItem_1yqua_437 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px;\n border-radius: 6px;\n margin-bottom: 4px;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.8);\n transition: all 0.2s;\n}\n\n._eventItem_1yqua_437._statusPending_1yqua_449 {\n background: rgba(255, 255, 255, 0.05);\n}\n\n._eventItem_1yqua_437._statusRunning_1yqua_453 {\n background: rgba(59, 130, 246, 0.2);\n animation: _pulse_1yqua_1 1s infinite;\n}\n\n._eventItem_1yqua_437._statusSuccess_1yqua_458 {\n background: rgba(16, 185, 129, 0.2);\n}\n\n._eventItem_1yqua_437._statusFailed_1yqua_462 {\n background: rgba(239, 68, 68, 0.2);\n}\n\n._eventStatusIcon_1yqua_466 {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n._eventDescription_1yqua_472 {\n flex: 1;\n}\n\n._eventDuration_1yqua_476 {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.5);\n}\n\n@keyframes _pulse_1yqua_1 {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n/* ========== Phase 4: Error Handler ========== */\n\n._errorDialog_1yqua_493 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(239, 68, 68, 0.3);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n}\n\n._errorHeader_1yqua_503 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._errorIcon_1yqua_511 {\n font-size: 24px;\n}\n\n._errorHeader_1yqua_503 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._errorDetails_1yqua_522 {\n padding: 20px;\n color: rgba(255, 255, 255, 0.8);\n font-size: 14px;\n}\n\n._errorDetails_1yqua_522 p {\n margin: 0 0 12px 0;\n}\n\n._errorDetails_1yqua_522 strong {\n color: white;\n}\n\n._errorCauses_1yqua_536 {\n padding: 0 20px 20px;\n}\n\n._errorCauses_1yqua_536 h4 {\n margin: 0 0 12px 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._errorCauses_1yqua_536 ul {\n margin: 0;\n padding-left: 20px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 13px;\n}\n\n._errorCauses_1yqua_536 li {\n margin-bottom: 6px;\n}\n\n._errorActions_1yqua_557 {\n display: flex;\n gap: 8px;\n padding: 20px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Replay Report ========== */\n\n._replayReport_1yqua_566 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n}\n\n._replayReport_1yqua_566._success_1yqua_421 {\n border-color: rgba(16, 185, 129, 0.3);\n}\n\n._replayReport_1yqua_566._failure_1yqua_580 {\n border-color: rgba(239, 68, 68, 0.3);\n}\n\n._reportHeader_1yqua_584 {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._reportIcon_1yqua_592 {\n font-size: 24px;\n}\n\n._reportHeader_1yqua_584 h3 {\n flex: 1;\n margin: 0;\n font-size: 18px;\n color: white;\n}\n\n._reportStats_1yqua_603 {\n display: flex;\n justify-content: space-around;\n padding: 20px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._statItem_1yqua_610 {\n text-align: center;\n}\n\n._statLabel_1yqua_614 {\n display: block;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.7);\n margin-bottom: 4px;\n}\n\n._statValue_1yqua_621 {\n display: block;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._reportDetails_1yqua_628 {\n padding: 20px;\n max-height: 200px;\n overflow-y: auto;\n}\n\n._reportDetails_1yqua_628 h4 {\n margin: 0 0 12px 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._eventResult_1yqua_640 {\n padding: 8px;\n margin-bottom: 4px;\n border-radius: 6px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n._eventResult_1yqua_640._statusSuccess_1yqua_458 {\n background: rgba(16, 185, 129, 0.2);\n}\n\n._eventResult_1yqua_640._statusFailed_1yqua_462 {\n background: rgba(239, 68, 68, 0.2);\n}\n\n._reportActions_1yqua_656 {\n display: flex;\n gap: 8px;\n padding: 20px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Loading States ========== */\n\n._loadingContent_1yqua_665 {\n text-align: center;\n padding: 32px;\n color: white;\n}\n\n._loadingSpinner_1yqua_671 {\n width: 40px;\n height: 40px;\n margin: 0 auto 16px;\n border: 3px solid rgba(255, 255, 255, 0.2);\n border-top-color: white;\n border-radius: 50%;\n animation: _spin_1yqua_1 1s linear infinite;\n}\n\n@keyframes _spin_1yqua_1 {\n to {\n transform: rotate(360deg);\n }\n}\n\n._loadingContent_1yqua_665 p {\n margin: 0;\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n/* ========== Phase 4: Task Card Dialog ========== */\n\n._taskCardDialog_1yqua_695 {\n padding: 20px;\n}\n\n/* ========== Settings ========== */\n\n._settingsOverlay_1yqua_701 {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: _overlayFadeIn_1yqua_1 0.2s ease;\n}\n\n._settingsDialog_1yqua_713 {\n background: rgba(30, 30, 30, 0.95);\n backdrop-filter: blur(20px);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n max-width: 480px;\n width: 90%;\n animation: _dialogSlideIn_1yqua_1 0.3s ease;\n}\n\n._settingsDialogHeader_1yqua_724 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n._settingsDialogHeader_1yqua_724 h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: white;\n}\n\n._settingsDialogContent_1yqua_739 {\n padding: 24px;\n}\n\n._settingsField_1yqua_743 {\n margin-bottom: 20px;\n}\n\n._settingsLabel_1yqua_747 {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.9);\n}\n\n._settingsInput_1yqua_755 {\n width: 100%;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 8px;\n color: white;\n font-size: 14px;\n font-family: inherit;\n box-sizing: border-box;\n transition: all 0.2s;\n}\n\n._settingsInput_1yqua_755::placeholder {\n color: rgba(255, 255, 255, 0.4);\n}\n\n._settingsInput_1yqua_755:focus {\n outline: none;\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(59, 130, 246, 0.5);\n}\n\n._settingsInput_1yqua_755[type='password'] {\n -webkit-text-security: disc;\n}")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
3
- import { PageAgentCore, assert, historyToReport, parseTestSpec, query, testSpecToPrompt, wait_for_condition } from "@page-agent/core";
4
- import { PageController } from "@page-agent/page-controller";
5
- import { ActionRecorder } from "@page-agent/recorder";
6
- import { I18n, Panel } from "@page-agent/ui";
7
- export * from "@page-agent/core";
8
- var RecordingPanel_module_default = {
9
- recordingButton: "_recordingButton_1yqua_4",
10
- eventCounter: "_eventCounter_1yqua_11",
11
- exportOverlay: "_exportOverlay_1yqua_31",
12
- overlayFadeIn: "_overlayFadeIn_1yqua_1",
13
- exportDialog: "_exportDialog_1yqua_52",
14
- dialogSlideIn: "_dialogSlideIn_1yqua_1",
15
- exportDialogHeader: "_exportDialogHeader_1yqua_74",
16
- closeButton: "_closeButton_1yqua_89",
17
- exportDialogContent: "_exportDialogContent_1yqua_110",
18
- exportOptions: "_exportOptions_1yqua_120",
19
- exportOptionButton: "_exportOptionButton_1yqua_126",
20
- exportOptionIcon: "_exportOptionIcon_1yqua_147",
21
- exportOptionLabel: "_exportOptionLabel_1yqua_152",
22
- controlButton: "_controlButton_1yqua_159",
23
- controls: "_controls_1yqua_179",
24
- recording: "_recording_1yqua_4",
25
- recordingPulse: "_recordingPulse_1yqua_1",
26
- taskCard: "_taskCard_1yqua_193",
27
- taskCardHeader: "_taskCardHeader_1yqua_209",
28
- taskIcon: "_taskIcon_1yqua_216",
29
- taskName: "_taskName_1yqua_220",
30
- confidenceBadge: "_confidenceBadge_1yqua_228",
31
- taskStats: "_taskStats_1yqua_237",
32
- taskParameters: "_taskParameters_1yqua_245",
33
- parameterItem: "_parameterItem_1yqua_258",
34
- paramKey: "_paramKey_1yqua_266",
35
- paramValue: "_paramValue_1yqua_271",
36
- taskActions: "_taskActions_1yqua_275",
37
- actionButton: "_actionButton_1yqua_280",
38
- btnPrimary: "_btnPrimary_1yqua_290",
39
- btnSecondary: "_btnSecondary_1yqua_301",
40
- historyHeader: "_historyHeader_1yqua_313",
41
- searchInput: "_searchInput_1yqua_327",
42
- filterTabs: "_filterTabs_1yqua_341",
43
- filterTab: "_filterTab_1yqua_341",
44
- active: "_active_1yqua_362",
45
- taskList: "_taskList_1yqua_368",
46
- emptyState: "_emptyState_1yqua_376",
47
- replayPanel: "_replayPanel_1yqua_385",
48
- replayHeader: "_replayHeader_1yqua_397",
49
- replayIcon: "_replayIcon_1yqua_405",
50
- replayStatus: "_replayStatus_1yqua_416",
51
- success: "_success_1yqua_421",
52
- failed: "_failed_1yqua_425",
53
- eventList: "_eventList_1yqua_431",
54
- eventItem: "_eventItem_1yqua_437",
55
- statusPending: "_statusPending_1yqua_449",
56
- statusRunning: "_statusRunning_1yqua_453",
57
- pulse: "_pulse_1yqua_1",
58
- statusSuccess: "_statusSuccess_1yqua_458",
59
- statusFailed: "_statusFailed_1yqua_462",
60
- eventStatusIcon: "_eventStatusIcon_1yqua_466",
61
- eventDescription: "_eventDescription_1yqua_472",
62
- eventDuration: "_eventDuration_1yqua_476",
63
- errorDialog: "_errorDialog_1yqua_493",
64
- errorHeader: "_errorHeader_1yqua_503",
65
- errorIcon: "_errorIcon_1yqua_511",
66
- errorDetails: "_errorDetails_1yqua_522",
67
- errorCauses: "_errorCauses_1yqua_536",
68
- errorActions: "_errorActions_1yqua_557",
69
- replayReport: "_replayReport_1yqua_566",
70
- failure: "_failure_1yqua_580",
71
- reportHeader: "_reportHeader_1yqua_584",
72
- reportIcon: "_reportIcon_1yqua_592",
73
- reportStats: "_reportStats_1yqua_603",
74
- statItem: "_statItem_1yqua_610",
75
- statLabel: "_statLabel_1yqua_614",
76
- statValue: "_statValue_1yqua_621",
77
- reportDetails: "_reportDetails_1yqua_628",
78
- eventResult: "_eventResult_1yqua_640",
79
- reportActions: "_reportActions_1yqua_656",
80
- loadingContent: "_loadingContent_1yqua_665",
81
- loadingSpinner: "_loadingSpinner_1yqua_671",
82
- spin: "_spin_1yqua_1",
83
- taskCardDialog: "_taskCardDialog_1yqua_695",
84
- settingsOverlay: "_settingsOverlay_1yqua_701",
85
- settingsDialog: "_settingsDialog_1yqua_713",
86
- settingsDialogHeader: "_settingsDialogHeader_1yqua_724",
87
- settingsDialogContent: "_settingsDialogContent_1yqua_739",
88
- settingsField: "_settingsField_1yqua_743",
89
- settingsLabel: "_settingsLabel_1yqua_747",
90
- settingsInput: "_settingsInput_1yqua_755"
91
- };
92
- //#endregion
93
- //#region src/TaskCard.ts
94
- /**
95
- * TaskCard - Display a single recorded task with intent and actions
96
- *
97
- * Shows task name, intent confidence, parameters, and provides
98
- * replay, export, and edit actions.
99
- */
100
- var TaskCard = class {
101
- #task;
102
- #onReplay;
103
- #onExport;
104
- #onEdit;
105
- constructor(task, callbacks) {
106
- this.#task = task;
107
- this.#onReplay = callbacks?.onReplay;
108
- this.#onExport = callbacks?.onExport;
109
- this.#onEdit = callbacks?.onEdit;
110
- }
111
- /**
112
- * Render task card element
113
- */
114
- render() {
115
- const card = document.createElement("div");
116
- card.className = RecordingPanel_module_default.taskCard;
117
- card.dataset.taskId = this.#task.id;
118
- const header = this.#renderHeader();
119
- card.appendChild(header);
120
- const stats = this.#renderStats();
121
- card.appendChild(stats);
122
- if (Object.keys(this.#task.intent.parameters.values).length > 0) {
123
- const params = this.#renderParameters();
124
- card.appendChild(params);
125
- }
126
- const actions = this.#renderActions();
127
- card.appendChild(actions);
128
- return card;
129
- }
130
- /**
131
- * Render card header
132
- */
133
- #renderHeader() {
134
- const header = document.createElement("div");
135
- header.className = RecordingPanel_module_default.taskCardHeader;
136
- const icon = document.createElement("span");
137
- icon.className = RecordingPanel_module_default.taskIcon;
138
- icon.textContent = this.#getIntentIcon(this.#task.intent.action);
139
- header.appendChild(icon);
140
- const name = document.createElement("h3");
141
- name.className = RecordingPanel_module_default.taskName;
142
- name.textContent = this.#task.name;
143
- header.appendChild(name);
144
- const confidence = document.createElement("span");
145
- confidence.className = RecordingPanel_module_default.confidenceBadge;
146
- const confidencePercent = Math.round(this.#task.intent.confidence * 100);
147
- confidence.textContent = `${confidencePercent}%`;
148
- confidence.title = `置信度: ${confidencePercent}%`;
149
- header.appendChild(confidence);
150
- return header;
151
- }
152
- /**
153
- * Render task statistics
154
- */
155
- #renderStats() {
156
- const stats = document.createElement("div");
157
- stats.className = RecordingPanel_module_default.taskStats;
158
- const eventCount = document.createElement("span");
159
- eventCount.textContent = `${this.#task.eventCount} 个操作`;
160
- stats.appendChild(eventCount);
161
- if (this.#task.estimatedDuration) {
162
- const duration = document.createElement("span");
163
- duration.textContent = `预计 ${this.#formatDuration(this.#task.estimatedDuration)}`;
164
- stats.appendChild(duration);
165
- }
166
- return stats;
167
- }
168
- /**
169
- * Render parameters section
170
- */
171
- #renderParameters() {
172
- const params = document.createElement("div");
173
- params.className = RecordingPanel_module_default.taskParameters;
174
- const label = document.createElement("h4");
175
- label.textContent = "参数:";
176
- params.appendChild(label);
177
- for (const [key, value] of Object.entries(this.#task.intent.parameters.values)) {
178
- const paramItem = document.createElement("div");
179
- paramItem.className = RecordingPanel_module_default.parameterItem;
180
- const keySpan = document.createElement("span");
181
- keySpan.className = RecordingPanel_module_default.paramKey;
182
- keySpan.textContent = `${key}:`;
183
- paramItem.appendChild(keySpan);
184
- const valueSpan = document.createElement("span");
185
- valueSpan.className = RecordingPanel_module_default.paramValue;
186
- valueSpan.textContent = String(value);
187
- paramItem.appendChild(valueSpan);
188
- params.appendChild(paramItem);
189
- }
190
- return params;
191
- }
192
- /**
193
- * Render action buttons
194
- */
195
- #renderActions() {
196
- const actions = document.createElement("div");
197
- actions.className = RecordingPanel_module_default.taskActions;
198
- const replayBtn = document.createElement("button");
199
- replayBtn.className = `${RecordingPanel_module_default.btnPrimary} ${RecordingPanel_module_default.actionButton}`;
200
- replayBtn.dataset.action = "replay";
201
- replayBtn.textContent = "▶ 立即重放";
202
- replayBtn.onclick = () => this.#onReplay?.(this.#task);
203
- actions.appendChild(replayBtn);
204
- const exportBtn = document.createElement("button");
205
- exportBtn.className = `${RecordingPanel_module_default.btnSecondary} ${RecordingPanel_module_default.actionButton}`;
206
- exportBtn.dataset.action = "export";
207
- exportBtn.textContent = "📤 导出";
208
- exportBtn.onclick = () => this.#onExport?.(this.#task);
209
- actions.appendChild(exportBtn);
210
- const editBtn = document.createElement("button");
211
- editBtn.className = `${RecordingPanel_module_default.btnSecondary} ${RecordingPanel_module_default.actionButton}`;
212
- editBtn.dataset.action = "edit";
213
- editBtn.textContent = "✏ 编辑";
214
- editBtn.onclick = () => this.#onEdit?.(this.#task);
215
- actions.appendChild(editBtn);
216
- return actions;
217
- }
218
- /**
219
- * Get intent icon
220
- */
221
- #getIntentIcon(intent) {
222
- return {
223
- login: "🔐",
224
- search: "🔍",
225
- fill_form: "📝",
226
- navigate: "🧭",
227
- data_entry: "⌨️",
228
- batch_operation: "🔄",
229
- verification: "✅",
230
- download: "⬇️",
231
- upload: "⬆️",
232
- select: "☑️",
233
- scroll: "📜",
234
- input_text: "💬",
235
- click_button: "👆",
236
- other: "📋"
237
- }[intent] || "📋";
238
- }
239
- /**
240
- * Format duration in human-readable format
241
- */
242
- #formatDuration(ms) {
243
- const seconds = Math.round(ms / 1e3);
244
- if (seconds < 60) return `${seconds}秒`;
245
- return `${Math.floor(seconds / 60)}分${seconds % 60}秒`;
246
- }
247
- };
248
- /**
249
- * Create a task card element
250
- */
251
- function createTaskCard(task, callbacks) {
252
- return new TaskCard(task, callbacks).render();
253
- }
254
- //#endregion
255
- //#region src/TaskHistory.ts
256
- /**
257
- * Storage key for localStorage
258
- */
259
- var STORAGE_KEY = "page-agent-tasks";
260
- /**
261
- * TaskHistory - Manage and display list of recorded tasks
262
- *
263
- * Provides task persistence, search, filtering, and rendering
264
- * of the task history list.
265
- */
266
- var TaskHistory = class {
267
- #tasks = [];
268
- #onTaskDelete;
269
- #onReplay;
270
- #onExport;
271
- #onEdit;
272
- constructor(callbacks) {
273
- this.#onTaskDelete = callbacks?.onTaskDelete;
274
- this.#onReplay = callbacks?.onReplay;
275
- this.#onExport = callbacks?.onExport;
276
- this.#onEdit = callbacks?.onEdit;
277
- this.#loadFromStorage();
278
- }
279
- /**
280
- * Add new task to history
281
- */
282
- addTask(task) {
283
- this.#tasks.unshift(task);
284
- this.#saveToStorage();
285
- if (this.#tasks.length > 50) {
286
- this.#tasks = this.#tasks.slice(0, 50);
287
- this.#saveToStorage();
288
- }
289
- }
290
- /**
291
- * Get task by ID
292
- */
293
- getTask(id) {
294
- return this.#tasks.find((t) => t.id === id);
295
- }
296
- /**
297
- * Delete task
298
- */
299
- deleteTask(id) {
300
- this.#tasks = this.#tasks.filter((t) => t.id !== id);
301
- this.#saveToStorage();
302
- this.#onTaskDelete?.(id);
303
- }
304
- /**
305
- * Search tasks by name/intent
306
- */
307
- searchTasks(query) {
308
- const lowerQuery = query.toLowerCase();
309
- return this.#tasks.filter((t) => t.name.toLowerCase().includes(lowerQuery) || t.intent.action.toLowerCase().includes(lowerQuery) || t.intent.description.toLowerCase().includes(lowerQuery));
310
- }
311
- /**
312
- * Filter by intent type
313
- */
314
- filterByIntent(intent) {
315
- if (intent === "other") return this.#tasks;
316
- return this.#tasks.filter((t) => t.intent.action === intent);
317
- }
318
- /**
319
- * Get all tasks
320
- */
321
- getAllTasks() {
322
- return [...this.#tasks];
323
- }
324
- /**
325
- * Clear all tasks
326
- */
327
- clearAll() {
328
- this.#tasks = [];
329
- this.#saveToStorage();
330
- }
331
- /**
332
- * Render history list
333
- */
334
- render() {
335
- const fragment = document.createDocumentFragment();
336
- const header = this.#renderHeader();
337
- fragment.appendChild(header);
338
- const filters = this.#renderFilters();
339
- fragment.appendChild(filters);
340
- const taskList = this.#renderTaskList();
341
- fragment.appendChild(taskList);
342
- return fragment;
343
- }
344
- /**
345
- * Render header
346
- */
347
- #renderHeader() {
348
- const header = document.createElement("div");
349
- header.className = RecordingPanel_module_default.historyHeader;
350
- const title = document.createElement("h3");
351
- title.textContent = "📼 任务历史";
352
- header.appendChild(title);
353
- const searchInput = document.createElement("input");
354
- searchInput.type = "text";
355
- searchInput.className = RecordingPanel_module_default.searchInput;
356
- searchInput.placeholder = "搜索任务...";
357
- searchInput.oninput = (e) => {
358
- const query = e.target.value;
359
- this.#refreshTaskList(query);
360
- };
361
- header.appendChild(searchInput);
362
- return header;
363
- }
364
- /**
365
- * Render filter tabs
366
- */
367
- #renderFilters() {
368
- const filters = document.createElement("div");
369
- filters.className = RecordingPanel_module_default.filterTabs;
370
- for (const option of [
371
- {
372
- label: "全部",
373
- intent: "other"
374
- },
375
- {
376
- label: "登录",
377
- intent: "login"
378
- },
379
- {
380
- label: "搜索",
381
- intent: "search"
382
- },
383
- {
384
- label: "表单",
385
- intent: "fill_form"
386
- }
387
- ]) {
388
- const tab = document.createElement("button");
389
- tab.className = RecordingPanel_module_default.filterTab;
390
- if (option.intent === "other") tab.classList.add(RecordingPanel_module_default.active);
391
- tab.textContent = option.label;
392
- tab.dataset.intent = option.intent;
393
- tab.onclick = () => {
394
- filters.querySelectorAll(`.${RecordingPanel_module_default.filterTab}`).forEach((t) => t.classList.remove(RecordingPanel_module_default.active));
395
- tab.classList.add(RecordingPanel_module_default.active);
396
- this.#refreshTaskList("", option.intent);
397
- };
398
- filters.appendChild(tab);
399
- }
400
- return filters;
401
- }
402
- /**
403
- * Render task list
404
- */
405
- #renderTaskList(tasks) {
406
- const container = document.createElement("div");
407
- container.className = RecordingPanel_module_default.taskList;
408
- const taskList = tasks || this.#tasks;
409
- if (taskList.length === 0) {
410
- const empty = document.createElement("div");
411
- empty.className = RecordingPanel_module_default.emptyState;
412
- empty.textContent = "暂无录制的任务";
413
- container.appendChild(empty);
414
- return container;
415
- }
416
- for (const task of taskList) {
417
- const card = createTaskCard(task, {
418
- onReplay: this.#onReplay,
419
- onExport: this.#onExport,
420
- onEdit: this.#onEdit
421
- });
422
- container.appendChild(card);
423
- }
424
- return container;
425
- }
426
- /**
427
- * Refresh task list with optional search/filter
428
- */
429
- #refreshTaskList(searchQuery = "", intentFilter) {
430
- let tasks = this.#tasks;
431
- if (searchQuery) tasks = this.searchTasks(searchQuery);
432
- if (intentFilter && intentFilter !== "other") tasks = tasks.filter((t) => t.intent.action === intentFilter);
433
- const taskList = document.querySelector(`.${RecordingPanel_module_default.taskList}`);
434
- if (taskList) {
435
- const newList = this.#renderTaskList(tasks);
436
- taskList.replaceWith(newList);
437
- }
438
- }
439
- /**
440
- * Save to localStorage
441
- */
442
- #saveToStorage() {
443
- try {
444
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.#tasks));
445
- } catch (error) {
446
- console.warn("[TaskHistory] Failed to save to localStorage:", error);
447
- }
448
- }
449
- /**
450
- * Load from localStorage
451
- */
452
- #loadFromStorage() {
453
- try {
454
- const stored = localStorage.getItem(STORAGE_KEY);
455
- if (stored) this.#tasks = JSON.parse(stored);
456
- } catch (error) {
457
- console.warn("[TaskHistory] Failed to load from localStorage:", error);
458
- this.#tasks = [];
459
- }
460
- }
461
- /**
462
- * Generate unique task ID
463
- */
464
- static generateId() {
465
- return `task-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
466
- }
467
- };
468
- //#endregion
469
- //#region src/RecordingPanel.ts
470
- /**
471
- * Extended Panel with recording functionality
472
- *
473
- * This class extends the base Panel class to add recording UI and logic.
474
- * All recording functionality is isolated in this extension, keeping the base Panel clean.
475
- */
476
- var RecordingPanel = class extends Panel {
477
- #recorder;
478
- #recordingButton;
479
- #eventCounter;
480
- #recordingUpdateTimer = null;
481
- #i18n;
482
- #taskHistory;
483
- #currentOverlay;
484
- #config;
485
- #testModeButton;
486
- #testContainer;
487
- #dslEditor;
488
- #isTestMode = false;
489
- #settingsButton;
490
- constructor(agent, config) {
491
- super(agent, {
492
- language: config.language,
493
- promptForNextTask: config.promptForNextTask
494
- });
495
- this.#recorder = config.recorder;
496
- this.#config = config;
497
- this.#i18n = new I18n(config.language ?? "zh-CN");
498
- this.#taskHistory = new TaskHistory({
499
- onReplay: (task) => this.#handleReplay(task),
500
- onExport: (task) => this.#handleExport(task),
501
- onEdit: (task) => this.#handleEdit(task)
502
- });
503
- this.#setupRecordingUI();
504
- this.#setupTestModeUI();
505
- this.#setupSettingsUI();
506
- }
507
- /**
508
- * Setup recording UI elements
509
- */
510
- #setupRecordingUI() {
511
- const header = this.wrapper.children[2];
512
- if (!header) {
513
- console.warn("[RecordingPanel] Header not found at expected position");
514
- return;
515
- }
516
- const controls = header.children[1];
517
- if (!controls) {
518
- console.warn("[RecordingPanel] Controls not found at expected position");
519
- return;
520
- }
521
- const buttons = controls.querySelectorAll("button");
522
- if (buttons.length < 2) {
523
- console.warn("[RecordingPanel] Controls section does not contain expected buttons");
524
- return;
525
- }
526
- this.#recordingButton = document.createElement("button");
527
- const existingButton = buttons[0];
528
- const baseButtonClass = Array.from(existingButton.classList).find((c) => c.includes("controlButton") || c.includes("expandButton") || c.includes("stopButton")) || existingButton.className;
529
- this.#recordingButton.className = `${baseButtonClass} ${RecordingPanel_module_default.recordingButton}`;
530
- this.#recordingButton.title = this.#i18n.t("ui.panel.startRecording");
531
- this.#recordingButton.innerHTML = "●";
532
- this.#recordingButton.setAttribute("data-page-agent-ignore", "true");
533
- this.#recordingButton.setAttribute("data-browser-use-ignore", "true");
534
- this.#eventCounter = document.createElement("span");
535
- this.#eventCounter.className = RecordingPanel_module_default.eventCounter;
536
- this.#eventCounter.textContent = "";
537
- this.#eventCounter.setAttribute("data-page-agent-ignore", "true");
538
- this.#eventCounter.setAttribute("data-browser-use-ignore", "true");
539
- controls.insertBefore(this.#recordingButton, controls.firstChild);
540
- controls.insertBefore(this.#eventCounter, this.#recordingButton.nextSibling);
541
- console.log("[RecordingPanel] Recording button added successfully");
542
- this.#recordingButton.addEventListener("click", (e) => {
543
- e.stopPropagation();
544
- this.#toggleRecording();
545
- });
546
- }
547
- /**
548
- * Toggle recording state
549
- */
550
- #toggleRecording() {
551
- if (this.#recorder.getStatus().isRecording) {
552
- const events = this.#recorder.stop();
553
- this.#updateRecordingUI(false);
554
- if (events.length > 0) this.#processRecording(events);
555
- } else {
556
- this.#recorder.start();
557
- this.#updateRecordingUI(true);
558
- }
559
- }
560
- /**
561
- * Update recording UI state
562
- */
563
- /**
564
- * Setup test mode toggle button in header controls
565
- */
566
- #setupTestModeUI() {
567
- const header = this.wrapper.children[2];
568
- if (!header) return;
569
- const controls = header.children[1];
570
- if (!controls) return;
571
- const buttons = controls.querySelectorAll("button");
572
- if (buttons.length < 1) return;
573
- const existingButton = buttons[0];
574
- const baseClass = Array.from(existingButton.classList).find((c) => c.includes("controlButton") || c.includes("expandButton")) || existingButton.className;
575
- this.#testModeButton = document.createElement("button");
576
- this.#testModeButton.className = baseClass;
577
- this.#testModeButton.textContent = "T";
578
- this.#testModeButton.title = "Toggle test mode";
579
- this.#testModeButton.setAttribute("data-page-agent-ignore", "true");
580
- this.#testModeButton.setAttribute("data-browser-use-ignore", "true");
581
- this.#testModeButton.style.fontWeight = "bold";
582
- this.#testModeButton.addEventListener("click", (e) => {
583
- e.stopPropagation();
584
- this.#toggleTestMode();
585
- });
586
- controls.insertBefore(this.#testModeButton, controls.firstChild);
587
- }
588
- /**
589
- * Toggle between automation and test mode
590
- */
591
- #toggleTestMode() {
592
- this.#isTestMode = !this.#isTestMode;
593
- if (this.#isTestMode) {
594
- this.#testModeButton.style.color = "rgb(59, 130, 246)";
595
- this.#showTestPanel();
596
- } else {
597
- this.#testModeButton.style.color = "";
598
- this.#hideTestPanel();
599
- }
600
- }
601
- /**
602
- * Show test panel by injecting DSL editor into the panel
603
- */
604
- #showTestPanel() {
605
- if (this.#testContainer) return;
606
- this.wrapper.children[1];
607
- const inputSection = this.wrapper.children[3];
608
- if (inputSection) inputSection.style.display = "none";
609
- const container = document.createElement("div");
610
- container.setAttribute("data-page-agent-ignore", "true");
611
- container.setAttribute("data-browser-use-ignore", "true");
612
- container.style.cssText = "padding: 8px 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; overflow-y: auto;";
613
- const editorLabel = document.createElement("div");
614
- editorLabel.textContent = "Test DSL (YAML)";
615
- editorLabel.style.cssText = "color: rgba(255,255,255,0.5); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;";
616
- container.appendChild(editorLabel);
617
- this.#dslEditor = document.createElement("textarea");
618
- this.#dslEditor.setAttribute("data-page-agent-ignore", "true");
619
- this.#dslEditor.placeholder = "web:\n url: https://example.com\ntasks:\n - name: My test\n flow:\n - ai: do something\n - aiAssert: result should be X";
620
- this.#dslEditor.style.cssText = "width: 100%; min-height: 180px; background: rgba(15,15,15,0.85); color: #f5f5f5; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; padding: 12px; font-family: \"SF Mono\", \"Monaco\", \"Menlo\", \"Consolas\", monospace; font-size: 13px; line-height: 1.6; resize: vertical; box-sizing: border-box;";
621
- container.appendChild(this.#dslEditor);
622
- const placeholderStyle = document.createElement("style");
623
- placeholderStyle.textContent = `
624
- [data-page-agent-ignore="true"]::placeholder {
625
- color: rgba(255, 255, 255, 0.4) !important;
626
- }
627
- `;
628
- document.head.appendChild(placeholderStyle);
629
- const btnRow = document.createElement("div");
630
- btnRow.style.cssText = "display: flex; gap: 6px;";
631
- btnRow.appendChild(this.#createTestBtn("Run", () => this.#handleTestRun()));
632
- btnRow.appendChild(this.#createTestBtn("Save", () => this.#handleTestSave()));
633
- btnRow.appendChild(this.#createTestBtn("Export", () => this.#handleTestExport()));
634
- container.appendChild(btnRow);
635
- this.#testContainer = container;
636
- this.wrapper.insertBefore(container, inputSection);
637
- }
638
- /**
639
- * Hide test panel and restore normal panel UI
640
- */
641
- #hideTestPanel() {
642
- if (this.#testContainer) {
643
- this.#testContainer.remove();
644
- this.#testContainer = void 0;
645
- }
646
- const historyWrapper = this.wrapper.children[1];
647
- const inputSection = this.wrapper.children[3];
648
- if (historyWrapper) historyWrapper.style.display = "";
649
- if (inputSection) inputSection.style.display = "";
650
- }
651
- #createTestBtn(text, onClick) {
652
- const btn = document.createElement("button");
653
- btn.setAttribute("data-page-agent-ignore", "true");
654
- btn.textContent = text;
655
- btn.style.cssText = "padding: 4px 12px; background: rgba(59,130,246,0.7); color: white; border: none; border-radius: 4px; font-size: 11px; cursor: pointer;";
656
- btn.addEventListener("mouseenter", () => {
657
- btn.style.background = "rgba(59,130,246,1)";
658
- });
659
- btn.addEventListener("mouseleave", () => {
660
- btn.style.background = "rgba(59,130,246,0.7)";
661
- });
662
- btn.addEventListener("click", (e) => {
663
- e.stopPropagation();
664
- onClick();
665
- });
666
- return btn;
667
- }
668
- async #handleTestRun() {
669
- if (!this.#dslEditor || !this.#config.onRunTest) return;
670
- const dsl = this.#dslEditor.value.trim();
671
- if (!dsl) return;
672
- const parsed = parseTestSpec(dsl);
673
- if (!parsed.success) {
674
- console.error("Parse error:", parsed.errors);
675
- return;
676
- }
677
- try {
678
- await this.#config.onRunTest(dsl);
679
- } catch (error) {
680
- console.error("Test failed:", error);
681
- }
682
- }
683
- #handleTestSave() {
684
- if (!this.#dslEditor) return;
685
- const dsl = this.#dslEditor.value.trim();
686
- if (!dsl) return;
687
- const parsed = parseTestSpec(dsl);
688
- if (!parsed.success || !parsed.spec) return;
689
- const name = parsed.spec.tasks.map((t) => t.name).join(" / ") || "Untitled";
690
- const saved = JSON.parse(localStorage.getItem("page-agent-test-specs") || "[]");
691
- saved.push({
692
- name,
693
- dsl
694
- });
695
- localStorage.setItem("page-agent-test-specs", JSON.stringify(saved));
696
- console.log("Saved:", name);
697
- }
698
- #handleTestExport() {
699
- console.log("Export functionality disabled: test result display removed");
700
- }
701
- #updateRecordingUI(isRecording) {
702
- if (!this.#recordingButton) return;
703
- if (isRecording) {
704
- this.#recordingButton.title = this.#i18n.t("ui.panel.stopRecording");
705
- this.#recordingButton.classList.add(RecordingPanel_module_default.recording);
706
- this.#startRecordingUpdateLoop();
707
- } else {
708
- this.#recordingButton.title = this.#i18n.t("ui.panel.startRecording");
709
- this.#recordingButton.classList.remove(RecordingPanel_module_default.recording);
710
- this.#stopRecordingUpdateLoop();
711
- if (this.#eventCounter) this.#eventCounter.textContent = "";
712
- }
713
- }
714
- /**
715
- * Start periodic update of event counter
716
- */
717
- #startRecordingUpdateLoop() {
718
- this.#stopRecordingUpdateLoop();
719
- this.#recordingUpdateTimer = setInterval(() => {
720
- if (this.#eventCounter) {
721
- const status = this.#recorder.getStatus();
722
- this.#eventCounter.textContent = status.eventCount > 0 ? `${status.eventCount}` : "";
723
- }
724
- }, 500);
725
- }
726
- /**
727
- * Stop periodic update of event counter
728
- */
729
- #stopRecordingUpdateLoop() {
730
- if (this.#recordingUpdateTimer) {
731
- clearInterval(this.#recordingUpdateTimer);
732
- this.#recordingUpdateTimer = null;
733
- }
734
- }
735
- /**
736
- * Process recording and create task card
737
- */
738
- async #processRecording(events) {
739
- this.#showLoadingState("AI分析中...");
740
- try {
741
- const taskData = await this.#analyzeIntent(events);
742
- this.#taskHistory.addTask(taskData);
743
- this.#showTaskCard(taskData);
744
- } catch (error) {
745
- console.error("[RecordingPanel] Failed to process recording:", error);
746
- this.#showExportDialog(events);
747
- }
748
- }
749
- /**
750
- * Analyze intent from recorded events
751
- */
752
- async #analyzeIntent(events) {
753
- const { IntentRecognizer } = await import("@page-agent/recorder");
754
- const intent = new IntentRecognizer().analyzeEvents(events);
755
- const { TaskParameterExtractor } = await import("@page-agent/recorder");
756
- const parameters = new TaskParameterExtractor().extractParameters(events, intent);
757
- const taskName = await this.#generateTaskName(events, intent);
758
- const estimatedDuration = this.#estimateDuration(events);
759
- return {
760
- id: TaskHistory.generateId(),
761
- name: taskName,
762
- intent: {
763
- ...intent,
764
- parameters,
765
- description: intent.context.semanticContext || "",
766
- taskName
767
- },
768
- events,
769
- createdAt: Date.now(),
770
- eventCount: events.length,
771
- estimatedDuration
772
- };
773
- }
774
- /**
775
- * Generate task name from events and intent
776
- */
777
- async #generateTaskName(events, intent) {
778
- const baseName = {
779
- login: "登录系统",
780
- search: "搜索内容",
781
- fill_form: "填写表单",
782
- navigate: "浏览页面",
783
- data_entry: "录入数据",
784
- batch_operation: "批量操作",
785
- verification: "验证操作",
786
- download: "下载文件",
787
- upload: "上传文件",
788
- other: "录制的操作"
789
- }[intent.action] || "录制的操作";
790
- for (const event of events) if (event.type === "click" && event.elementInfo?.textContent) {
791
- const text = event.elementInfo.textContent.trim();
792
- if (text && text.length < 30) return `${baseName}: ${text}`;
793
- }
794
- return baseName;
795
- }
796
- /**
797
- * Estimate duration from events
798
- */
799
- #estimateDuration(events) {
800
- if (events.length < 2) return 1e3;
801
- const firstEvent = events[0];
802
- return events[events.length - 1].timestamp - firstEvent.timestamp;
803
- }
804
- /**
805
- * Show loading state
806
- */
807
- #showLoadingState(message) {
808
- this.#currentOverlay?.remove();
809
- const overlay = document.createElement("div");
810
- overlay.className = RecordingPanel_module_default.exportOverlay;
811
- const content = document.createElement("div");
812
- content.className = RecordingPanel_module_default.loadingContent;
813
- content.innerHTML = `
814
- <div class="${RecordingPanel_module_default.loadingSpinner}"></div>
815
- <p>${message}</p>
816
- `;
817
- overlay.appendChild(content);
818
- document.body.appendChild(overlay);
819
- this.#currentOverlay = overlay;
820
- }
821
- /**
822
- * Show task card
823
- */
824
- #showTaskCard(task) {
825
- this.#currentOverlay?.remove();
826
- const overlay = document.createElement("div");
827
- overlay.className = RecordingPanel_module_default.exportOverlay;
828
- const cardElement = createTaskCard(task, {
829
- onReplay: (t) => {
830
- overlay.remove();
831
- this.#handleReplay(t);
832
- },
833
- onExport: (t) => {
834
- overlay.remove();
835
- this.#handleExport(t);
836
- },
837
- onEdit: (t) => {
838
- overlay.remove();
839
- this.#handleEdit(t);
840
- }
841
- });
842
- const dialog = document.createElement("div");
843
- dialog.className = RecordingPanel_module_default.taskCardDialog;
844
- dialog.appendChild(cardElement);
845
- overlay.appendChild(dialog);
846
- document.body.appendChild(overlay);
847
- this.#currentOverlay = overlay;
848
- overlay.addEventListener("click", (e) => {
849
- if (e.target === overlay) overlay.remove();
850
- });
851
- }
852
- /**
853
- * Handle replay action
854
- */
855
- #handleReplay(task) {
856
- console.log("[RecordingPanel] Replay task:", task.name);
857
- alert(`重放功能即将推出: ${task.name}`);
858
- }
859
- /**
860
- * Handle export action
861
- */
862
- #handleExport(task) {
863
- this.#downloadRecording(task.events, "json");
864
- }
865
- /**
866
- * Handle edit action
867
- */
868
- #handleEdit(task) {
869
- console.log("[RecordingPanel] Edit task:", task.name);
870
- alert(`编辑功能即将推出: ${task.name}`);
871
- }
872
- /**
873
- * Show export dialog for recorded events
874
- */
875
- #showExportDialog(events) {
876
- return new Promise((resolve) => {
877
- const overlay = document.createElement("div");
878
- overlay.className = RecordingPanel_module_default.exportOverlay;
879
- const dialog = document.createElement("div");
880
- dialog.className = RecordingPanel_module_default.exportDialog;
881
- const header = document.createElement("div");
882
- header.className = RecordingPanel_module_default.exportDialogHeader;
883
- header.innerHTML = `
884
- <h3>${this.#i18n.t("ui.panel.exportRecording")}</h3>
885
- <button class="${RecordingPanel_module_default.closeButton}">×</button>
886
- `;
887
- dialog.appendChild(header);
888
- const content = document.createElement("div");
889
- content.className = RecordingPanel_module_default.exportDialogContent;
890
- content.innerHTML = `
891
- <p>${this.#i18n.t("ui.panel.recordedEvents", { count: events.length })}</p>
892
- <div class="${RecordingPanel_module_default.exportOptions}">
893
- <button data-format="json" class="${RecordingPanel_module_default.exportOptionButton}">
894
- <div class="${RecordingPanel_module_default.exportOptionIcon}">{}</div>
895
- <div class="${RecordingPanel_module_default.exportOptionLabel}">JSON</div>
896
- </button>
897
- <button data-format="markdown" class="${RecordingPanel_module_default.exportOptionButton}">
898
- <div class="${RecordingPanel_module_default.exportOptionIcon}">M↓</div>
899
- <div class="${RecordingPanel_module_default.exportOptionLabel}">Markdown</div>
900
- </button>
901
- <button data-format="playwright" class="${RecordingPanel_module_default.exportOptionButton}">
902
- <div class="${RecordingPanel_module_default.exportOptionIcon}">🎭</div>
903
- <div class="${RecordingPanel_module_default.exportOptionLabel}">Playwright</div>
904
- </button>
905
- </div>
906
- `;
907
- dialog.appendChild(content);
908
- overlay.appendChild(dialog);
909
- document.body.appendChild(overlay);
910
- const closeBtn = header.querySelector(`.${RecordingPanel_module_default.closeButton}`);
911
- const closeDialog = () => {
912
- overlay.remove();
913
- resolve();
914
- };
915
- closeBtn.addEventListener("click", closeDialog);
916
- content.querySelectorAll(`.${RecordingPanel_module_default.exportOptionButton}`).forEach((button) => {
917
- button.addEventListener("click", () => {
918
- const format = button.getAttribute("data-format");
919
- this.#downloadRecording(events, format);
920
- closeDialog();
921
- });
922
- });
923
- overlay.addEventListener("click", (e) => {
924
- if (e.target === overlay) closeDialog();
925
- });
926
- });
927
- }
928
- /**
929
- * Download recording as file
930
- */
931
- #downloadRecording(events, format) {
932
- let content;
933
- let filename;
934
- let mimeType;
935
- switch (format) {
936
- case "json":
937
- content = this.#recorder.export("json", events);
938
- filename = `recording-${Date.now()}.json`;
939
- mimeType = "application/json";
940
- break;
941
- case "markdown":
942
- content = this.#recorder.export("markdown", events);
943
- filename = `recording-${Date.now()}.md`;
944
- mimeType = "text/markdown";
945
- break;
946
- case "playwright":
947
- content = this.#recorder.export("playwright", events);
948
- filename = `test-${Date.now()}.spec.ts`;
949
- mimeType = "text/typescript";
950
- break;
951
- default: return;
952
- }
953
- const blob = new Blob([content], { type: mimeType });
954
- const url = URL.createObjectURL(blob);
955
- const link = document.createElement("a");
956
- link.href = url;
957
- link.download = filename;
958
- document.body.appendChild(link);
959
- link.click();
960
- document.body.removeChild(link);
961
- URL.revokeObjectURL(url);
962
- }
963
- /**
964
- * Setup settings button in header controls
965
- */
966
- #setupSettingsUI() {
967
- const header = this.wrapper.children[2];
968
- if (!header) return;
969
- const controls = header.children[1];
970
- if (!controls) return;
971
- const buttons = controls.querySelectorAll("button");
972
- if (buttons.length < 1) return;
973
- const existingButton = buttons[0];
974
- const baseClass = Array.from(existingButton.classList).find((c) => c.includes("controlButton") || c.includes("expandButton")) || existingButton.className;
975
- this.#settingsButton = document.createElement("button");
976
- this.#settingsButton.className = baseClass;
977
- this.#settingsButton.innerHTML = "⚙";
978
- this.#settingsButton.title = "Settings";
979
- this.#settingsButton.setAttribute("data-page-agent-ignore", "true");
980
- this.#settingsButton.setAttribute("data-browser-use-ignore", "true");
981
- this.#settingsButton.style.fontSize = "16px";
982
- this.#settingsButton.addEventListener("click", (e) => {
983
- e.stopPropagation();
984
- this.#showSettingsDialog();
985
- });
986
- controls.insertBefore(this.#settingsButton, controls.firstChild);
987
- }
988
- /**
989
- * Show settings dialog
990
- */
991
- #showSettingsDialog() {
992
- this.#currentOverlay?.remove();
993
- const overlay = document.createElement("div");
994
- overlay.className = RecordingPanel_module_default.settingsOverlay || RecordingPanel_module_default.exportOverlay;
995
- const dialog = document.createElement("div");
996
- dialog.className = RecordingPanel_module_default.settingsDialog || RecordingPanel_module_default.exportDialog;
997
- const header = document.createElement("div");
998
- header.className = RecordingPanel_module_default.settingsDialogHeader || RecordingPanel_module_default.exportDialogHeader;
999
- header.innerHTML = `
1000
- <h3>Settings</h3>
1001
- <button class="${RecordingPanel_module_default.closeButton}">×</button>
1002
- `;
1003
- dialog.appendChild(header);
1004
- const content = document.createElement("div");
1005
- content.className = RecordingPanel_module_default.settingsDialogContent || RecordingPanel_module_default.exportDialogContent;
1006
- const currentConfig = this.#loadConfigFromStorage();
1007
- content.innerHTML = `
1008
- <div class="${RecordingPanel_module_default.settingsField}">
1009
- <label class="${RecordingPanel_module_default.settingsLabel}">Model</label>
1010
- <input type="text" class="${RecordingPanel_module_default.settingsInput}" id="settings-model"
1011
- value="${this.#escapeHtml(currentConfig?.model || "")}"
1012
- placeholder="deepseek-v4-flash" />
1013
- </div>
1014
- <div class="${RecordingPanel_module_default.settingsField}">
1015
- <label class="${RecordingPanel_module_default.settingsLabel}">API Base URL</label>
1016
- <input type="text" class="${RecordingPanel_module_default.settingsInput}" id="settings-baseurl"
1017
- value="${this.#escapeHtml(currentConfig?.baseURL || "")}"
1018
- placeholder="https://api.deepseek.com" />
1019
- </div>
1020
- <div class="${RecordingPanel_module_default.settingsField}">
1021
- <label class="${RecordingPanel_module_default.settingsLabel}">API Key (optional)</label>
1022
- <input type="password" class="${RecordingPanel_module_default.settingsInput}" id="settings-apikey"
1023
- value="${this.#escapeHtml(currentConfig?.apiKey || "")}"
1024
- placeholder="••••••••" />
1025
- </div>
1026
- `;
1027
- const buttonRow = document.createElement("div");
1028
- buttonRow.style.cssText = "display: flex; gap: 8px; justify-content: flex-end; padding: 20px;";
1029
- buttonRow.innerHTML = `
1030
- <button id="settings-cancel" class="${RecordingPanel_module_default.btnSecondary}">Cancel</button>
1031
- <button id="settings-save" class="${RecordingPanel_module_default.btnPrimary}">Save</button>
1032
- `;
1033
- dialog.appendChild(content);
1034
- dialog.appendChild(buttonRow);
1035
- overlay.appendChild(dialog);
1036
- document.body.appendChild(overlay);
1037
- this.#currentOverlay = overlay;
1038
- const closeBtn = header.querySelector(`.${RecordingPanel_module_default.closeButton}`);
1039
- const closeDialog = () => {
1040
- overlay.remove();
1041
- };
1042
- closeBtn.addEventListener("click", closeDialog);
1043
- buttonRow.querySelector("#settings-cancel").addEventListener("click", closeDialog);
1044
- buttonRow.querySelector("#settings-save").addEventListener("click", () => {
1045
- const model = document.getElementById("settings-model").value.trim();
1046
- const baseURL = document.getElementById("settings-baseurl").value.trim();
1047
- const apiKey = document.getElementById("settings-apikey").value.trim();
1048
- if (!model || !baseURL) {
1049
- alert("Please fill in Model and Base URL");
1050
- return;
1051
- }
1052
- this.#saveConfigToStorage({
1053
- model,
1054
- baseURL,
1055
- apiKey
1056
- });
1057
- closeDialog();
1058
- console.log("Settings saved:", {
1059
- model,
1060
- baseURL,
1061
- apiKey: "••••"
1062
- });
1063
- });
1064
- overlay.addEventListener("click", (e) => {
1065
- if (e.target === overlay) closeDialog();
1066
- });
1067
- }
1068
- /**
1069
- * Load config from localStorage
1070
- */
1071
- #loadConfigFromStorage() {
1072
- try {
1073
- const saved = localStorage.getItem("page-agent-demo-llm-config");
1074
- return saved ? JSON.parse(saved) : null;
1075
- } catch {
1076
- return null;
1077
- }
1078
- }
1079
- /**
1080
- * Save config to localStorage
1081
- */
1082
- #saveConfigToStorage(config) {
1083
- localStorage.setItem("page-agent-demo-llm-config", JSON.stringify(config));
1084
- }
1085
- #escapeHtml(str) {
1086
- const d = document.createElement("div");
1087
- d.textContent = str;
1088
- return d.innerHTML;
1089
- }
1090
- /**
1091
- * Dispose panel and clean up event listeners
1092
- */
1093
- dispose() {
1094
- this.#stopRecordingUpdateLoop();
1095
- this.#currentOverlay?.remove();
1096
- if (this.#isTestMode) this.#hideTestPanel();
1097
- super.dispose();
1098
- }
1099
- };
1100
- //#endregion
1101
- //#region src/PageAgent.ts
1102
- var PageAgent = class extends PageAgentCore {
1103
- panel;
1104
- recorder;
1105
- constructor(config) {
1106
- const pageController = new PageController({
1107
- ...config,
1108
- enableMask: config.enableMask ?? true
1109
- });
1110
- super({
1111
- ...config,
1112
- pageController
1113
- });
1114
- this.recorder = new ActionRecorder({ pageController });
1115
- this.panel = new RecordingPanel(this, {
1116
- language: config.language,
1117
- recorder: this.recorder,
1118
- promptForNextTask: config.promptForNextTask,
1119
- onGenerateTestDSL: (desc) => this.generateTestDSL(desc),
1120
- onRunTest: (dsl) => this.runTest(dsl)
1121
- });
1122
- }
1123
- /**
1124
- * Start recording user interactions
1125
- */
1126
- startRecording() {
1127
- this.recorder.start();
1128
- }
1129
- /**
1130
- * Stop recording and return captured events
1131
- */
1132
- stopRecording() {
1133
- return this.recorder.stop();
1134
- }
1135
- /**
1136
- * Export recording in specified format
1137
- */
1138
- exportRecording(format) {
1139
- return this.recorder.export(format);
1140
- }
1141
- /**
1142
- * Get recording status
1143
- */
1144
- getRecordingStatus() {
1145
- return this.recorder.getStatus();
1146
- }
1147
- /**
1148
- * Get all recorded events
1149
- */
1150
- getRecordedEvents() {
1151
- return this.recorder.getEvents();
1152
- }
1153
- /**
1154
- * Clear all recorded events
1155
- */
1156
- clearRecording() {
1157
- this.recorder.clear();
1158
- }
1159
- /**
1160
- * Get the recognized intent of the recording
1161
- */
1162
- getRecordingIntent() {
1163
- return this.recorder.getIntent();
1164
- }
1165
- /**
1166
- * Replay recorded events
1167
- * @param events - Optional events to replay (uses current recording if not provided)
1168
- * @param config - Optional replay configuration
1169
- */
1170
- async replayRecording(events, config) {
1171
- return await this.recorder.replay(events, config);
1172
- }
1173
- /**
1174
- * Load events from a JSON file and replay them
1175
- * @param filePath - Path to the recording file
1176
- * @param config - Optional replay configuration
1177
- */
1178
- async replayRecordingFile(filePath, config) {
1179
- return await this.recorder.replayFile(filePath, config);
1180
- }
1181
- async generateTestDSL(description) {
1182
- return `web:
1183
- url: ${(await this.pageController.getBrowserState()).url}
1184
-
1185
- tasks:
1186
- - name: ${description.slice(0, 50)}
1187
- flow:
1188
- - ai: ${description}
1189
- - sleep: 2000
1190
- - aiAssert: The action completed successfully
1191
- `;
1192
- }
1193
- async runTest(dsl) {
1194
- const parseResult = parseTestSpec(dsl);
1195
- if (!parseResult.success || !parseResult.spec) throw new Error(`Invalid test DSL: ${parseResult.errors?.join(", ")}`);
1196
- const spec = parseResult.spec;
1197
- this.tools.set("assert", assert);
1198
- this.tools.set("query", query);
1199
- this.tools.set("wait_for_condition", wait_for_condition);
1200
- try {
1201
- const prompt = testSpecToPrompt(spec);
1202
- return historyToReport((await this.execute(prompt)).history, spec);
1203
- } finally {
1204
- this.tools.delete("assert");
1205
- this.tools.delete("query");
1206
- this.tools.delete("wait_for_condition");
1207
- }
1208
- }
1209
- saveTestSpec(name, dsl) {
1210
- const key = "page-agent-test-specs";
1211
- const saved = JSON.parse(localStorage.getItem(key) || "[]");
1212
- saved.push({
1213
- name,
1214
- dsl
1215
- });
1216
- localStorage.setItem(key, JSON.stringify(saved));
1217
- }
1218
- loadTestSpecs() {
1219
- try {
1220
- return JSON.parse(localStorage.getItem("page-agent-test-specs") || "[]");
1221
- } catch {
1222
- return [];
1223
- }
1224
- }
1225
- };
1226
- //#endregion
1227
- export { PageAgent, RecordingPanel };
1228
-
1229
- //# sourceMappingURL=page-agent.js.map