@stv/page-test 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/PageAgent.d.ts +102 -0
- package/dist/esm/page-agent.js +1229 -0
- package/dist/esm/page-agent.js.map +1 -0
- package/dist/iife/page-agent.demo.js +16587 -0
- package/dist/iife/page-agent.demo.js.map +1 -0
- package/package.json +19 -0
|
@@ -0,0 +1,1229 @@
|
|
|
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
|