@stv/page-test 1.0.0 → 1.0.1
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/iife/{page-agent.demo.js → page-test.demo.js} +2150 -1938
- package/dist/iife/page-test.demo.js.map +1 -0
- package/package.json +1 -1
- package/dist/esm/PageAgent.d.ts +0 -102
- package/dist/esm/page-agent.js +0 -1229
- package/dist/esm/page-agent.js.map +0 -1
- package/dist/iife/page-agent.demo.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"page-agent.js","names":["#task","#onReplay","#onExport","#onEdit","#renderHeader","#renderStats","#renderParameters","#renderActions","#getIntentIcon","#formatDuration","#onTaskDelete","#onReplay","#onExport","#onEdit","#loadFromStorage","#tasks","#saveToStorage","#renderHeader","#renderFilters","#renderTaskList","#refreshTaskList","#recorder","#config","#i18n","#taskHistory","#handleReplay","#handleExport","#handleEdit","#setupRecordingUI","#setupTestModeUI","#setupSettingsUI","#recordingButton","#eventCounter","#toggleRecording","#updateRecordingUI","#processRecording","#testModeButton","#toggleTestMode","#isTestMode","#showTestPanel","#hideTestPanel","#testContainer","#dslEditor","#createTestBtn","#handleTestRun","#handleTestSave","#handleTestExport","#startRecordingUpdateLoop","#stopRecordingUpdateLoop","#recordingUpdateTimer","#showLoadingState","#analyzeIntent","#showTaskCard","#showExportDialog","#generateTaskName","#estimateDuration","#currentOverlay","#downloadRecording","#settingsButton","#showSettingsDialog","#loadConfigFromStorage","#escapeHtml","#saveConfigToStorage"],"sources":["../../src/RecordingPanel.module.css","../../src/TaskCard.ts","../../src/TaskHistory.ts","../../src/RecordingPanel.ts","../../src/PageAgent.ts"],"sourcesContent":["/* Recording-specific styles for RecordingPanel */\n/* These styles are extracted from @page-agent/ui/panel/Panel.module.css */\n\n.recordingButton {\n\tposition: relative;\n\ttransition: all 0.2s ease;\n}\n\n/* Placeholder - moved below controlButton definition for proper cascade */\n\n.eventCounter {\n\tfont-size: 10px;\n\tcolor: rgba(255, 255, 255, 0.7);\n\tmin-width: 16px;\n\ttext-align: center;\n\tpadding: 0 4px;\n}\n\n@keyframes recordingPulse {\n\t0%,\n\t100% {\n\t\topacity: 1;\n\t\ttransform: scale(1);\n\t}\n\t50% {\n\t\topacity: 0.7;\n\t\ttransform: scale(1.1);\n\t}\n}\n\n.exportOverlay {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: rgba(0, 0, 0, 0.5);\n\tbackdrop-filter: blur(4px);\n\tz-index: 2147483647;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tanimation: overlayFadeIn 0.2s ease;\n}\n\n@keyframes overlayFadeIn {\n\tfrom {\n\t\topacity: 0;\n\t}\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n.exportDialog {\n\tbackground: rgba(30, 30, 30, 0.95);\n\tbackdrop-filter: blur(20px);\n\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\tborder-radius: 16px;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n\tmax-width: 480px;\n\twidth: 90%;\n\tanimation: dialogSlideIn 0.3s ease;\n}\n\n@keyframes dialogSlideIn {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(20px) scale(0.95);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0) scale(1);\n\t}\n}\n\n.exportDialogHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 20px 24px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.exportDialogHeader h3 {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tcolor: white;\n}\n\n.closeButton {\n\tbackground: none;\n\tborder: none;\n\tcolor: rgba(255, 255, 255, 0.6);\n\tfont-size: 24px;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 32px;\n\theight: 32px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 8px;\n\ttransition: all 0.2s;\n}\n\n.closeButton:hover {\n\tbackground: rgba(255, 255, 255, 0.1);\n\tcolor: white;\n}\n\n.exportDialogContent {\n\tpadding: 24px;\n}\n\n.exportDialogContent p {\n\tmargin: 0 0 20px 0;\n\tcolor: rgba(255, 255, 255, 0.8);\n\tfont-size: 14px;\n}\n\n.exportOptions {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(3, 1fr);\n\tgap: 12px;\n}\n\n.exportOptionButton {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 20px 16px;\n\tbackground: rgba(255, 255, 255, 0.05);\n\tborder: 2px solid rgba(255, 255, 255, 0.1);\n\tborder-radius: 12px;\n\tcursor: pointer;\n\ttransition: all 0.2s;\n\tcolor: white;\n\tfont-family: inherit;\n}\n\n.exportOptionButton:hover {\n\tbackground: rgba(255, 255, 255, 0.1);\n\tborder-color: rgba(255, 255, 255, 0.2);\n\ttransform: translateY(-2px);\n}\n\n.exportOptionIcon {\n\tfont-size: 28px;\n\topacity: 0.9;\n}\n\n.exportOptionLabel {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcolor: white;\n}\n\n/* Need to re-export controlButton and controls styles from Panel for the recording button to work */\n.controlButton {\n\tbackground: rgba(255, 255, 255, 0.1);\n\tborder: none;\n\tcolor: white;\n\tcursor: pointer;\n\tpadding: 8px;\n\tborder-radius: 8px;\n\ttransition: all 0.2s;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-width: 36px;\n\theight: 36px;\n\tfont-size: 14px;\n}\n\n.controlButton:hover {\n\tbackground: rgba(255, 255, 255, 0.2);\n}\n\n.controls {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n}\n\n/* Recording state - use !important to ensure priority over base styles */\n.recordingButton.recording {\n\tcolor: rgb(34, 197, 94) !important;\n\tanimation: recordingPulse 1.5s ease-in-out infinite;\n}\n\n/* ========== Phase 4: Task Cards ========== */\n\n.taskCard {\n\tbackground: rgba(30, 41, 59, 0.8);\n\tbackdrop-filter: blur(12px);\n\tborder: 1px solid rgba(148, 163, 184, 0.1);\n\tborder-radius: 12px;\n\tpadding: 16px;\n\tmargin-bottom: 12px;\n\ttransition: all 0.2s ease;\n}\n\n.taskCard:hover {\n\tborder-color: rgba(59, 130, 246, 0.3);\n\ttransform: translateY(-2px);\n\tbox-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);\n}\n\n.taskCardHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmargin-bottom: 12px;\n}\n\n.taskIcon {\n\tfont-size: 24px;\n}\n\n.taskName {\n\tflex: 1;\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: white;\n}\n\n.confidenceBadge {\n\tbackground: linear-gradient(135deg, #10b981, #059669);\n\tcolor: white;\n\tpadding: 2px 8px;\n\tborder-radius: 12px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n}\n\n.taskStats {\n\tdisplay: flex;\n\tgap: 16px;\n\tmargin-bottom: 12px;\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.7);\n}\n\n.taskParameters {\n\tmargin-bottom: 16px;\n\tpadding: 12px;\n\tbackground: rgba(0, 0, 0, 0.2);\n\tborder-radius: 8px;\n}\n\n.taskParameters h4 {\n\tmargin: 0 0 8px 0;\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.8);\n}\n\n.parameterItem {\n\tdisplay: flex;\n\tgap: 8px;\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.7);\n\tmargin-bottom: 4px;\n}\n\n.paramKey {\n\tfont-weight: 600;\n\tcolor: rgba(255, 255, 255, 0.9);\n}\n\n.paramValue {\n\tcolor: rgba(255, 255, 255, 0.7);\n}\n\n.taskActions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.actionButton {\n\tflex: 1;\n\tpadding: 8px 16px;\n\tborder-radius: 6px;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.2s;\n}\n\n.btnPrimary {\n\tbackground: linear-gradient(135deg, #3b82f6, #2563eb);\n\tcolor: white;\n\tborder: none;\n}\n\n.btnPrimary:hover {\n\tbackground: linear-gradient(135deg, #2563eb, #1d4ed8);\n\ttransform: translateY(-1px);\n}\n\n.btnSecondary {\n\tbackground: rgba(255, 255, 255, 0.1);\n\tcolor: white;\n\tborder: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.btnSecondary:hover {\n\tbackground: rgba(255, 255, 255, 0.2);\n}\n\n/* ========== Phase 4: Task History ========== */\n\n.historyHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmargin-bottom: 16px;\n}\n\n.historyHeader h3 {\n\tflex: 1;\n\tmargin: 0;\n\tfont-size: 18px;\n\tcolor: white;\n}\n\n.searchInput {\n\tflex: 1;\n\tpadding: 8px 12px;\n\tbackground: rgba(255, 255, 255, 0.1);\n\tborder: 1px solid rgba(255, 255, 255, 0.2);\n\tborder-radius: 8px;\n\tcolor: white;\n\tfont-size: 14px;\n}\n\n.searchInput::placeholder {\n\tcolor: rgba(255, 255, 255, 0.5);\n}\n\n.filterTabs {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 16px;\n}\n\n.filterTab {\n\tpadding: 6px 12px;\n\tbackground: rgba(255, 255, 255, 0.1);\n\tborder: 1px solid rgba(255, 255, 255, 0.2);\n\tborder-radius: 6px;\n\tcolor: rgba(255, 255, 255, 0.7);\n\tfont-size: 12px;\n\tcursor: pointer;\n\ttransition: all 0.2s;\n}\n\n.filterTab:hover {\n\tbackground: rgba(255, 255, 255, 0.2);\n}\n\n.filterTab.active {\n\tbackground: rgba(59, 130, 246, 0.3);\n\tborder-color: rgba(59, 130, 246, 0.5);\n\tcolor: white;\n}\n\n.taskList {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmax-height: 400px;\n\toverflow-y: auto;\n}\n\n.emptyState {\n\ttext-align: center;\n\tpadding: 32px;\n\tcolor: rgba(255, 255, 255, 0.5);\n\tfont-size: 14px;\n}\n\n/* ========== Phase 4: Replay Panel ========== */\n\n.replayPanel {\n\tbackground: rgba(30, 41, 59, 0.95);\n\tbackdrop-filter: blur(20px);\n\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\tborder-radius: 16px;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n\tmax-width: 600px;\n\twidth: 90%;\n\tmax-height: 80vh;\n\toverflow-y: auto;\n}\n\n.replayHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 20px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.replayIcon {\n\tfont-size: 24px;\n}\n\n.replayHeader h3 {\n\tflex: 1;\n\tmargin: 0;\n\tfont-size: 18px;\n\tcolor: white;\n}\n\n.replayStatus {\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.7);\n}\n\n.replayStatus.success {\n\tcolor: #10b981;\n}\n\n.replayStatus.failed {\n\tcolor: #ef4444;\n}\n\n/* Event List */\n\n.eventList {\n\tpadding: 16px;\n\tmax-height: 300px;\n\toverflow-y: auto;\n}\n\n.eventItem {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px;\n\tborder-radius: 6px;\n\tmargin-bottom: 4px;\n\tfont-size: 13px;\n\tcolor: rgba(255, 255, 255, 0.8);\n\ttransition: all 0.2s;\n}\n\n.eventItem.statusPending {\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\n.eventItem.statusRunning {\n\tbackground: rgba(59, 130, 246, 0.2);\n\tanimation: pulse 1s infinite;\n}\n\n.eventItem.statusSuccess {\n\tbackground: rgba(16, 185, 129, 0.2);\n}\n\n.eventItem.statusFailed {\n\tbackground: rgba(239, 68, 68, 0.2);\n}\n\n.eventStatusIcon {\n\tfont-size: 16px;\n\twidth: 20px;\n\ttext-align: center;\n}\n\n.eventDescription {\n\tflex: 1;\n}\n\n.eventDuration {\n\tfont-size: 11px;\n\tcolor: rgba(255, 255, 255, 0.5);\n}\n\n@keyframes pulse {\n\t0%,\n\t100% {\n\t\topacity: 1;\n\t}\n\t50% {\n\t\topacity: 0.8;\n\t}\n}\n\n/* ========== Phase 4: Error Handler ========== */\n\n.errorDialog {\n\tbackground: rgba(30, 30, 30, 0.95);\n\tbackdrop-filter: blur(20px);\n\tborder: 1px solid rgba(239, 68, 68, 0.3);\n\tborder-radius: 16px;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n\tmax-width: 480px;\n\twidth: 90%;\n}\n\n.errorHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 20px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.errorIcon {\n\tfont-size: 24px;\n}\n\n.errorHeader h3 {\n\tflex: 1;\n\tmargin: 0;\n\tfont-size: 18px;\n\tcolor: white;\n}\n\n.errorDetails {\n\tpadding: 20px;\n\tcolor: rgba(255, 255, 255, 0.8);\n\tfont-size: 14px;\n}\n\n.errorDetails p {\n\tmargin: 0 0 12px 0;\n}\n\n.errorDetails strong {\n\tcolor: white;\n}\n\n.errorCauses {\n\tpadding: 0 20px 20px;\n}\n\n.errorCauses h4 {\n\tmargin: 0 0 12px 0;\n\tfont-size: 14px;\n\tcolor: rgba(255, 255, 255, 0.8);\n}\n\n.errorCauses ul {\n\tmargin: 0;\n\tpadding-left: 20px;\n\tcolor: rgba(255, 255, 255, 0.7);\n\tfont-size: 13px;\n}\n\n.errorCauses li {\n\tmargin-bottom: 6px;\n}\n\n.errorActions {\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 20px;\n\tborder-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Replay Report ========== */\n\n.replayReport {\n\tbackground: rgba(30, 30, 30, 0.95);\n\tbackdrop-filter: blur(20px);\n\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\tborder-radius: 16px;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n\tmax-width: 480px;\n\twidth: 90%;\n}\n\n.replayReport.success {\n\tborder-color: rgba(16, 185, 129, 0.3);\n}\n\n.replayReport.failure {\n\tborder-color: rgba(239, 68, 68, 0.3);\n}\n\n.reportHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 20px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.reportIcon {\n\tfont-size: 24px;\n}\n\n.reportHeader h3 {\n\tflex: 1;\n\tmargin: 0;\n\tfont-size: 18px;\n\tcolor: white;\n}\n\n.reportStats {\n\tdisplay: flex;\n\tjustify-content: space-around;\n\tpadding: 20px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.statItem {\n\ttext-align: center;\n}\n\n.statLabel {\n\tdisplay: block;\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.7);\n\tmargin-bottom: 4px;\n}\n\n.statValue {\n\tdisplay: block;\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tcolor: white;\n}\n\n.reportDetails {\n\tpadding: 20px;\n\tmax-height: 200px;\n\toverflow-y: auto;\n}\n\n.reportDetails h4 {\n\tmargin: 0 0 12px 0;\n\tfont-size: 14px;\n\tcolor: rgba(255, 255, 255, 0.8);\n}\n\n.eventResult {\n\tpadding: 8px;\n\tmargin-bottom: 4px;\n\tborder-radius: 6px;\n\tfont-size: 12px;\n\tcolor: rgba(255, 255, 255, 0.8);\n}\n\n.eventResult.statusSuccess {\n\tbackground: rgba(16, 185, 129, 0.2);\n}\n\n.eventResult.statusFailed {\n\tbackground: rgba(239, 68, 68, 0.2);\n}\n\n.reportActions {\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 20px;\n\tborder-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/* ========== Phase 4: Loading States ========== */\n\n.loadingContent {\n\ttext-align: center;\n\tpadding: 32px;\n\tcolor: white;\n}\n\n.loadingSpinner {\n\twidth: 40px;\n\theight: 40px;\n\tmargin: 0 auto 16px;\n\tborder: 3px solid rgba(255, 255, 255, 0.2);\n\tborder-top-color: white;\n\tborder-radius: 50%;\n\tanimation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n\tto {\n\t\ttransform: rotate(360deg);\n\t}\n}\n\n.loadingContent p {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: rgba(255, 255, 255, 0.8);\n}\n\n/* ========== Phase 4: Task Card Dialog ========== */\n\n.taskCardDialog {\n\tpadding: 20px;\n}\n\n/* ========== Settings ========== */\n\n.settingsOverlay {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: rgba(0, 0, 0, 0.5);\n\tbackdrop-filter: blur(4px);\n\tz-index: 2147483647;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tanimation: overlayFadeIn 0.2s ease;\n}\n\n.settingsDialog {\n\tbackground: rgba(30, 30, 30, 0.95);\n\tbackdrop-filter: blur(20px);\n\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\tborder-radius: 16px;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n\tmax-width: 480px;\n\twidth: 90%;\n\tanimation: dialogSlideIn 0.3s ease;\n}\n\n.settingsDialogHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 20px 24px;\n\tborder-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.settingsDialogHeader h3 {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tcolor: white;\n}\n\n.settingsDialogContent {\n\tpadding: 24px;\n}\n\n.settingsField {\n\tmargin-bottom: 20px;\n}\n\n.settingsLabel {\n\tdisplay: block;\n\tmargin-bottom: 8px;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcolor: rgba(255, 255, 255, 0.9);\n}\n\n.settingsInput {\n\twidth: 100%;\n\tpadding: 10px 12px;\n\tbackground: rgba(255, 255, 255, 0.05);\n\tborder: 1px solid rgba(255, 255, 255, 0.15);\n\tborder-radius: 8px;\n\tcolor: white;\n\tfont-size: 14px;\n\tfont-family: inherit;\n\tbox-sizing: border-box;\n\ttransition: all 0.2s;\n}\n\n.settingsInput::placeholder {\n\tcolor: rgba(255, 255, 255, 0.4);\n}\n\n.settingsInput:focus {\n\toutline: none;\n\tbackground: rgba(255, 255, 255, 0.08);\n\tborder-color: rgba(59, 130, 246, 0.5);\n}\n\n.settingsInput[type='password'] {\n\t-webkit-text-security: disc;\n}\n","import type { TaskCard as TaskCardData } from '@page-agent/recorder'\n\nimport styles from './RecordingPanel.module.css'\n\n/**\n * TaskCard - Display a single recorded task with intent and actions\n *\n * Shows task name, intent confidence, parameters, and provides\n * replay, export, and edit actions.\n */\nexport class TaskCard {\n\t#task: TaskCardData\n\t#onReplay?: (task: TaskCardData) => void\n\t#onExport?: (task: TaskCardData) => void\n\t#onEdit?: (task: TaskCardData) => void\n\n\tconstructor(\n\t\ttask: TaskCardData,\n\t\tcallbacks?: {\n\t\t\tonReplay?: (task: TaskCardData) => void\n\t\t\tonExport?: (task: TaskCardData) => void\n\t\t\tonEdit?: (task: TaskCardData) => void\n\t\t}\n\t) {\n\t\tthis.#task = task\n\t\tthis.#onReplay = callbacks?.onReplay\n\t\tthis.#onExport = callbacks?.onExport\n\t\tthis.#onEdit = callbacks?.onEdit\n\t}\n\n\t/**\n\t * Render task card element\n\t */\n\trender(): HTMLElement {\n\t\tconst card = document.createElement('div')\n\t\tcard.className = styles.taskCard\n\t\tcard.dataset.taskId = this.#task.id\n\n\t\t// Header with icon, name, and confidence\n\t\tconst header = this.#renderHeader()\n\t\tcard.appendChild(header)\n\n\t\t// Stats (event count, duration)\n\t\tconst stats = this.#renderStats()\n\t\tcard.appendChild(stats)\n\n\t\t// Parameters (if any)\n\t\tif (Object.keys(this.#task.intent.parameters.values).length > 0) {\n\t\t\tconst params = this.#renderParameters()\n\t\t\tcard.appendChild(params)\n\t\t}\n\n\t\t// Action buttons\n\t\tconst actions = this.#renderActions()\n\t\tcard.appendChild(actions)\n\n\t\treturn card\n\t}\n\n\t/**\n\t * Render card header\n\t */\n\t#renderHeader(): HTMLElement {\n\t\tconst header = document.createElement('div')\n\t\theader.className = styles.taskCardHeader\n\n\t\t// Intent icon\n\t\tconst icon = document.createElement('span')\n\t\ticon.className = styles.taskIcon\n\t\ticon.textContent = this.#getIntentIcon(this.#task.intent.action)\n\t\theader.appendChild(icon)\n\n\t\t// Task name\n\t\tconst name = document.createElement('h3')\n\t\tname.className = styles.taskName\n\t\tname.textContent = this.#task.name\n\t\theader.appendChild(name)\n\n\t\t// Confidence badge\n\t\tconst confidence = document.createElement('span')\n\t\tconfidence.className = styles.confidenceBadge\n\t\tconst confidencePercent = Math.round(this.#task.intent.confidence * 100)\n\t\tconfidence.textContent = `${confidencePercent}%`\n\t\tconfidence.title = `置信度: ${confidencePercent}%`\n\t\theader.appendChild(confidence)\n\n\t\treturn header\n\t}\n\n\t/**\n\t * Render task statistics\n\t */\n\t#renderStats(): HTMLElement {\n\t\tconst stats = document.createElement('div')\n\t\tstats.className = styles.taskStats\n\n\t\t// Event count\n\t\tconst eventCount = document.createElement('span')\n\t\teventCount.textContent = `${this.#task.eventCount} 个操作`\n\t\tstats.appendChild(eventCount)\n\n\t\t// Estimated duration\n\t\tif (this.#task.estimatedDuration) {\n\t\t\tconst duration = document.createElement('span')\n\t\t\tduration.textContent = `预计 ${this.#formatDuration(this.#task.estimatedDuration)}`\n\t\t\tstats.appendChild(duration)\n\t\t}\n\n\t\treturn stats\n\t}\n\n\t/**\n\t * Render parameters section\n\t */\n\t#renderParameters(): HTMLElement {\n\t\tconst params = document.createElement('div')\n\t\tparams.className = styles.taskParameters\n\n\t\tconst label = document.createElement('h4')\n\t\tlabel.textContent = '参数:'\n\t\tparams.appendChild(label)\n\n\t\tfor (const [key, value] of Object.entries(this.#task.intent.parameters.values)) {\n\t\t\tconst paramItem = document.createElement('div')\n\t\t\tparamItem.className = styles.parameterItem\n\n\t\t\tconst keySpan = document.createElement('span')\n\t\t\tkeySpan.className = styles.paramKey\n\t\t\tkeySpan.textContent = `${key}:`\n\t\t\tparamItem.appendChild(keySpan)\n\n\t\t\tconst valueSpan = document.createElement('span')\n\t\t\tvalueSpan.className = styles.paramValue\n\t\t\tvalueSpan.textContent = String(value)\n\t\t\tparamItem.appendChild(valueSpan)\n\n\t\t\tparams.appendChild(paramItem)\n\t\t}\n\n\t\treturn params\n\t}\n\n\t/**\n\t * Render action buttons\n\t */\n\t#renderActions(): HTMLElement {\n\t\tconst actions = document.createElement('div')\n\t\tactions.className = styles.taskActions\n\n\t\t// Replay button\n\t\tconst replayBtn = document.createElement('button')\n\t\treplayBtn.className = `${styles.btnPrimary} ${styles.actionButton}`\n\t\treplayBtn.dataset.action = 'replay'\n\t\treplayBtn.textContent = '▶ 立即重放'\n\t\treplayBtn.onclick = () => this.#onReplay?.(this.#task)\n\t\tactions.appendChild(replayBtn)\n\n\t\t// Export button\n\t\tconst exportBtn = document.createElement('button')\n\t\texportBtn.className = `${styles.btnSecondary} ${styles.actionButton}`\n\t\texportBtn.dataset.action = 'export'\n\t\texportBtn.textContent = '📤 导出'\n\t\texportBtn.onclick = () => this.#onExport?.(this.#task)\n\t\tactions.appendChild(exportBtn)\n\n\t\t// Edit button\n\t\tconst editBtn = document.createElement('button')\n\t\teditBtn.className = `${styles.btnSecondary} ${styles.actionButton}`\n\t\teditBtn.dataset.action = 'edit'\n\t\teditBtn.textContent = '✏ 编辑'\n\t\teditBtn.onclick = () => this.#onEdit?.(this.#task)\n\t\tactions.appendChild(editBtn)\n\n\t\treturn actions\n\t}\n\n\t/**\n\t * Get intent icon\n\t */\n\t#getIntentIcon(intent: string): string {\n\t\tconst icons: Record<string, string> = {\n\t\t\tlogin: '🔐',\n\t\t\tsearch: '🔍',\n\t\t\tfill_form: '📝',\n\t\t\tnavigate: '🧭',\n\t\t\tdata_entry: '⌨️',\n\t\t\tbatch_operation: '🔄',\n\t\t\tverification: '✅',\n\t\t\tdownload: '⬇️',\n\t\t\tupload: '⬆️',\n\t\t\tselect: '☑️',\n\t\t\tscroll: '📜',\n\t\t\tinput_text: '💬',\n\t\t\tclick_button: '👆',\n\t\t\tother: '📋',\n\t\t}\n\t\treturn icons[intent] || '📋'\n\t}\n\n\t/**\n\t * Format duration in human-readable format\n\t */\n\t#formatDuration(ms: number): string {\n\t\tconst seconds = Math.round(ms / 1000)\n\t\tif (seconds < 60) {\n\t\t\treturn `${seconds}秒`\n\t\t}\n\t\tconst minutes = Math.floor(seconds / 60)\n\t\tconst remainingSeconds = seconds % 60\n\t\treturn `${minutes}分${remainingSeconds}秒`\n\t}\n}\n\n/**\n * Create a task card element\n */\nexport function createTaskCard(\n\ttask: TaskCardData,\n\tcallbacks?: {\n\t\tonReplay?: (task: TaskCardData) => void\n\t\tonExport?: (task: TaskCardData) => void\n\t\tonEdit?: (task: TaskCardData) => void\n\t}\n): HTMLElement {\n\tconst card = new TaskCard(task, callbacks)\n\treturn card.render()\n}\n","import type { IntentType } from '@page-agent/recorder'\nimport type { AIIntentResult, IntentLabel, RecordedEvent } from '@page-agent/recorder'\n\nimport { createTaskCard } from './TaskCard.js'\n\nimport styles from './RecordingPanel.module.css'\n\n/**\n * Local TaskCard type to avoid circular dependency\n * This matches the TaskCard type from @page-agent/recorder\n */\ninterface TaskCardData {\n\tid: string\n\tname: string\n\tintent: AIIntentResult\n\tevents: RecordedEvent[]\n\tcreatedAt: number\n\teventCount: number\n\testimatedDuration?: number\n}\n\n/**\n * Storage key for localStorage\n */\nconst STORAGE_KEY = 'page-agent-tasks'\n\n/**\n * TaskHistory - Manage and display list of recorded tasks\n *\n * Provides task persistence, search, filtering, and rendering\n * of the task history list.\n */\nexport class TaskHistory {\n\t#tasks: TaskCardData[] = []\n\t#onTaskDelete?: (taskId: string) => void\n\t#onReplay?: (task: TaskCardData) => void\n\t#onExport?: (task: TaskCardData) => void\n\t#onEdit?: (task: TaskCardData) => void\n\n\tconstructor(callbacks?: {\n\t\tonTaskDelete?: (taskId: string) => void\n\t\tonReplay?: (task: TaskCardData) => void\n\t\tonExport?: (task: TaskCardData) => void\n\t\tonEdit?: (task: TaskCardData) => void\n\t}) {\n\t\tthis.#onTaskDelete = callbacks?.onTaskDelete\n\t\tthis.#onReplay = callbacks?.onReplay\n\t\tthis.#onExport = callbacks?.onExport\n\t\tthis.#onEdit = callbacks?.onEdit\n\n\t\t// Load tasks from storage\n\t\tthis.#loadFromStorage()\n\t}\n\n\t/**\n\t * Add new task to history\n\t */\n\taddTask(task: TaskCardData): void {\n\t\t// Add to beginning of list\n\t\tthis.#tasks.unshift(task)\n\n\t\t// Save to storage\n\t\tthis.#saveToStorage()\n\n\t\t// Limit history size (keep last 50 tasks)\n\t\tif (this.#tasks.length > 50) {\n\t\t\tthis.#tasks = this.#tasks.slice(0, 50)\n\t\t\tthis.#saveToStorage()\n\t\t}\n\t}\n\n\t/**\n\t * Get task by ID\n\t */\n\tgetTask(id: string): TaskCardData | undefined {\n\t\treturn this.#tasks.find((t) => t.id === id)\n\t}\n\n\t/**\n\t * Delete task\n\t */\n\tdeleteTask(id: string): void {\n\t\tthis.#tasks = this.#tasks.filter((t) => t.id !== id)\n\t\tthis.#saveToStorage()\n\t\tthis.#onTaskDelete?.(id)\n\t}\n\n\t/**\n\t * Search tasks by name/intent\n\t */\n\tsearchTasks(query: string): TaskCardData[] {\n\t\tconst lowerQuery = query.toLowerCase()\n\t\treturn this.#tasks.filter(\n\t\t\t(t) =>\n\t\t\t\tt.name.toLowerCase().includes(lowerQuery) ||\n\t\t\t\tt.intent.action.toLowerCase().includes(lowerQuery) ||\n\t\t\t\tt.intent.description.toLowerCase().includes(lowerQuery)\n\t\t)\n\t}\n\n\t/**\n\t * Filter by intent type\n\t */\n\tfilterByIntent(intent: IntentType): TaskCardData[] {\n\t\tif (intent === 'other') {\n\t\t\treturn this.#tasks\n\t\t}\n\t\treturn this.#tasks.filter((t) => t.intent.action === intent)\n\t}\n\n\t/**\n\t * Get all tasks\n\t */\n\tgetAllTasks(): TaskCardData[] {\n\t\treturn [...this.#tasks]\n\t}\n\n\t/**\n\t * Clear all tasks\n\t */\n\tclearAll(): void {\n\t\tthis.#tasks = []\n\t\tthis.#saveToStorage()\n\t}\n\n\t/**\n\t * Render history list\n\t */\n\trender(): DocumentFragment {\n\t\tconst fragment = document.createDocumentFragment()\n\n\t\t// Header with title and search\n\t\tconst header = this.#renderHeader()\n\t\tfragment.appendChild(header)\n\n\t\t// Filter tabs\n\t\tconst filters = this.#renderFilters()\n\t\tfragment.appendChild(filters)\n\n\t\t// Task list\n\t\tconst taskList = this.#renderTaskList()\n\t\tfragment.appendChild(taskList)\n\n\t\treturn fragment\n\t}\n\n\t/**\n\t * Render header\n\t */\n\t#renderHeader(): HTMLElement {\n\t\tconst header = document.createElement('div')\n\t\theader.className = styles.historyHeader\n\n\t\tconst title = document.createElement('h3')\n\t\ttitle.textContent = '📼 任务历史'\n\t\theader.appendChild(title)\n\n\t\tconst searchInput = document.createElement('input')\n\t\tsearchInput.type = 'text'\n\t\tsearchInput.className = styles.searchInput\n\t\tsearchInput.placeholder = '搜索任务...'\n\t\tsearchInput.oninput = (e) => {\n\t\t\tconst query = (e.target as HTMLInputElement).value\n\t\t\tthis.#refreshTaskList(query)\n\t\t}\n\t\theader.appendChild(searchInput)\n\n\t\treturn header\n\t}\n\n\t/**\n\t * Render filter tabs\n\t */\n\t#renderFilters(): HTMLElement {\n\t\tconst filters = document.createElement('div')\n\t\tfilters.className = styles.filterTabs\n\n\t\tconst filterOptions: { label: string; intent: IntentType }[] = [\n\t\t\t{ label: '全部', intent: 'other' },\n\t\t\t{ label: '登录', intent: 'login' },\n\t\t\t{ label: '搜索', intent: 'search' },\n\t\t\t{ label: '表单', intent: 'fill_form' },\n\t\t]\n\n\t\tfor (const option of filterOptions) {\n\t\t\tconst tab = document.createElement('button')\n\t\t\ttab.className = styles.filterTab\n\t\t\tif (option.intent === 'other') {\n\t\t\t\ttab.classList.add(styles.active)\n\t\t\t}\n\t\t\ttab.textContent = option.label\n\t\t\ttab.dataset.intent = option.intent\n\t\t\ttab.onclick = () => {\n\t\t\t\t// Remove active from all tabs\n\t\t\t\tfilters\n\t\t\t\t\t.querySelectorAll(`.${styles.filterTab}`)\n\t\t\t\t\t.forEach((t) => t.classList.remove(styles.active))\n\t\t\t\t// Add active to clicked tab\n\t\t\t\ttab.classList.add(styles.active)\n\t\t\t\t// Refresh list\n\t\t\t\tthis.#refreshTaskList('', option.intent)\n\t\t\t}\n\t\t\tfilters.appendChild(tab)\n\t\t}\n\n\t\treturn filters\n\t}\n\n\t/**\n\t * Render task list\n\t */\n\t#renderTaskList(tasks?: TaskCardData[]): HTMLElement {\n\t\tconst container = document.createElement('div')\n\t\tcontainer.className = styles.taskList\n\n\t\tconst taskList = tasks || this.#tasks\n\n\t\tif (taskList.length === 0) {\n\t\t\tconst empty = document.createElement('div')\n\t\t\tempty.className = styles.emptyState\n\t\t\tempty.textContent = '暂无录制的任务'\n\t\t\tcontainer.appendChild(empty)\n\t\t\treturn container\n\t\t}\n\n\t\tfor (const task of taskList) {\n\t\t\tconst card = createTaskCard(task, {\n\t\t\t\tonReplay: this.#onReplay,\n\t\t\t\tonExport: this.#onExport,\n\t\t\t\tonEdit: this.#onEdit,\n\t\t\t})\n\t\t\tcontainer.appendChild(card)\n\t\t}\n\n\t\treturn container\n\t}\n\n\t/**\n\t * Refresh task list with optional search/filter\n\t */\n\t#refreshTaskList(searchQuery: string = '', intentFilter?: IntentType): void {\n\t\tlet tasks = this.#tasks\n\n\t\t// Apply search\n\t\tif (searchQuery) {\n\t\t\ttasks = this.searchTasks(searchQuery)\n\t\t}\n\n\t\t// Apply filter\n\t\tif (intentFilter && intentFilter !== 'other') {\n\t\t\ttasks = tasks.filter((t) => t.intent.action === intentFilter)\n\t\t}\n\n\t\t// Re-render list\n\t\tconst taskList = document.querySelector(`.${styles.taskList}`)\n\t\tif (taskList) {\n\t\t\tconst newList = this.#renderTaskList(tasks)\n\t\t\ttaskList.replaceWith(newList)\n\t\t}\n\t}\n\n\t/**\n\t * Save to localStorage\n\t */\n\t#saveToStorage(): void {\n\t\ttry {\n\t\t\tlocalStorage.setItem(STORAGE_KEY, JSON.stringify(this.#tasks))\n\t\t} catch (error) {\n\t\t\tconsole.warn('[TaskHistory] Failed to save to localStorage:', error)\n\t\t}\n\t}\n\n\t/**\n\t * Load from localStorage\n\t */\n\t#loadFromStorage(): void {\n\t\ttry {\n\t\t\tconst stored = localStorage.getItem(STORAGE_KEY)\n\t\t\tif (stored) {\n\t\t\t\tthis.#tasks = JSON.parse(stored)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('[TaskHistory] Failed to load from localStorage:', error)\n\t\t\tthis.#tasks = []\n\t\t}\n\t}\n\n\t/**\n\t * Generate unique task ID\n\t */\n\tstatic generateId(): string {\n\t\treturn `task-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n\t}\n}\n\n/**\n * Helper function to create task history\n */\nexport function createTaskHistory(callbacks?: {\n\tonTaskSelect?: (task: TaskCardData) => void\n\tonTaskDelete?: (taskId: string) => void\n\tonReplay?: (task: TaskCardData) => void\n\tonExport?: (task: TaskCardData) => void\n\tonEdit?: (task: TaskCardData) => void\n}): TaskHistory {\n\treturn new TaskHistory(callbacks)\n}\n","import { type TestReport, parseTestSpec } from '@page-agent/core'\nimport type { AgentStepEvent } from '@page-agent/core'\nimport type {\n\tActionRecorder,\n\tExportFormat,\n\tRecordedEvent,\n\tTaskCard as TaskCardData,\n} from '@page-agent/recorder'\nimport { Panel, type PanelAgentAdapter, type PanelConfig } from '@page-agent/ui'\nimport { I18n, type SupportedLanguage } from '@page-agent/ui'\n\nimport { createTaskCard } from './TaskCard.js'\nimport { TaskHistory } from './TaskHistory.js'\n\nimport styles from './RecordingPanel.module.css'\n\n/**\n * RecordingPanel configuration\n */\nexport interface RecordingPanelConfig extends Omit<PanelConfig, 'recorder'> {\n\trecorder: ActionRecorder\n\t/** Callback to generate test DSL from natural language */\n\tonGenerateTestDSL?: (description: string) => Promise<string>\n\t/** Callback to run test from DSL */\n\tonRunTest?: (dsl: string) => Promise<TestReport>\n}\n\n/**\n * Extended Panel with recording functionality\n *\n * This class extends the base Panel class to add recording UI and logic.\n * All recording functionality is isolated in this extension, keeping the base Panel clean.\n */\nexport class RecordingPanel extends Panel {\n\t#recorder: ActionRecorder\n\t#recordingButton?: HTMLElement\n\t#eventCounter?: HTMLElement\n\t#recordingUpdateTimer: ReturnType<typeof setInterval> | null = null\n\t#i18n: I18n\n\t#taskHistory: TaskHistory\n\t#currentOverlay?: HTMLElement\n\n\t// Test mode state\n\t#config: RecordingPanelConfig\n\t#testModeButton?: HTMLElement\n\t#testContainer?: HTMLElement\n\t#dslEditor?: HTMLTextAreaElement\n\t#isTestMode = false\n\n\t// Settings button\n\t#settingsButton?: HTMLElement\n\n\tconstructor(agent: PanelAgentAdapter, config: RecordingPanelConfig) {\n\t\t// Call parent constructor (without recorder)\n\t\tsuper(agent, {\n\t\t\tlanguage: config.language,\n\t\t\tpromptForNextTask: config.promptForNextTask,\n\t\t})\n\n\t\t// Save agent reference for test progress monitoring\n\t\tthis.#recorder = config.recorder\n\t\tthis.#config = config\n\t\tthis.#i18n = new I18n(config.language ?? 'zh-CN')\n\n\t\t// Initialize task history\n\t\tthis.#taskHistory = new TaskHistory({\n\t\t\tonReplay: (task) => this.#handleReplay(task),\n\t\t\tonExport: (task) => this.#handleExport(task),\n\t\t\tonEdit: (task) => this.#handleEdit(task),\n\t\t})\n\n\t\t// Setup recording UI\n\t\tthis.#setupRecordingUI()\n\t\tthis.#setupTestModeUI()\n\t\tthis.#setupSettingsUI()\n\t}\n\n\t// ========== Recording functionality ==========\n\n\t/**\n\t * Setup recording UI elements\n\t */\n\t#setupRecordingUI(): void {\n\t\t// Find controls section in the parent Panel's DOM\n\t\t// Panel wrapper structure (from Panel.ts createWrapper):\n\t\t// [0] background\n\t\t// [1] historySectionWrapper\n\t\t// [2] header > statusSection + controls\n\t\t// [3] inputSectionWrapper\n\n\t\tconst header = this.wrapper.children[2] as HTMLElement\n\t\tif (!header) {\n\t\t\tconsole.warn('[RecordingPanel] Header not found at expected position')\n\t\t\treturn\n\t\t}\n\n\t\t// Controls is the second child of header (after statusSection)\n\t\tconst controls = header.children[1] as HTMLElement\n\t\tif (!controls) {\n\t\t\tconsole.warn('[RecordingPanel] Controls not found at expected position')\n\t\t\treturn\n\t\t}\n\n\t\t// Verify this is the controls section by checking for buttons\n\t\tconst buttons = controls.querySelectorAll('button')\n\t\tif (buttons.length < 2) {\n\t\t\tconsole.warn('[RecordingPanel] Controls section does not contain expected buttons')\n\t\t\treturn\n\t\t}\n\n\t\t// Create recording button with base control button class\n\t\tthis.#recordingButton = document.createElement('button')\n\t\t// Extract base button class from existing buttons (e.g., expandButton)\n\t\tconst existingButton = buttons[0]!\n\t\tconst baseButtonClass =\n\t\t\tArray.from(existingButton.classList).find(\n\t\t\t\t(c) => c.includes('controlButton') || c.includes('expandButton') || c.includes('stopButton')\n\t\t\t) || existingButton.className\n\n\t\tthis.#recordingButton.className = `${baseButtonClass} ${styles.recordingButton}`\n\t\tthis.#recordingButton.title = this.#i18n.t('ui.panel.startRecording')\n\t\tthis.#recordingButton.innerHTML = '●'\n\t\tthis.#recordingButton.setAttribute('data-page-agent-ignore', 'true')\n\t\tthis.#recordingButton.setAttribute('data-browser-use-ignore', 'true')\n\n\t\t// Create event counter\n\t\tthis.#eventCounter = document.createElement('span')\n\t\tthis.#eventCounter.className = styles.eventCounter\n\t\tthis.#eventCounter.textContent = ''\n\t\tthis.#eventCounter.setAttribute('data-page-agent-ignore', 'true')\n\t\tthis.#eventCounter.setAttribute('data-browser-use-ignore', 'true')\n\n\t\t// Insert recording button at the beginning of controls\n\t\tcontrols.insertBefore(this.#recordingButton, controls.firstChild)\n\n\t\t// Add event counter after the button\n\t\tcontrols.insertBefore(this.#eventCounter, this.#recordingButton.nextSibling)\n\n\t\tconsole.log('[RecordingPanel] Recording button added successfully')\n\n\t\t// Setup click handler\n\t\tthis.#recordingButton.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\tthis.#toggleRecording()\n\t\t})\n\t}\n\n\t/**\n\t * Toggle recording state\n\t */\n\t#toggleRecording(): void {\n\t\tconst status = this.#recorder.getStatus()\n\n\t\tif (status.isRecording) {\n\t\t\t// Stop recording\n\t\t\tconst events = this.#recorder.stop()\n\t\t\tthis.#updateRecordingUI(false)\n\n\t\t\t// Process and show task card\n\t\t\tif (events.length > 0) {\n\t\t\t\tthis.#processRecording(events)\n\t\t\t}\n\t\t} else {\n\t\t\t// Start recording\n\t\t\tthis.#recorder.start()\n\t\t\tthis.#updateRecordingUI(true)\n\t\t}\n\t}\n\n\t/**\n\t * Update recording UI state\n\t */\n\n\t// ========== Test Mode ==========\n\n\t/**\n\t * Setup test mode toggle button in header controls\n\t */\n\t#setupTestModeUI(): void {\n\t\tconst header = this.wrapper.children[2] as HTMLElement\n\t\tif (!header) return\n\t\tconst controls = header.children[1] as HTMLElement\n\t\tif (!controls) return\n\n\t\tconst buttons = controls.querySelectorAll('button')\n\t\tif (buttons.length < 1) return\n\t\tconst existingButton = buttons[0]!\n\t\tconst baseClass =\n\t\t\tArray.from(existingButton.classList).find(\n\t\t\t\t(c) => c.includes('controlButton') || c.includes('expandButton')\n\t\t\t) || existingButton.className\n\n\t\tthis.#testModeButton = document.createElement('button')\n\t\tthis.#testModeButton.className = baseClass\n\t\tthis.#testModeButton.textContent = 'T'\n\t\tthis.#testModeButton.title = 'Toggle test mode'\n\t\tthis.#testModeButton.setAttribute('data-page-agent-ignore', 'true')\n\t\tthis.#testModeButton.setAttribute('data-browser-use-ignore', 'true')\n\t\tthis.#testModeButton.style.fontWeight = 'bold'\n\t\tthis.#testModeButton.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\tthis.#toggleTestMode()\n\t\t})\n\n\t\tcontrols.insertBefore(this.#testModeButton, controls.firstChild)\n\t}\n\n\t/**\n\t * Toggle between automation and test mode\n\t */\n\t#toggleTestMode(): void {\n\t\tthis.#isTestMode = !this.#isTestMode\n\n\t\tif (this.#isTestMode) {\n\t\t\tthis.#testModeButton!.style.color = 'rgb(59, 130, 246)'\n\t\t\tthis.#showTestPanel()\n\t\t} else {\n\t\t\tthis.#testModeButton!.style.color = ''\n\t\t\tthis.#hideTestPanel()\n\t\t}\n\t}\n\n\t/**\n\t * Show test panel by injecting DSL editor into the panel\n\t */\n\t#showTestPanel(): void {\n\t\tif (this.#testContainer) return // Already shown\n\n\t\tconst historyWrapper = this.wrapper.children[1] as HTMLElement\n\t\tconst inputSection = this.wrapper.children[3] as HTMLElement\n\n\t\t// Only hide input section in test mode, keep history visible\n\t\tif (inputSection) inputSection.style.display = 'none'\n\t\t// Note: historyWrapper is kept visible to show execution history during testing\n\n\t\tconst container = document.createElement('div')\n\t\tcontainer.setAttribute('data-page-agent-ignore', 'true')\n\t\tcontainer.setAttribute('data-browser-use-ignore', 'true')\n\t\tcontainer.style.cssText =\n\t\t\t'padding: 8px 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; overflow-y: auto;'\n\n\t\t// DSL editor\n\t\tconst editorLabel = document.createElement('div')\n\t\teditorLabel.textContent = 'Test DSL (YAML)'\n\t\teditorLabel.style.cssText =\n\t\t\t'color: rgba(255,255,255,0.5); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;'\n\t\tcontainer.appendChild(editorLabel)\n\n\t\tthis.#dslEditor = document.createElement('textarea')\n\t\tthis.#dslEditor.setAttribute('data-page-agent-ignore', 'true')\n\t\tthis.#dslEditor.placeholder =\n\t\t\t'web:\\n url: https://example.com\\ntasks:\\n - name: My test\\n flow:\\n - ai: do something\\n - aiAssert: result should be X'\n\t\tthis.#dslEditor.style.cssText =\n\t\t\t'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;'\n\t\tcontainer.appendChild(this.#dslEditor)\n\n\t\t// Add placeholder styling for better readability\n\t\tconst placeholderStyle = document.createElement('style')\n\t\tplaceholderStyle.textContent = `\n\t\t\t[data-page-agent-ignore=\"true\"]::placeholder {\n\t\t\t\tcolor: rgba(255, 255, 255, 0.4) !important;\n\t\t\t}\n\t\t`\n\t\tdocument.head.appendChild(placeholderStyle)\n\n\t\t// Buttons\n\t\tconst btnRow = document.createElement('div')\n\t\tbtnRow.style.cssText = 'display: flex; gap: 6px;'\n\t\tbtnRow.appendChild(this.#createTestBtn('Run', () => this.#handleTestRun()))\n\t\tbtnRow.appendChild(this.#createTestBtn('Save', () => this.#handleTestSave()))\n\t\tbtnRow.appendChild(this.#createTestBtn('Export', () => this.#handleTestExport()))\n\t\tcontainer.appendChild(btnRow)\n\n\t\tthis.#testContainer = container\n\t\tthis.wrapper.insertBefore(container, inputSection)\n\t}\n\n\t/**\n\t * Hide test panel and restore normal panel UI\n\t */\n\t#hideTestPanel(): void {\n\t\tif (this.#testContainer) {\n\t\t\tthis.#testContainer.remove()\n\t\t\tthis.#testContainer = undefined\n\t\t}\n\n\t\tconst historyWrapper = this.wrapper.children[1] as HTMLElement\n\t\tconst inputSection = this.wrapper.children[3] as HTMLElement\n\t\tif (historyWrapper) historyWrapper.style.display = ''\n\t\tif (inputSection) inputSection.style.display = ''\n\t}\n\n\t#createTestBtn(text: string, onClick: () => void): HTMLButtonElement {\n\t\tconst btn = document.createElement('button')\n\t\tbtn.setAttribute('data-page-agent-ignore', 'true')\n\t\tbtn.textContent = text\n\t\tbtn.style.cssText =\n\t\t\t'padding: 4px 12px; background: rgba(59,130,246,0.7); color: white; border: none; border-radius: 4px; font-size: 11px; cursor: pointer;'\n\t\tbtn.addEventListener('mouseenter', () => {\n\t\t\tbtn.style.background = 'rgba(59,130,246,1)'\n\t\t})\n\t\tbtn.addEventListener('mouseleave', () => {\n\t\t\tbtn.style.background = 'rgba(59,130,246,0.7)'\n\t\t})\n\t\tbtn.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\tonClick()\n\t\t})\n\t\treturn btn\n\t}\n\n\tasync #handleTestRun(): Promise<void> {\n\t\tif (!this.#dslEditor || !this.#config.onRunTest) return\n\t\tconst dsl = this.#dslEditor.value.trim()\n\t\tif (!dsl) return\n\n\t\tconst parsed = parseTestSpec(dsl)\n\t\tif (!parsed.success) {\n\t\t\tconsole.error('Parse error:', parsed.errors)\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.#config.onRunTest(dsl)\n\t\t} catch (error) {\n\t\t\tconsole.error('Test failed:', error)\n\t\t}\n\t}\n\n\t#handleTestSave(): void {\n\t\tif (!this.#dslEditor) return\n\t\tconst dsl = this.#dslEditor.value.trim()\n\t\tif (!dsl) return\n\t\tconst parsed = parseTestSpec(dsl)\n\t\tif (!parsed.success || !parsed.spec) return\n\t\tconst name = parsed.spec.tasks.map((t) => t.name).join(' / ') || 'Untitled'\n\t\tconst saved = JSON.parse(localStorage.getItem('page-agent-test-specs') || '[]')\n\t\tsaved.push({ name, dsl })\n\t\tlocalStorage.setItem('page-agent-test-specs', JSON.stringify(saved))\n\t\tconsole.log('Saved:', name)\n\t}\n\n\t#handleTestExport(): void {\n\t\tconsole.log('Export functionality disabled: test result display removed')\n\t}\n\n\t#updateRecordingUI(isRecording: boolean): void {\n\t\tif (!this.#recordingButton) return\n\n\t\tif (isRecording) {\n\t\t\tthis.#recordingButton.title = this.#i18n.t('ui.panel.stopRecording')\n\t\t\tthis.#recordingButton.classList.add(styles.recording)\n\t\t\tthis.#startRecordingUpdateLoop()\n\t\t} else {\n\t\t\tthis.#recordingButton.title = this.#i18n.t('ui.panel.startRecording')\n\t\t\tthis.#recordingButton.classList.remove(styles.recording)\n\t\t\tthis.#stopRecordingUpdateLoop()\n\t\t\tif (this.#eventCounter) {\n\t\t\t\tthis.#eventCounter.textContent = ''\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Start periodic update of event counter\n\t */\n\t#startRecordingUpdateLoop(): void {\n\t\tthis.#stopRecordingUpdateLoop()\n\t\tthis.#recordingUpdateTimer = setInterval(() => {\n\t\t\tif (this.#eventCounter) {\n\t\t\t\tconst status = this.#recorder.getStatus()\n\t\t\t\tthis.#eventCounter.textContent = status.eventCount > 0 ? `${status.eventCount}` : ''\n\t\t\t}\n\t\t}, 500)\n\t}\n\n\t/**\n\t * Stop periodic update of event counter\n\t */\n\t#stopRecordingUpdateLoop(): void {\n\t\tif (this.#recordingUpdateTimer) {\n\t\t\tclearInterval(this.#recordingUpdateTimer)\n\t\t\tthis.#recordingUpdateTimer = null\n\t\t}\n\t}\n\n\t/**\n\t * Process recording and create task card\n\t */\n\tasync #processRecording(events: RecordedEvent[]): Promise<void> {\n\t\t// Show loading state\n\t\tthis.#showLoadingState('AI分析中...')\n\n\t\ttry {\n\t\t\t// Analyze intent (will use AI if available, otherwise fallback to keyword)\n\t\t\tconst taskData = await this.#analyzeIntent(events)\n\n\t\t\t// Add to task history\n\t\t\tthis.#taskHistory.addTask(taskData)\n\n\t\t\t// Show task card\n\t\t\tthis.#showTaskCard(taskData)\n\t\t} catch (error) {\n\t\t\tconsole.error('[RecordingPanel] Failed to process recording:', error)\n\t\t\t// Fallback to export dialog\n\t\t\tthis.#showExportDialog(events)\n\t\t}\n\t}\n\n\t/**\n\t * Analyze intent from recorded events\n\t */\n\tasync #analyzeIntent(events: RecordedEvent[]): Promise<TaskCardData> {\n\t\t// For now, use basic intent recognition\n\t\t// TODO: Integrate AIIntentRecognizer when LLM client is available\n\t\tconst { IntentRecognizer } = await import('@page-agent/recorder')\n\t\tconst recognizer = new IntentRecognizer()\n\t\tconst intent = recognizer.analyzeEvents(events)\n\n\t\t// Extract parameters\n\t\tconst { TaskParameterExtractor } = await import('@page-agent/recorder')\n\t\tconst paramExtractor = new TaskParameterExtractor()\n\t\tconst parameters = paramExtractor.extractParameters(events, intent)\n\n\t\t// Generate task name\n\t\tconst taskName = await this.#generateTaskName(events, intent)\n\n\t\t// Estimate duration\n\t\tconst estimatedDuration = this.#estimateDuration(events)\n\n\t\treturn {\n\t\t\tid: TaskHistory.generateId(),\n\t\t\tname: taskName,\n\t\t\tintent: {\n\t\t\t\t...intent,\n\t\t\t\tparameters,\n\t\t\t\tdescription: intent.context.semanticContext || '',\n\t\t\t\ttaskName,\n\t\t\t},\n\t\t\tevents,\n\t\t\tcreatedAt: Date.now(),\n\t\t\teventCount: events.length,\n\t\t\testimatedDuration,\n\t\t}\n\t}\n\n\t/**\n\t * Generate task name from events and intent\n\t */\n\tasync #generateTaskName(events: RecordedEvent[], intent: any): Promise<string> {\n\t\t// Simple mapping for now\n\t\tconst actionNames: Record<string, string> = {\n\t\t\tlogin: '登录系统',\n\t\t\tsearch: '搜索内容',\n\t\t\tfill_form: '填写表单',\n\t\t\tnavigate: '浏览页面',\n\t\t\tdata_entry: '录入数据',\n\t\t\tbatch_operation: '批量操作',\n\t\t\tverification: '验证操作',\n\t\t\tdownload: '下载文件',\n\t\t\tupload: '上传文件',\n\t\t\tother: '录制的操作',\n\t\t}\n\n\t\tconst baseName = actionNames[intent.action] || '录制的操作'\n\n\t\t// Try to extract context from first click event\n\t\tfor (const event of events) {\n\t\t\tif (event.type === 'click' && event.elementInfo?.textContent) {\n\t\t\t\tconst text = event.elementInfo.textContent.trim()\n\t\t\t\tif (text && text.length < 30) {\n\t\t\t\t\treturn `${baseName}: ${text}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn baseName\n\t}\n\n\t/**\n\t * Estimate duration from events\n\t */\n\t#estimateDuration(events: RecordedEvent[]): number {\n\t\tif (events.length < 2) return 1000\n\n\t\tconst firstEvent = events[0]!\n\t\tconst lastEvent = events[events.length - 1]!\n\t\treturn lastEvent.timestamp - firstEvent.timestamp\n\t}\n\n\t/**\n\t * Show loading state\n\t */\n\t#showLoadingState(message: string): void {\n\t\t// Remove existing overlay\n\t\tthis.#currentOverlay?.remove()\n\n\t\tconst overlay = document.createElement('div')\n\t\toverlay.className = styles.exportOverlay\n\n\t\tconst content = document.createElement('div')\n\t\tcontent.className = styles.loadingContent\n\t\tcontent.innerHTML = `\n\t\t\t<div class=\"${styles.loadingSpinner}\"></div>\n\t\t\t<p>${message}</p>\n\t\t`\n\n\t\toverlay.appendChild(content)\n\t\tdocument.body.appendChild(overlay)\n\t\tthis.#currentOverlay = overlay\n\t}\n\n\t/**\n\t * Show task card\n\t */\n\t#showTaskCard(task: TaskCardData): void {\n\t\t// Remove existing overlay\n\t\tthis.#currentOverlay?.remove()\n\n\t\tconst overlay = document.createElement('div')\n\t\toverlay.className = styles.exportOverlay\n\n\t\t// Create task card\n\t\tconst cardElement = createTaskCard(task, {\n\t\t\tonReplay: (t) => {\n\t\t\t\toverlay.remove()\n\t\t\t\tthis.#handleReplay(t)\n\t\t\t},\n\t\t\tonExport: (t) => {\n\t\t\t\toverlay.remove()\n\t\t\t\tthis.#handleExport(t)\n\t\t\t},\n\t\t\tonEdit: (t) => {\n\t\t\t\toverlay.remove()\n\t\t\t\tthis.#handleEdit(t)\n\t\t\t},\n\t\t})\n\n\t\t// Wrap in dialog\n\t\tconst dialog = document.createElement('div')\n\t\tdialog.className = styles.taskCardDialog\n\t\tdialog.appendChild(cardElement)\n\n\t\toverlay.appendChild(dialog)\n\t\tdocument.body.appendChild(overlay)\n\t\tthis.#currentOverlay = overlay\n\n\t\t// Close on overlay click\n\t\toverlay.addEventListener('click', (e) => {\n\t\t\tif (e.target === overlay) {\n\t\t\t\toverlay.remove()\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Handle replay action\n\t */\n\t#handleReplay(task: TaskCardData): void {\n\t\tconsole.log('[RecordingPanel] Replay task:', task.name)\n\t\t// TODO: Implement replay functionality\n\t\talert(`重放功能即将推出: ${task.name}`)\n\t}\n\n\t/**\n\t * Handle export action\n\t */\n\t#handleExport(task: TaskCardData): void {\n\t\tthis.#downloadRecording(task.events, 'json')\n\t}\n\n\t/**\n\t * Handle edit action\n\t */\n\t#handleEdit(task: TaskCardData): void {\n\t\tconsole.log('[RecordingPanel] Edit task:', task.name)\n\t\t// TODO: Implement edit functionality\n\t\talert(`编辑功能即将推出: ${task.name}`)\n\t}\n\n\t/**\n\t * Show export dialog for recorded events\n\t */\n\t#showExportDialog(events: RecordedEvent[]): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\t// Create modal overlay\n\t\t\tconst overlay = document.createElement('div')\n\t\t\toverlay.className = styles.exportOverlay\n\n\t\t\t// Create dialog\n\t\t\tconst dialog = document.createElement('div')\n\t\t\tdialog.className = styles.exportDialog\n\n\t\t\t// Header\n\t\t\tconst header = document.createElement('div')\n\t\t\theader.className = styles.exportDialogHeader\n\t\t\theader.innerHTML = `\n\t\t\t\t<h3>${this.#i18n.t('ui.panel.exportRecording')}</h3>\n\t\t\t\t<button class=\"${styles.closeButton}\">×</button>\n\t\t\t`\n\t\t\tdialog.appendChild(header)\n\n\t\t\t// Content\n\t\t\tconst content = document.createElement('div')\n\t\t\tcontent.className = styles.exportDialogContent\n\t\t\tcontent.innerHTML = `\n\t\t\t\t<p>${this.#i18n.t('ui.panel.recordedEvents', { count: events.length })}</p>\n\t\t\t\t<div class=\"${styles.exportOptions}\">\n\t\t\t\t\t<button data-format=\"json\" class=\"${styles.exportOptionButton}\">\n\t\t\t\t\t\t<div class=\"${styles.exportOptionIcon}\">{}</div>\n\t\t\t\t\t\t<div class=\"${styles.exportOptionLabel}\">JSON</div>\n\t\t\t\t\t</button>\n\t\t\t\t\t<button data-format=\"markdown\" class=\"${styles.exportOptionButton}\">\n\t\t\t\t\t\t<div class=\"${styles.exportOptionIcon}\">M↓</div>\n\t\t\t\t\t\t<div class=\"${styles.exportOptionLabel}\">Markdown</div>\n\t\t\t\t\t</button>\n\t\t\t\t\t<button data-format=\"playwright\" class=\"${styles.exportOptionButton}\">\n\t\t\t\t\t\t<div class=\"${styles.exportOptionIcon}\">🎭</div>\n\t\t\t\t\t\t<div class=\"${styles.exportOptionLabel}\">Playwright</div>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t`\n\t\t\tdialog.appendChild(content)\n\n\t\t\toverlay.appendChild(dialog)\n\t\t\tdocument.body.appendChild(overlay)\n\n\t\t\t// Close button handler\n\t\t\tconst closeBtn = header.querySelector(`.${styles.closeButton}`)!\n\t\t\tconst closeDialog = () => {\n\t\t\t\toverlay.remove()\n\t\t\t\tresolve()\n\t\t\t}\n\t\t\tcloseBtn.addEventListener('click', closeDialog)\n\n\t\t\t// Export option handlers\n\t\t\tconst exportButtons = content.querySelectorAll(`.${styles.exportOptionButton}`)\n\t\t\texportButtons.forEach((button) => {\n\t\t\t\tbutton.addEventListener('click', () => {\n\t\t\t\t\tconst format = button.getAttribute('data-format') as ExportFormat\n\t\t\t\t\tthis.#downloadRecording(events, format)\n\t\t\t\t\tcloseDialog()\n\t\t\t\t})\n\t\t\t})\n\n\t\t\t// Close on overlay click\n\t\t\toverlay.addEventListener('click', (e) => {\n\t\t\t\tif (e.target === overlay) {\n\t\t\t\t\tcloseDialog()\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Download recording as file\n\t */\n\t#downloadRecording(events: RecordedEvent[], format: ExportFormat): void {\n\t\tlet content: string\n\t\tlet filename: string\n\t\tlet mimeType: string\n\n\t\tswitch (format) {\n\t\t\tcase 'json':\n\t\t\t\tcontent = this.#recorder.export('json', events)\n\t\t\t\tfilename = `recording-${Date.now()}.json`\n\t\t\t\tmimeType = 'application/json'\n\t\t\t\tbreak\n\t\t\tcase 'markdown':\n\t\t\t\tcontent = this.#recorder.export('markdown', events)\n\t\t\t\tfilename = `recording-${Date.now()}.md`\n\t\t\t\tmimeType = 'text/markdown'\n\t\t\t\tbreak\n\t\t\tcase 'playwright':\n\t\t\t\tcontent = this.#recorder.export('playwright', events)\n\t\t\t\tfilename = `test-${Date.now()}.spec.ts`\n\t\t\t\tmimeType = 'text/typescript'\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\treturn\n\t\t}\n\n\t\t// Create download link\n\t\tconst blob = new Blob([content], { type: mimeType })\n\t\tconst url = URL.createObjectURL(blob)\n\t\tconst link = document.createElement('a')\n\t\tlink.href = url\n\t\tlink.download = filename\n\t\tdocument.body.appendChild(link)\n\t\tlink.click()\n\t\tdocument.body.removeChild(link)\n\t\tURL.revokeObjectURL(url)\n\t}\n\n\t// ========== Settings functionality ==========\n\n\t/**\n\t * Setup settings button in header controls\n\t */\n\t#setupSettingsUI(): void {\n\t\tconst header = this.wrapper.children[2] as HTMLElement\n\t\tif (!header) return\n\t\tconst controls = header.children[1] as HTMLElement\n\t\tif (!controls) return\n\n\t\tconst buttons = controls.querySelectorAll('button')\n\t\tif (buttons.length < 1) return\n\t\tconst existingButton = buttons[0]!\n\t\tconst baseClass =\n\t\t\tArray.from(existingButton.classList).find(\n\t\t\t\t(c) => c.includes('controlButton') || c.includes('expandButton')\n\t\t\t) || existingButton.className\n\n\t\tthis.#settingsButton = document.createElement('button')\n\t\tthis.#settingsButton.className = baseClass\n\t\tthis.#settingsButton.innerHTML = '⚙'\n\t\tthis.#settingsButton.title = 'Settings'\n\t\tthis.#settingsButton.setAttribute('data-page-agent-ignore', 'true')\n\t\tthis.#settingsButton.setAttribute('data-browser-use-ignore', 'true')\n\t\tthis.#settingsButton.style.fontSize = '16px'\n\t\tthis.#settingsButton.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\tthis.#showSettingsDialog()\n\t\t})\n\n\t\tcontrols.insertBefore(this.#settingsButton, controls.firstChild)\n\t}\n\n\t/**\n\t * Show settings dialog\n\t */\n\t#showSettingsDialog(): void {\n\t\tthis.#currentOverlay?.remove()\n\n\t\tconst overlay = document.createElement('div')\n\t\toverlay.className = styles.settingsOverlay || styles.exportOverlay\n\n\t\tconst dialog = document.createElement('div')\n\t\tdialog.className = styles.settingsDialog || styles.exportDialog\n\n\t\tconst header = document.createElement('div')\n\t\theader.className = styles.settingsDialogHeader || styles.exportDialogHeader\n\t\theader.innerHTML = `\n\t\t\t\t<h3>Settings</h3>\n\t\t\t\t<button class=\"${styles.closeButton}\">×</button>\n\t\t\t`\n\t\tdialog.appendChild(header)\n\n\t\tconst content = document.createElement('div')\n\t\tcontent.className = styles.settingsDialogContent || styles.exportDialogContent\n\n\t\tconst currentConfig = this.#loadConfigFromStorage()\n\n\t\tcontent.innerHTML = `\n\t\t\t\t<div class=\"${styles.settingsField}\">\n\t\t\t\t\t<label class=\"${styles.settingsLabel}\">Model</label>\n\t\t\t\t\t<input type=\"text\" class=\"${styles.settingsInput}\" id=\"settings-model\"\n\t\t\t\t\t\tvalue=\"${this.#escapeHtml(currentConfig?.model || '')}\"\n\t\t\t\t\t\tplaceholder=\"deepseek-v4-flash\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"${styles.settingsField}\">\n\t\t\t\t\t<label class=\"${styles.settingsLabel}\">API Base URL</label>\n\t\t\t\t\t<input type=\"text\" class=\"${styles.settingsInput}\" id=\"settings-baseurl\"\n\t\t\t\t\t\tvalue=\"${this.#escapeHtml(currentConfig?.baseURL || '')}\"\n\t\t\t\t\t\tplaceholder=\"https://api.deepseek.com\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"${styles.settingsField}\">\n\t\t\t\t\t<label class=\"${styles.settingsLabel}\">API Key (optional)</label>\n\t\t\t\t\t<input type=\"password\" class=\"${styles.settingsInput}\" id=\"settings-apikey\"\n\t\t\t\t\t\tvalue=\"${this.#escapeHtml(currentConfig?.apiKey || '')}\"\n\t\t\t\t\t\tplaceholder=\"••••••••\" />\n\t\t\t\t</div>\n\t\t\t`\n\n\t\tconst buttonRow = document.createElement('div')\n\t\tbuttonRow.style.cssText = 'display: flex; gap: 8px; justify-content: flex-end; padding: 20px;'\n\t\tbuttonRow.innerHTML = `\n\t\t\t\t<button id=\"settings-cancel\" class=\"${styles.btnSecondary}\">Cancel</button>\n\t\t\t\t<button id=\"settings-save\" class=\"${styles.btnPrimary}\">Save</button>\n\t\t\t`\n\t\tdialog.appendChild(content)\n\t\tdialog.appendChild(buttonRow)\n\n\t\toverlay.appendChild(dialog)\n\t\tdocument.body.appendChild(overlay)\n\t\tthis.#currentOverlay = overlay\n\n\t\tconst closeBtn = header.querySelector(`.${styles.closeButton}`)!\n\t\tconst closeDialog = () => {\n\t\t\toverlay.remove()\n\t\t}\n\t\tcloseBtn.addEventListener('click', closeDialog)\n\n\t\tconst cancelBtn = buttonRow.querySelector('#settings-cancel')!\n\t\tcancelBtn.addEventListener('click', closeDialog)\n\n\t\tconst saveBtn = buttonRow.querySelector('#settings-save')!\n\t\tsaveBtn.addEventListener('click', () => {\n\t\t\tconst model = (document.getElementById('settings-model') as HTMLInputElement).value.trim()\n\t\t\tconst baseURL = (document.getElementById('settings-baseurl') as HTMLInputElement).value.trim()\n\t\t\tconst apiKey = (document.getElementById('settings-apikey') as HTMLInputElement).value.trim()\n\n\t\t\tif (!model || !baseURL) {\n\t\t\t\talert('Please fill in Model and Base URL')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.#saveConfigToStorage({ model, baseURL, apiKey })\n\t\t\tcloseDialog()\n\t\t\tconsole.log('Settings saved:', { model, baseURL, apiKey: '••••' })\n\t\t})\n\n\t\toverlay.addEventListener('click', (e) => {\n\t\t\tif (e.target === overlay) {\n\t\t\t\tcloseDialog()\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Load config from localStorage\n\t */\n\t#loadConfigFromStorage(): { model: string; baseURL: string; apiKey: string } | null {\n\t\ttry {\n\t\t\tconst saved = localStorage.getItem('page-agent-demo-llm-config')\n\t\t\treturn saved ? JSON.parse(saved) : null\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Save config to localStorage\n\t */\n\t#saveConfigToStorage(config: { model: string; baseURL: string; apiKey: string }): void {\n\t\tlocalStorage.setItem('page-agent-demo-llm-config', JSON.stringify(config))\n\t}\n\n\t#escapeHtml(str: string): string {\n\t\tconst d = document.createElement('div')\n\t\td.textContent = str\n\t\treturn d.innerHTML\n\t}\n\n\t/**\n\t * Dispose panel and clean up event listeners\n\t */\n\toverride dispose(): void {\n\t\t// Clean up recording\n\t\tthis.#stopRecordingUpdateLoop()\n\n\t\t// Clean up overlay\n\t\tthis.#currentOverlay?.remove()\n\n\t\t// Clean up test mode\n\t\tif (this.#isTestMode) this.#hideTestPanel()\n\n\t\t// Call parent dispose\n\t\tsuper.dispose()\n\t}\n}\n","import {\n\ttype AgentConfig,\n\tPageAgentCore,\n\ttype TestReport,\n\tassert,\n\thistoryToReport,\n\tparseTestSpec,\n\tquery,\n\ttestSpecToPrompt,\n\twait_for_condition,\n} from '@page-agent/core'\nimport { PageController, type PageControllerConfig } from '@page-agent/page-controller'\nimport {\n\tActionRecorder,\n\ttype ExportFormat,\n\ttype RecordedEvent,\n\ttype ReplayConfig,\n\ttype ReplayResult,\n} from '@page-agent/recorder'\nimport { type PanelConfig } from '@page-agent/ui'\n\nimport { RecordingPanel } from './RecordingPanel'\n\nexport * from '@page-agent/core'\nexport { RecordingPanel } from './RecordingPanel'\n\nexport type PageAgentConfig = AgentConfig & PageControllerConfig & Omit<PanelConfig, 'language'>\n\nexport class PageAgent extends PageAgentCore {\n\tpanel: RecordingPanel\n\trecorder: ActionRecorder\n\n\tconstructor(config: PageAgentConfig) {\n\t\tconst pageController = new PageController({\n\t\t\t...config,\n\t\t\tenableMask: config.enableMask ?? true,\n\t\t})\n\n\t\tsuper({ ...config, pageController })\n\n\t\t// Initialize recorder\n\t\tthis.recorder = new ActionRecorder({\n\t\t\tpageController,\n\t\t})\n\n\t\tthis.panel = new RecordingPanel(this, {\n\t\t\tlanguage: config.language,\n\t\t\trecorder: this.recorder,\n\t\t\tpromptForNextTask: config.promptForNextTask,\n\t\t\tonGenerateTestDSL: (desc) => this.generateTestDSL(desc),\n\t\t\tonRunTest: (dsl) => this.runTest(dsl),\n\t\t})\n\t}\n\n\t/**\n\t * Start recording user interactions\n\t */\n\tstartRecording(): void {\n\t\tthis.recorder.start()\n\t}\n\n\t/**\n\t * Stop recording and return captured events\n\t */\n\tstopRecording(): RecordedEvent[] {\n\t\treturn this.recorder.stop()\n\t}\n\n\t/**\n\t * Export recording in specified format\n\t */\n\texportRecording(format: ExportFormat): string {\n\t\treturn this.recorder.export(format)\n\t}\n\n\t/**\n\t * Get recording status\n\t */\n\tgetRecordingStatus(): { isRecording: boolean; eventCount: number } {\n\t\treturn this.recorder.getStatus()\n\t}\n\n\t/**\n\t * Get all recorded events\n\t */\n\tgetRecordedEvents(): RecordedEvent[] {\n\t\treturn this.recorder.getEvents()\n\t}\n\n\t/**\n\t * Clear all recorded events\n\t */\n\tclearRecording(): void {\n\t\tthis.recorder.clear()\n\t}\n\n\t/**\n\t * Get the recognized intent of the recording\n\t */\n\tgetRecordingIntent() {\n\t\treturn this.recorder.getIntent()\n\t}\n\n\t/**\n\t * Replay recorded events\n\t * @param events - Optional events to replay (uses current recording if not provided)\n\t * @param config - Optional replay configuration\n\t */\n\tasync replayRecording(events?: RecordedEvent[], config?: ReplayConfig): Promise<ReplayResult> {\n\t\treturn await this.recorder.replay(events, config)\n\t}\n\n\t/**\n\t * Load events from a JSON file and replay them\n\t * @param filePath - Path to the recording file\n\t * @param config - Optional replay configuration\n\t */\n\tasync replayRecordingFile(filePath: string, config?: ReplayConfig): Promise<ReplayResult> {\n\t\treturn await this.recorder.replayFile(filePath, config)\n\t}\n\n\t// ========== Testing API ==========\n\n\tasync generateTestDSL(description: string): Promise<string> {\n\t\tconst pageState = await this.pageController.getBrowserState()\n\t\tconst url = pageState.url\n\n\t\treturn `web:\n url: ${url}\n\ntasks:\n - name: ${description.slice(0, 50)}\n flow:\n - ai: ${description}\n - sleep: 2000\n - aiAssert: The action completed successfully\n`\n\t}\n\n\tasync runTest(dsl: string): Promise<TestReport> {\n\t\tconst parseResult = parseTestSpec(dsl)\n\t\tif (!parseResult.success || !parseResult.spec) {\n\t\t\tthrow new Error(`Invalid test DSL: ${parseResult.errors?.join(', ')}`)\n\t\t}\n\t\tconst spec = parseResult.spec\n\n\t\t// Register testing tools\n\t\tthis.tools.set('assert', assert)\n\t\tthis.tools.set('query', query)\n\t\tthis.tools.set('wait_for_condition', wait_for_condition)\n\n\t\ttry {\n\t\t\t// Convert DSL to agent prompt\n\t\t\tconst prompt = testSpecToPrompt(spec)\n\n\t\t\t// Execute using agent loop\n\t\t\tconst result = await this.execute(prompt)\n\n\t\t\t// Convert history to test report\n\t\t\treturn historyToReport(result.history, spec)\n\t\t} finally {\n\t\t\t// Clean up testing tools\n\t\t\tthis.tools.delete('assert')\n\t\t\tthis.tools.delete('query')\n\t\t\tthis.tools.delete('wait_for_condition')\n\t\t}\n\t}\n\n\tsaveTestSpec(name: string, dsl: string): void {\n\t\tconst key = 'page-agent-test-specs'\n\t\tconst saved = JSON.parse(localStorage.getItem(key) || '[]')\n\t\tsaved.push({ name, dsl })\n\t\tlocalStorage.setItem(key, JSON.stringify(saved))\n\t}\n\n\tloadTestSpecs(): { name: string; dsl: string }[] {\n\t\ttry {\n\t\t\treturn JSON.parse(localStorage.getItem('page-agent-test-specs') || '[]')\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|