deepspider 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.env.example +3 -0
  2. package/README.md +13 -13
  3. package/package.json +6 -6
  4. package/src/agent/core/PanelBridge.js +29 -77
  5. package/src/agent/core/StreamHandler.js +139 -14
  6. package/src/agent/index.js +51 -12
  7. package/src/agent/logger.js +184 -9
  8. package/src/agent/middleware/report.js +42 -16
  9. package/src/agent/middleware/subagent.js +233 -0
  10. package/src/agent/middleware/toolGuard.js +77 -0
  11. package/src/agent/middleware/validationWorkflow.js +171 -0
  12. package/src/agent/prompts/system.js +181 -59
  13. package/src/agent/run.js +41 -6
  14. package/src/agent/skills/crawler/SKILL.md +64 -3
  15. package/src/agent/skills/crawler/evolved.md +9 -1
  16. package/src/agent/skills/dynamic-analysis/SKILL.md +74 -7
  17. package/src/agent/skills/env/SKILL.md +75 -0
  18. package/src/agent/skills/evolve.js +0 -3
  19. package/src/agent/skills/sandbox/SKILL.md +35 -0
  20. package/src/agent/skills/static-analysis/SKILL.md +98 -2
  21. package/src/agent/subagents/anti-detect.js +10 -20
  22. package/src/agent/subagents/captcha.js +7 -19
  23. package/src/agent/subagents/crawler.js +25 -37
  24. package/src/agent/subagents/factory.js +109 -9
  25. package/src/agent/subagents/index.js +4 -13
  26. package/src/agent/subagents/js2python.js +7 -19
  27. package/src/agent/subagents/reverse.js +180 -0
  28. package/src/agent/tools/analysis.js +84 -1
  29. package/src/agent/tools/anti-detect.js +5 -2
  30. package/src/agent/tools/browser.js +160 -0
  31. package/src/agent/tools/captcha.js +1 -1
  32. package/src/agent/tools/capture.js +24 -3
  33. package/src/agent/tools/correlate.js +129 -15
  34. package/src/agent/tools/crawler.js +2 -1
  35. package/src/agent/tools/crawlerGenerator.js +90 -0
  36. package/src/agent/tools/debug.js +43 -6
  37. package/src/agent/tools/evolve.js +6 -3
  38. package/src/agent/tools/extractor.js +5 -1
  39. package/src/agent/tools/file.js +16 -7
  40. package/src/agent/tools/generateHook.js +66 -0
  41. package/src/agent/tools/hookManager.js +19 -9
  42. package/src/agent/tools/index.js +33 -20
  43. package/src/agent/tools/nodejs.js +41 -6
  44. package/src/agent/tools/python.js +4 -4
  45. package/src/agent/tools/report.js +2 -2
  46. package/src/agent/tools/runtime.js +1 -1
  47. package/src/agent/tools/sandbox.js +21 -1
  48. package/src/agent/tools/scratchpad.js +70 -0
  49. package/src/agent/tools/tracing.js +26 -0
  50. package/src/agent/tools/verifyAlgorithm.js +117 -0
  51. package/src/analyzer/EncryptionAnalyzer.js +2 -2
  52. package/src/browser/EnvBridge.js +27 -13
  53. package/src/browser/client.js +124 -18
  54. package/src/browser/collector.js +101 -22
  55. package/src/browser/defaultHooks.js +3 -1
  56. package/src/browser/hooks/index.js +5 -0
  57. package/src/browser/interceptors/AntiDebugInterceptor.js +132 -0
  58. package/src/browser/interceptors/NetworkInterceptor.js +77 -13
  59. package/src/browser/interceptors/ScriptInterceptor.js +34 -9
  60. package/src/browser/interceptors/index.js +1 -0
  61. package/src/browser/ui/analysisPanel.js +469 -464
  62. package/src/cli/commands/config.js +11 -3
  63. package/src/config/paths.js +9 -1
  64. package/src/config/settings.js +7 -1
  65. package/src/core/PatchGenerator.js +26 -6
  66. package/src/core/Sandbox.js +140 -3
  67. package/src/env/EnvCodeGenerator.js +60 -88
  68. package/src/env/modules/bom/history.js +6 -0
  69. package/src/env/modules/bom/location.js +6 -0
  70. package/src/env/modules/bom/navigator.js +13 -0
  71. package/src/env/modules/bom/screen.js +6 -0
  72. package/src/env/modules/bom/storage.js +7 -0
  73. package/src/env/modules/dom/document.js +14 -0
  74. package/src/env/modules/dom/event.js +4 -0
  75. package/src/env/modules/index.js +27 -10
  76. package/src/env/modules/webapi/fetch.js +4 -0
  77. package/src/env/modules/webapi/url.js +4 -0
  78. package/src/env/modules/webapi/xhr.js +8 -0
  79. package/src/store/DataStore.js +130 -47
  80. package/src/store/Store.js +2 -1
  81. package/src/agent/subagents/dynamic.js +0 -64
  82. package/src/agent/subagents/env-agent.js +0 -82
  83. package/src/agent/subagents/sandbox.js +0 -55
  84. package/src/agent/subagents/static.js +0 -66
@@ -157,8 +157,6 @@ export function getAnalysisPanelScript() {
157
157
 
158
158
  // 状态 - 从 sessionStorage 恢复消息
159
159
  const STORAGE_KEY = 'deepspider_chat_messages';
160
- const STAGES_STORAGE_KEY = 'deepspider_stages';
161
- const CURRENT_STAGE_KEY = 'deepspider_current_stage';
162
160
  const SELECTED_ELEMENTS_KEY = 'deepspider_selected_elements';
163
161
  try {
164
162
  const saved = sessionStorage.getItem(STORAGE_KEY);
@@ -166,22 +164,6 @@ export function getAnalysisPanelScript() {
166
164
  } catch (e) {
167
165
  deepspider.chatMessages = [];
168
166
  }
169
- // 阶段配置 - 支持多阶段爬取流程
170
- try {
171
- const savedStages = sessionStorage.getItem(STAGES_STORAGE_KEY);
172
- deepspider.stages = savedStages ? JSON.parse(savedStages) : [
173
- { name: 'list', fields: [], entry: null, pagination: null }
174
- ];
175
- } catch (e) {
176
- deepspider.stages = [{ name: 'list', fields: [], entry: null, pagination: null }];
177
- }
178
- // 当前选中的阶段
179
- try {
180
- const savedCurrentStage = sessionStorage.getItem(CURRENT_STAGE_KEY);
181
- deepspider.currentStageIndex = savedCurrentStage ? parseInt(savedCurrentStage) : 0;
182
- } catch (e) {
183
- deepspider.currentStageIndex = 0;
184
- }
185
167
  // 已选元素列表 - 从 sessionStorage 恢复
186
168
  try {
187
169
  const savedElements = sessionStorage.getItem(SELECTED_ELEMENTS_KEY);
@@ -201,16 +183,6 @@ export function getAnalysisPanelScript() {
201
183
  }
202
184
  }
203
185
 
204
- // 保存阶段配置到 sessionStorage
205
- function saveStages() {
206
- try {
207
- sessionStorage.setItem(STAGES_STORAGE_KEY, JSON.stringify(deepspider.stages));
208
- sessionStorage.setItem(CURRENT_STAGE_KEY, String(deepspider.currentStageIndex));
209
- } catch (e) {
210
- console.warn('[DeepSpider] 保存阶段配置失败:', e);
211
- }
212
- }
213
-
214
186
  // 保存已选元素到 sessionStorage
215
187
  function saveSelectedElements() {
216
188
  try {
@@ -251,7 +223,12 @@ export function getAnalysisPanelScript() {
251
223
  position: fixed;
252
224
  top: 20px; right: 20px;
253
225
  width: 400px;
254
- max-height: 70vh;
226
+ height: 70vh;
227
+ min-width: 320px;
228
+ min-height: 300px;
229
+ max-width: 90vw;
230
+ max-height: 95vh;
231
+ overflow: hidden;
255
232
  background: linear-gradient(180deg, #1e2530 0%, #161b22 100%);
256
233
  border: 1px solid rgba(99, 179, 237, 0.2);
257
234
  border-radius: 16px;
@@ -266,10 +243,25 @@ export function getAnalysisPanelScript() {
266
243
  backdrop-filter: blur(10px);
267
244
  }
268
245
  #deepspider-panel.visible { display: flex; animation: deepspider-fadein 0.25s ease-out; }
246
+ #deepspider-panel.resizing { transition: none; }
269
247
  #deepspider-panel.minimized { max-height: 48px; overflow: hidden; }
270
248
  #deepspider-panel.minimized .deepspider-messages,
271
249
  #deepspider-panel.minimized .deepspider-input,
272
- #deepspider-panel.minimized .deepspider-report-btn { display: none !important; }
250
+ #deepspider-panel.minimized .deepspider-report-btn,
251
+ #deepspider-panel.minimized .deepspider-resize-handle { display: none !important; }
252
+ /* 边缘拖拽缩放手柄 */
253
+ .deepspider-resize-handle {
254
+ position: absolute;
255
+ z-index: 1;
256
+ }
257
+ .deepspider-resize-handle.top { top: -4px; left: 8px; right: 8px; height: 8px; cursor: n-resize; }
258
+ .deepspider-resize-handle.bottom { bottom: -4px; left: 8px; right: 8px; height: 8px; cursor: s-resize; }
259
+ .deepspider-resize-handle.left { left: -4px; top: 8px; bottom: 8px; width: 8px; cursor: w-resize; }
260
+ .deepspider-resize-handle.right { right: -4px; top: 8px; bottom: 8px; width: 8px; cursor: e-resize; }
261
+ .deepspider-resize-handle.top-left { top: -4px; left: -4px; width: 14px; height: 14px; cursor: nw-resize; }
262
+ .deepspider-resize-handle.top-right { top: -4px; right: -4px; width: 14px; height: 14px; cursor: ne-resize; }
263
+ .deepspider-resize-handle.bottom-left { bottom: -4px; left: -4px; width: 14px; height: 14px; cursor: sw-resize; }
264
+ .deepspider-resize-handle.bottom-right { bottom: -4px; right: -4px; width: 14px; height: 14px; cursor: se-resize; }
273
265
  @keyframes deepspider-fadein {
274
266
  from { opacity: 0; transform: translateY(-12px) scale(0.98); }
275
267
  to { opacity: 1; transform: translateY(0) scale(1); }
@@ -337,12 +329,29 @@ export function getAnalysisPanelScript() {
337
329
  font-size: 13px;
338
330
  cursor: pointer;
339
331
  text-align: center;
340
- transition: all 0.2s;
332
+ transition: all 0.25s;
341
333
  box-shadow: 0 2px 8px rgba(72, 187, 120, 0.3);
342
334
  }
343
335
  .deepspider-report-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(72, 187, 120, 0.4); }
344
336
  .deepspider-report-btn:active { transform: translateY(0); }
345
337
  .deepspider-report-btn.visible { display: block; }
338
+ .deepspider-report-btn.viewed {
339
+ margin: 6px 14px;
340
+ padding: 6px 12px;
341
+ background: rgba(72, 187, 120, 0.1);
342
+ border: 1px solid rgba(72, 187, 120, 0.25);
343
+ color: #48bb78;
344
+ font-size: 12px;
345
+ font-weight: 500;
346
+ box-shadow: none;
347
+ border-radius: 8px;
348
+ }
349
+ .deepspider-report-btn.viewed:hover {
350
+ background: rgba(72, 187, 120, 0.18);
351
+ border-color: rgba(72, 187, 120, 0.4);
352
+ transform: none;
353
+ box-shadow: none;
354
+ }
346
355
  /* 报告模态框 */
347
356
  #deepspider-report-modal {
348
357
  display: none;
@@ -414,28 +423,32 @@ export function getAnalysisPanelScript() {
414
423
  flex: 1;
415
424
  overflow-y: auto;
416
425
  padding: 28px 32px;
417
- color: #c9d1d9;
426
+ color: #c9d1d9 !important;
418
427
  font-size: 14px;
419
428
  line-height: 1.7;
420
429
  }
430
+ .deepspider-report-content * {
431
+ color: inherit !important;
432
+ font-family: inherit;
433
+ }
421
434
  .deepspider-report-content::-webkit-scrollbar { width: 10px; }
422
435
  .deepspider-report-content::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 5px; }
423
436
  .deepspider-report-content::-webkit-scrollbar-thumb { background: rgba(99, 179, 237, 0.3); border-radius: 5px; }
424
437
  .deepspider-report-content::-webkit-scrollbar-thumb:hover { background: rgba(99, 179, 237, 0.5); }
425
438
  .deepspider-report-content h1, .deepspider-report-content h2, .deepspider-report-content h3 {
426
- color: #63b3ed;
439
+ color: #63b3ed !important;
427
440
  margin-top: 1.8em;
428
441
  margin-bottom: 0.6em;
429
442
  font-weight: 600;
430
443
  }
431
444
  .deepspider-report-content h1 { font-size: 24px; border-bottom: 1px solid rgba(99, 179, 237, 0.2); padding-bottom: 12px; }
432
445
  .deepspider-report-content h2 { font-size: 20px; }
433
- .deepspider-report-content h3 { font-size: 16px; color: #8b949e; }
446
+ .deepspider-report-content h3 { font-size: 16px; color: #8b949e !important; }
434
447
  .deepspider-report-content h1:first-child { margin-top: 0; }
435
448
  .deepspider-report-content p { margin: 12px 0; }
436
449
  .deepspider-report-content ul, .deepspider-report-content ol { margin: 12px 0; padding-left: 24px; }
437
450
  .deepspider-report-content li { margin: 6px 0; }
438
- .deepspider-report-content strong { color: #e6edf3; font-weight: 600; }
451
+ .deepspider-report-content strong { color: #e6edf3 !important; font-weight: 600; }
439
452
  /* 代码块容器 - 支持复制 */
440
453
  .deepspider-code-block {
441
454
  position: relative;
@@ -487,11 +500,11 @@ export function getAnalysisPanelScript() {
487
500
  background: rgba(99, 179, 237, 0.1);
488
501
  padding: 3px 8px;
489
502
  border-radius: 6px;
490
- font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
503
+ font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace !important;
491
504
  font-size: 13px;
492
- color: #79c0ff;
505
+ color: #79c0ff !important;
493
506
  }
494
- .deepspider-report-content pre code { background: transparent; padding: 0; color: #c9d1d9; }
507
+ .deepspider-report-content pre code { background: transparent; padding: 0; color: #c9d1d9 !important; }
495
508
  .deepspider-report-content table {
496
509
  width: 100%;
497
510
  border-collapse: collapse;
@@ -505,14 +518,16 @@ export function getAnalysisPanelScript() {
505
518
  padding: 12px 16px;
506
519
  text-align: left;
507
520
  }
508
- .deepspider-report-content th { background: rgba(99, 179, 237, 0.08); color: #63b3ed; font-weight: 600; }
521
+ .deepspider-report-content th { background: rgba(99, 179, 237, 0.08); color: #63b3ed !important; font-weight: 600; }
509
522
  .deepspider-report-content tr:hover td { background: rgba(99, 179, 237, 0.03); }
523
+ .deepspider-report-content a { color: #79c0ff !important; text-decoration: underline; }
524
+ .deepspider-report-content blockquote { border-left: 3px solid rgba(99, 179, 237, 0.3); padding-left: 14px; color: #8b949e !important; margin: 12px 0; }
525
+ .deepspider-report-content hr { border: none; border-top: 1px solid rgba(99, 179, 237, 0.15); margin: 20px 0; }
510
526
  .deepspider-messages {
511
527
  flex: 1;
512
528
  overflow-y: auto;
513
529
  padding: 14px;
514
- max-height: 400px;
515
- min-height: 120px;
530
+ min-height: 0;
516
531
  background: rgba(0,0,0,0.15);
517
532
  }
518
533
  .deepspider-messages::-webkit-scrollbar { width: 6px; }
@@ -534,6 +549,7 @@ export function getAnalysisPanelScript() {
534
549
  line-height: 1.6;
535
550
  word-break: break-word;
536
551
  animation: deepspider-msg-in 0.25s ease-out;
552
+ color: #c9d1d9;
537
553
  }
538
554
  .deepspider-msg pre {
539
555
  background: #0d1117;
@@ -589,19 +605,57 @@ export function getAnalysisPanelScript() {
589
605
  .deepspider-msg-user {
590
606
  background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
591
607
  margin-left: 40px;
592
- color: #fff;
608
+ color: #fff !important;
593
609
  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3);
594
610
  }
611
+ .deepspider-msg-user * { color: inherit !important; }
595
612
  .deepspider-msg-assistant {
596
613
  background: rgba(99, 179, 237, 0.08);
597
614
  margin-right: 40px;
598
615
  border: 1px solid rgba(99, 179, 237, 0.15);
616
+ color: #c9d1d9 !important;
617
+ }
618
+ .deepspider-msg-assistant * { color: inherit !important; }
619
+ .deepspider-msg-assistant code { color: #79c0ff !important; }
620
+ .deepspider-msg-assistant pre code { color: #c9d1d9 !important; }
621
+ .deepspider-msg-assistant a { color: #79c0ff !important; }
622
+ /* 选项卡片 */
623
+ .deepspider-choices { margin-top: 10px; }
624
+ .deepspider-choices-question { margin-bottom: 10px; font-size: 13px; color: #c9d1d9; }
625
+ .deepspider-choices-grid { display: flex; flex-direction: column; gap: 8px; }
626
+ .deepspider-choice-btn {
627
+ padding: 10px 14px;
628
+ background: rgba(99, 179, 237, 0.08);
629
+ border: 1px solid rgba(99, 179, 237, 0.2);
630
+ border-radius: 10px;
631
+ color: #c9d1d9;
632
+ cursor: pointer;
633
+ text-align: left;
634
+ transition: all 0.2s;
635
+ font-size: 13px;
599
636
  }
600
- .deepspider-msg-assistant.streaming {
601
- white-space: pre-wrap;
602
- font-family: 'SF Mono', 'Monaco', monospace;
603
- font-size: 12px;
604
- opacity: 0.9;
637
+ .deepspider-choice-btn:hover {
638
+ background: rgba(99, 179, 237, 0.15);
639
+ border-color: rgba(99, 179, 237, 0.4);
640
+ }
641
+ .deepspider-choice-btn.selected {
642
+ background: rgba(99, 179, 237, 0.2);
643
+ border-color: #63b3ed;
644
+ color: #63b3ed;
645
+ }
646
+ .deepspider-choice-label { font-weight: 500; }
647
+ .deepspider-choice-desc { font-size: 11px; color: #8b949e; margin-top: 4px; }
648
+ /* 确认按钮组 */
649
+ .deepspider-confirm-btns { display: flex; gap: 8px; margin-top: 10px; }
650
+ .deepspider-confirm-btn {
651
+ flex: 1; padding: 10px; border-radius: 8px; font-size: 13px;
652
+ font-weight: 500; cursor: pointer; transition: all 0.2s; border: none;
653
+ }
654
+ .deepspider-confirm-yes {
655
+ background: linear-gradient(135deg, #48bb78, #38a169); color: #fff;
656
+ }
657
+ .deepspider-confirm-no {
658
+ background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.15); color: #8b949e;
605
659
  }
606
660
  .deepspider-msg-system {
607
661
  background: transparent;
@@ -616,7 +670,6 @@ export function getAnalysisPanelScript() {
616
670
  display: flex;
617
671
  gap: 10px;
618
672
  background: rgba(0,0,0,0.2);
619
- border-radius: 0 0 16px 16px;
620
673
  }
621
674
  .deepspider-input textarea {
622
675
  flex: 1;
@@ -630,6 +683,9 @@ export function getAnalysisPanelScript() {
630
683
  font-family: inherit;
631
684
  transition: all 0.2s;
632
685
  outline: none;
686
+ min-height: 40px;
687
+ max-height: 110px;
688
+ overflow-y: auto;
633
689
  }
634
690
  .deepspider-input textarea:focus {
635
691
  border-color: rgba(99, 179, 237, 0.5);
@@ -653,6 +709,16 @@ export function getAnalysisPanelScript() {
653
709
  .deepspider-input button:active:not(:disabled) { transform: translateY(0); }
654
710
  .deepspider-input button:disabled { background: rgba(255,255,255,0.1); color: #6e7681; cursor: not-allowed; box-shadow: none; }
655
711
  /* 已选元素标签区域 */
712
+ .deepspider-bottom-section {
713
+ flex-shrink: 0;
714
+ max-height: 50%;
715
+ overflow-y: auto;
716
+ display: flex;
717
+ flex-direction: column;
718
+ }
719
+ .deepspider-bottom-section::-webkit-scrollbar { width: 4px; }
720
+ .deepspider-bottom-section::-webkit-scrollbar-track { background: transparent; }
721
+ .deepspider-bottom-section::-webkit-scrollbar-thumb { background: rgba(99, 179, 237, 0.2); border-radius: 2px; }
656
722
  .deepspider-selected-tags {
657
723
  padding: 10px 14px;
658
724
  border-bottom: 1px solid rgba(99, 179, 237, 0.15);
@@ -718,44 +784,49 @@ export function getAnalysisPanelScript() {
718
784
  /* 功能按钮行 */
719
785
  .deepspider-action-buttons {
720
786
  display: flex;
787
+ flex-direction: column;
721
788
  gap: 8px;
722
789
  padding: 10px 14px;
723
790
  border-top: 1px solid rgba(99, 179, 237, 0.1);
724
791
  background: rgba(0,0,0,0.1);
792
+ flex-shrink: 0;
725
793
  }
726
- .deepspider-action-buttons button {
727
- flex: 1;
728
- padding: 10px 14px;
794
+ .deepspider-quick-actions {
795
+ display: none;
796
+ flex-direction: column;
797
+ gap: 6px;
798
+ width: 100%;
799
+ }
800
+ .deepspider-quick-actions.visible { display: flex; }
801
+ .deepspider-quick-btn {
802
+ width: 100%;
803
+ padding: 9px 14px;
804
+ background: rgba(255,255,255,0.03);
805
+ border: 1px solid rgba(255,255,255,0.1);
729
806
  border-radius: 8px;
807
+ color: #c9d1d9;
730
808
  font-size: 12px;
731
809
  font-weight: 500;
732
810
  cursor: pointer;
811
+ text-align: left;
733
812
  transition: all 0.2s;
734
- display: flex;
735
- align-items: center;
736
- justify-content: center;
737
- gap: 6px;
738
813
  }
739
- .deepspider-btn-analyze {
740
- background: linear-gradient(135deg, #805ad5 0%, #6b46c1 100%);
741
- border: none;
742
- color: #fff;
743
- box-shadow: 0 2px 8px rgba(128, 90, 213, 0.3);
744
- }
745
- .deepspider-btn-analyze:hover:not(:disabled) {
746
- transform: translateY(-1px);
747
- box-shadow: 0 4px 12px rgba(128, 90, 213, 0.4);
748
- }
749
- .deepspider-btn-analyze:disabled {
750
- background: rgba(128, 90, 213, 0.3);
751
- color: rgba(255,255,255,0.5);
752
- cursor: not-allowed;
753
- box-shadow: none;
814
+ .deepspider-quick-btn:hover {
815
+ background: rgba(99, 179, 237, 0.1);
816
+ border-color: rgba(99, 179, 237, 0.3);
817
+ color: #63b3ed;
754
818
  }
755
819
  .deepspider-btn-send-msg {
820
+ width: 100%;
821
+ padding: 10px 14px;
822
+ border-radius: 8px;
823
+ font-size: 12px;
824
+ font-weight: 500;
756
825
  background: linear-gradient(135deg, #63b3ed 0%, #4299e1 100%);
757
826
  border: none;
758
827
  color: #fff;
828
+ cursor: pointer;
829
+ transition: all 0.2s;
759
830
  box-shadow: 0 2px 8px rgba(99, 179, 237, 0.3);
760
831
  }
761
832
  .deepspider-btn-send-msg:hover:not(:disabled) {
@@ -768,6 +839,52 @@ export function getAnalysisPanelScript() {
768
839
  cursor: not-allowed;
769
840
  box-shadow: none;
770
841
  }
842
+ .deepspider-select-banner {
843
+ display: none;
844
+ padding: 8px 14px;
845
+ background: linear-gradient(135deg, rgba(99,179,237,0.15) 0%, rgba(99,179,237,0.08) 100%);
846
+ border-bottom: 1px solid rgba(99,179,237,0.2);
847
+ font-size: 12px;
848
+ color: #63b3ed;
849
+ align-items: center;
850
+ justify-content: space-between;
851
+ animation: deepspider-fadein 0.2s ease-out;
852
+ }
853
+ .deepspider-select-banner.visible { display: flex; }
854
+ .deepspider-select-banner button {
855
+ background: rgba(255,255,255,0.1);
856
+ border: 1px solid rgba(99,179,237,0.3);
857
+ color: #63b3ed;
858
+ padding: 4px 10px;
859
+ border-radius: 6px;
860
+ font-size: 11px;
861
+ cursor: pointer;
862
+ }
863
+ #deepspider-reopen-btn {
864
+ display: none;
865
+ position: fixed;
866
+ bottom: 24px;
867
+ right: 24px;
868
+ width: 44px;
869
+ height: 44px;
870
+ background: linear-gradient(135deg, #1e2530 0%, #161b22 100%);
871
+ border: 1px solid rgba(99, 179, 237, 0.3);
872
+ border-radius: 50%;
873
+ color: #63b3ed;
874
+ font-size: 20px;
875
+ cursor: pointer;
876
+ z-index: 2147483640;
877
+ align-items: center;
878
+ justify-content: center;
879
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4);
880
+ transition: all 0.2s;
881
+ }
882
+ #deepspider-reopen-btn:hover {
883
+ transform: scale(1.1);
884
+ box-shadow: 0 6px 24px rgba(99,179,237,0.3);
885
+ border-color: rgba(99,179,237,0.5);
886
+ }
887
+ #deepspider-reopen-btn.visible { display: flex; }
771
888
  #deepspider-overlay {
772
889
  position: fixed;
773
890
  pointer-events: none;
@@ -793,7 +910,6 @@ export function getAnalysisPanelScript() {
793
910
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
794
911
  border: 1px solid rgba(99, 179, 237, 0.2);
795
912
  }
796
- #deepspider-config-modal.visible { display: flex !important; }
797
913
  \`;
798
914
  document.head.appendChild(style);
799
915
 
@@ -812,23 +928,37 @@ export function getAnalysisPanelScript() {
812
928
  <button id="deepspider-btn-close" title="关闭">&times;</button>
813
929
  </div>
814
930
  </div>
815
- <button id="deepspider-report-btn" class="deepspider-report-btn">📊 查看分析报告</button>
816
- <div class="deepspider-messages" id="deepspider-messages">
817
- <div class="deepspider-empty">
818
- <div class="deepspider-empty-icon">🔍</div>
819
- 点击上方 ⦿ 按钮选择页面元素<br>或在下方输入问题开始分析
820
- </div>
931
+ <div class="deepspider-select-banner" id="deepspider-select-banner">
932
+ <span>选择模式 · 点击选择元素 · ESC 退出</span>
933
+ <button id="deepspider-exit-select">退出选择</button>
821
934
  </div>
935
+ <button id="deepspider-report-btn" class="deepspider-report-btn">📊 查看分析报告</button>
936
+ <div class="deepspider-messages" id="deepspider-messages"></div>
937
+ <div class="deepspider-bottom-section">
822
938
  <div class="deepspider-selected-tags" id="deepspider-selected-tags">
823
939
  <div class="deepspider-selected-tags-list" id="deepspider-selected-tags-list"></div>
824
940
  </div>
825
941
  <div class="deepspider-input">
826
- <textarea id="deepspider-chat-input" placeholder="输入问题,按 Enter 发送..." rows="2"></textarea>
942
+ <textarea id="deepspider-chat-input" placeholder="输入问题,按 Enter 发送..." rows="1"></textarea>
827
943
  </div>
828
944
  <div class="deepspider-action-buttons" id="deepspider-action-buttons">
829
- <button class="deepspider-btn-analyze" id="deepspider-btn-analyze" disabled>📊 完整分析</button>
945
+ <div class="deepspider-quick-actions" id="deepspider-quick-actions">
946
+ <button class="deepspider-quick-btn" data-action="trace">🔍 追踪数据来源</button>
947
+ <button class="deepspider-quick-btn" data-action="decrypt">🔓 分析加密参数</button>
948
+ <button class="deepspider-quick-btn" data-action="full">🚀 完整分析并生成爬虫</button>
949
+ <button class="deepspider-quick-btn" data-action="extract">📋 提取页面结构</button>
950
+ </div>
830
951
  <button class="deepspider-btn-send-msg" id="deepspider-btn-send-msg" disabled>发送</button>
831
952
  </div>
953
+ </div>
954
+ <div class="deepspider-resize-handle top"></div>
955
+ <div class="deepspider-resize-handle bottom"></div>
956
+ <div class="deepspider-resize-handle left"></div>
957
+ <div class="deepspider-resize-handle right"></div>
958
+ <div class="deepspider-resize-handle top-left"></div>
959
+ <div class="deepspider-resize-handle top-right"></div>
960
+ <div class="deepspider-resize-handle bottom-left"></div>
961
+ <div class="deepspider-resize-handle bottom-right"></div>
832
962
  \`;
833
963
  document.body.appendChild(panel);
834
964
 
@@ -846,306 +976,26 @@ export function getAnalysisPanelScript() {
846
976
  \`;
847
977
  document.body.appendChild(reportModal);
848
978
 
849
- // ========== 创建配置弹窗 ==========
850
- const configModal = document.createElement('div');
851
- configModal.id = 'deepspider-config-modal';
852
- configModal.style.cssText = 'display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(13,17,23,0.85);z-index:2147483649;justify-content:center;align-items:center;';
853
- configModal.innerHTML = \`
854
- <div style="width:400px;background:linear-gradient(180deg,#1e2530,#161b22);border:1px solid rgba(99,179,237,0.2);border-radius:16px;overflow:hidden;">
855
- <div style="padding:16px 20px;background:linear-gradient(180deg,rgba(99,179,237,0.08),transparent);border-bottom:1px solid rgba(99,179,237,0.15);display:flex;justify-content:space-between;align-items:center;">
856
- <h4 style="margin:0;color:#63b3ed;font-size:15px;">⚙️ 配置爬虫</h4>
857
- <button id="deepspider-config-close" style="background:none;border:none;color:#8b949e;font-size:20px;cursor:pointer;">&times;</button>
858
- </div>
859
- <div style="padding:16px 20px;">
860
- <div style="margin-bottom:16px;">
861
- <label style="display:block;color:#8b949e;font-size:12px;margin-bottom:6px;">抓取方式</label>
862
- <select id="deepspider-grab-method" style="width:100%;padding:8px 12px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:#c9d1d9;font-size:13px;">
863
- <option value="browser">浏览器渲染 (browser)</option>
864
- <option value="html">静态HTML (html)</option>
865
- <option value="api">API请求 (api)</option>
866
- </select>
867
- </div>
868
- <div style="margin-bottom:16px;">
869
- <label style="display:block;color:#8b949e;font-size:12px;margin-bottom:6px;">最大页数</label>
870
- <input type="number" id="deepspider-max-page" value="10" style="width:100%;padding:8px 12px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:#c9d1d9;font-size:13px;box-sizing:border-box;">
871
- </div>
872
- <div style="margin-bottom:16px;">
873
- <label style="display:block;color:#8b949e;font-size:12px;margin-bottom:6px;">下一页按钮 XPath(可选)</label>
874
- <input type="text" id="deepspider-next-xpath" placeholder="例如: //a[contains(text(),'下一页')]" style="width:100%;padding:8px 12px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:#c9d1d9;font-size:13px;box-sizing:border-box;">
875
- </div>
876
- <button id="deepspider-config-submit" style="width:100%;padding:12px;background:linear-gradient(135deg,#48bb78,#38a169);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:600;cursor:pointer;">生成爬虫</button>
877
- </div>
878
- </div>
879
- \`;
880
- document.body.appendChild(configModal);
979
+ // ========== 创建重新打开按钮 ==========
980
+ const reopenBtn = document.createElement('div');
981
+ reopenBtn.id = 'deepspider-reopen-btn';
982
+ reopenBtn.textContent = '🔍';
983
+ document.body.appendChild(reopenBtn);
881
984
 
882
- // 配置弹窗事件
883
- document.getElementById('deepspider-config-close').onclick = () => {
884
- configModal.classList.remove('visible');
885
- };
886
- configModal.addEventListener('click', (e) => {
887
- if (e.target === configModal) configModal.classList.remove('visible');
888
- });
889
- document.getElementById('deepspider-config-submit').onclick = submitConfig;
890
-
891
- // 创建空阶段对象
892
- function createStage(name) {
893
- return { name: name, fields: [], entry: null, pagination: null };
894
- }
895
-
896
- // 更新阶段面板显示
897
- function updateStagesPanel() {
898
- let stagesPanel = document.getElementById('deepspider-stages-panel');
899
- if (!stagesPanel) {
900
- stagesPanel = document.createElement('div');
901
- stagesPanel.id = 'deepspider-stages-panel';
902
- stagesPanel.style.cssText = 'padding:10px 14px;border-top:1px solid rgba(99,179,237,0.15);background:rgba(0,0,0,0.1);';
903
- const inputArea = panel.querySelector('.deepspider-input');
904
- panel.insertBefore(stagesPanel, inputArea);
905
- }
906
-
907
- const totalFields = deepspider.stages.reduce((sum, s) => sum + s.fields.length, 0);
908
- if (totalFields === 0 && !deepspider.stages.some(s => s.entry)) {
909
- stagesPanel.style.display = 'none';
910
- return;
911
- }
912
-
913
- stagesPanel.style.display = 'block';
914
- const currentStage = deepspider.stages[deepspider.currentStageIndex];
915
-
916
- // 阶段标签
917
- const stageTabs = deepspider.stages.map((s, i) => {
918
- const isActive = i === deepspider.currentStageIndex;
919
- const fieldCount = s.fields.length;
920
- const hasEntry = s.entry ? ' →' : '';
921
- return '<span data-stage="' + i + '" style="' +
922
- 'background:' + (isActive ? 'rgba(99,179,237,0.3)' : 'rgba(99,179,237,0.1)') + ';' +
923
- 'border:1px solid ' + (isActive ? 'rgba(99,179,237,0.5)' : 'rgba(99,179,237,0.2)') + ';' +
924
- 'padding:4px 10px;border-radius:6px;font-size:11px;color:#63b3ed;cursor:pointer;' +
925
- 'display:inline-flex;align-items:center;gap:4px;">' +
926
- s.name + ' (' + fieldCount + ')' + hasEntry + '</span>';
927
- }).join('');
928
-
929
- // 当前阶段的字段
930
- const fieldTags = currentStage.fields.map((f, i) =>
931
- '<span style="background:rgba(72,187,120,0.15);border:1px solid rgba(72,187,120,0.2);' +
932
- 'padding:4px 8px;border-radius:6px;font-size:11px;color:#48bb78;' +
933
- 'display:inline-flex;align-items:center;gap:4px;">' +
934
- f.name + '<span style="cursor:pointer;color:#8b949e;" data-remove="' + i + '">&times;</span></span>'
935
- ).join('');
936
-
937
- // 入口显示
938
- const entryTag = currentStage.entry ?
939
- '<span style="background:rgba(237,137,54,0.15);border:1px solid rgba(237,137,54,0.2);' +
940
- 'padding:4px 8px;border-radius:6px;font-size:11px;color:#ed8936;' +
941
- 'display:inline-flex;align-items:center;gap:4px;">' +
942
- currentStage.entry.field + ' → ' + currentStage.entry.to_stage +
943
- '<span style="cursor:pointer;color:#8b949e;" data-remove-entry="1">&times;</span></span>' : '';
944
-
945
- stagesPanel.innerHTML =
946
- '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
947
- '<div style="display:flex;gap:6px;flex-wrap:wrap;">' + stageTabs +
948
- '<span id="deepspider-add-stage" style="background:rgba(255,255,255,0.05);border:1px dashed rgba(255,255,255,0.2);' +
949
- 'padding:4px 10px;border-radius:6px;font-size:11px;color:#8b949e;cursor:pointer;">+ 阶段</span></div>' +
950
- '<div style="display:flex;gap:6px;">' +
951
- '<button id="deepspider-gen-config" style="background:linear-gradient(135deg,#48bb78,#38a169);' +
952
- 'border:none;color:#fff;padding:4px 10px;border-radius:6px;font-size:11px;cursor:pointer;">生成配置</button>' +
953
- '<button id="deepspider-clear-all" style="background:rgba(248,81,73,0.2);border:1px solid rgba(248,81,73,0.3);' +
954
- 'color:#f85149;padding:4px 10px;border-radius:6px;font-size:11px;cursor:pointer;">清空</button>' +
955
- '</div></div>' +
956
- '<div style="margin-bottom:6px;font-size:11px;color:#8b949e;">阶段: ' + currentStage.name + '</div>' +
957
- '<div style="display:flex;flex-wrap:wrap;gap:6px;">' + fieldTags + entryTag + '</div>';
958
-
959
- bindStagesPanelEvents(stagesPanel);
960
- }
961
-
962
- // 绑定阶段面板事件
963
- function bindStagesPanelEvents(stagesPanel) {
964
- // 阶段切换
965
- stagesPanel.querySelectorAll('[data-stage]').forEach(tab => {
966
- tab.onclick = () => {
967
- const idx = parseInt(tab.dataset.stage);
968
- if (idx >= 0 && idx < deepspider.stages.length) {
969
- deepspider.currentStageIndex = idx;
970
- saveStages();
971
- updateStagesPanel();
972
- }
973
- };
974
- });
975
- // 添加阶段
976
- document.getElementById('deepspider-add-stage').onclick = addStage;
977
- // 生成配置
978
- document.getElementById('deepspider-gen-config').onclick = generateConfig;
979
- // 清空
980
- document.getElementById('deepspider-clear-all').onclick = clearAll;
981
- // 移除字段
982
- stagesPanel.querySelectorAll('[data-remove]').forEach(btn => {
983
- btn.onclick = () => removeField(parseInt(btn.dataset.remove));
984
- });
985
- // 移除入口
986
- stagesPanel.querySelectorAll('[data-remove-entry]').forEach(btn => {
987
- btn.onclick = removeEntry;
988
- });
989
- }
990
-
991
- // 移除当前阶段的字段
992
- function removeField(index) {
993
- const currentStage = deepspider.stages[deepspider.currentStageIndex];
994
- if (!currentStage || index < 0 || index >= currentStage.fields.length) return;
995
- currentStage.fields.splice(index, 1);
996
- saveStages();
997
- updateStagesPanel();
998
- }
999
-
1000
- // 移除当前阶段的入口
1001
- function removeEntry() {
1002
- const currentStage = deepspider.stages[deepspider.currentStageIndex];
1003
- if (!currentStage) return;
1004
- currentStage.entry = null;
1005
- saveStages();
1006
- updateStagesPanel();
1007
- }
1008
-
1009
- // 添加新阶段
1010
- function addStage() {
1011
- const name = 'stage_' + (deepspider.stages.length + 1);
1012
- deepspider.stages.push(createStage(name));
1013
- deepspider.currentStageIndex = deepspider.stages.length - 1;
1014
- saveStages();
1015
- updateStagesPanel();
1016
- addMessage('system', '✅ 已添加阶段: ' + name);
1017
- }
1018
-
1019
- // 清空所有阶段
1020
- function clearAll() {
1021
- deepspider.stages = [createStage('list')];
1022
- deepspider.currentStageIndex = 0;
1023
- saveStages();
1024
- updateStagesPanel();
1025
- }
1026
-
1027
- // 生成爬虫配置 - 显示配置弹窗
1028
- function generateConfig() {
1029
- showConfigModal();
1030
- }
1031
-
1032
- // 显示配置弹窗
1033
- function showConfigModal() {
1034
- const modal = document.getElementById('deepspider-config-modal');
1035
- if (!modal) return;
1036
-
1037
- // 更新阶段分页配置区域
1038
- const stagesConfigHtml = deepspider.stages.map((stage, i) => {
1039
- const hasPagination = stage.pagination !== null;
1040
- return '<div style="margin-bottom:12px;padding:10px;background:rgba(0,0,0,0.2);border-radius:8px;">' +
1041
- '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
1042
- '<span style="color:#63b3ed;font-size:12px;font-weight:600;">' + stage.name + '</span>' +
1043
- '<span style="color:#8b949e;font-size:11px;">' + stage.fields.length + ' 字段' +
1044
- (stage.entry ? ' → ' + stage.entry.to_stage : '') + '</span></div>' +
1045
- '<div style="display:flex;gap:8px;align-items:center;">' +
1046
- '<label style="color:#8b949e;font-size:11px;white-space:nowrap;">分页:</label>' +
1047
- '<input type="checkbox" data-stage-pagination="' + i + '"' + (hasPagination ? ' checked' : '') + '>' +
1048
- '<input type="text" data-stage-xpath="' + i + '" placeholder="下一页XPath" ' +
1049
- 'value="' + (stage.pagination?.next_page_xpath || '') + '" ' +
1050
- 'style="flex:1;padding:4px 8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);' +
1051
- 'border-radius:4px;color:#c9d1d9;font-size:11px;' + (hasPagination ? '' : 'opacity:0.5;') + '">' +
1052
- '<input type="number" data-stage-max="' + i + '" placeholder="页数" ' +
1053
- 'value="' + (stage.pagination?.max_page || 10) + '" ' +
1054
- 'style="width:50px;padding:4px 8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);' +
1055
- 'border-radius:4px;color:#c9d1d9;font-size:11px;' + (hasPagination ? '' : 'opacity:0.5;') + '">' +
1056
- '</div></div>';
1057
- }).join('');
1058
-
1059
- const configContent = modal.querySelector('div > div:last-child');
1060
- configContent.innerHTML =
1061
- '<div style="margin-bottom:16px;">' +
1062
- '<label style="display:block;color:#8b949e;font-size:12px;margin-bottom:6px;">抓取方式</label>' +
1063
- '<select id="deepspider-grab-method" style="width:100%;padding:8px 12px;background:rgba(255,255,255,0.05);' +
1064
- 'border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:#c9d1d9;font-size:13px;">' +
1065
- '<option value="browser">浏览器渲染 (browser)</option>' +
1066
- '<option value="html">静态HTML (html)</option>' +
1067
- '<option value="api">API请求 (api)</option></select></div>' +
1068
- '<div style="margin-bottom:16px;">' +
1069
- '<label style="display:block;color:#8b949e;font-size:12px;margin-bottom:6px;">阶段配置</label>' +
1070
- stagesConfigHtml + '</div>' +
1071
- '<button id="deepspider-config-submit" style="width:100%;padding:12px;' +
1072
- 'background:linear-gradient(135deg,#48bb78,#38a169);border:none;border-radius:8px;' +
1073
- 'color:#fff;font-size:13px;font-weight:600;cursor:pointer;">生成爬虫</button>';
1074
-
1075
- // 绑定分页复选框事件
1076
- configContent.querySelectorAll('[data-stage-pagination]').forEach(checkbox => {
1077
- checkbox.onchange = () => {
1078
- const idx = checkbox.dataset.stagePagination;
1079
- const xpathInput = configContent.querySelector('[data-stage-xpath="' + idx + '"]');
1080
- const maxInput = configContent.querySelector('[data-stage-max="' + idx + '"]');
1081
- xpathInput.style.opacity = checkbox.checked ? '1' : '0.5';
1082
- maxInput.style.opacity = checkbox.checked ? '1' : '0.5';
1083
- };
1084
- });
1085
-
1086
- document.getElementById('deepspider-config-submit').onclick = submitConfig;
1087
- modal.classList.add('visible');
1088
- }
1089
-
1090
- // 提交配置
1091
- function submitConfig() {
1092
- const modal = document.getElementById('deepspider-config-modal');
1093
- const grabMethod = document.getElementById('deepspider-grab-method')?.value || 'browser';
1094
-
1095
- // 收集各阶段的分页配置
1096
- deepspider.stages.forEach((stage, i) => {
1097
- const checkbox = modal.querySelector('[data-stage-pagination="' + i + '"]');
1098
- const xpathInput = modal.querySelector('[data-stage-xpath="' + i + '"]');
1099
- const maxInput = modal.querySelector('[data-stage-max="' + i + '"]');
1100
-
1101
- if (checkbox?.checked && xpathInput?.value) {
1102
- stage.pagination = {
1103
- next_page_xpath: xpathInput.value,
1104
- max_page: parseInt(maxInput?.value) || 10
1105
- };
1106
- } else {
1107
- stage.pagination = null;
1108
- }
1109
- });
1110
- saveStages();
1111
-
1112
- // 构建阶段化配置
1113
- const config = {
1114
- url: location.href,
1115
- grab_method: grabMethod,
1116
- stages: deepspider.stages.map(s => ({
1117
- name: s.name,
1118
- fields: s.fields.map(f => ({
1119
- name: f.name,
1120
- xpath: f.xpath,
1121
- type: f.type
1122
- })),
1123
- entry: s.entry,
1124
- pagination: s.pagination
1125
- }))
1126
- };
1127
-
1128
- modal?.classList.remove('visible');
985
+ reopenBtn.onclick = () => {
1129
986
  panel.classList.add('visible');
987
+ reopenBtn.classList.remove('visible');
988
+ };
1130
989
 
1131
- const totalFields = deepspider.stages.reduce((sum, s) => sum + s.fields.length, 0);
1132
- addMessage('user', '生成爬虫配置 (' + deepspider.stages.length + ' 阶段, ' + totalFields + ' 字段)');
1133
- addMessage('system', '正在生成配置...');
1134
-
1135
- if (typeof __deepspider_send__ === 'function') {
1136
- __deepspider_send__(JSON.stringify({
1137
- __ds__: true,
1138
- type: 'generate-config',
1139
- config: config,
1140
- url: location.href
1141
- }));
1142
- }
1143
- }
990
+ // 退出选择按钮
991
+ document.getElementById('deepspider-exit-select').onclick = () => {
992
+ stopSelectMode();
993
+ };
1144
994
 
1145
995
  // 点击背景关闭模态框
1146
996
  reportModal.addEventListener('click', (e) => {
1147
997
  if (e.target === reportModal) {
1148
- reportModal.classList.remove('visible');
998
+ closeReportModal();
1149
999
  }
1150
1000
  });
1151
1001
 
@@ -1158,30 +1008,105 @@ export function getAnalysisPanelScript() {
1158
1008
  infoBox.id = 'deepspider-info';
1159
1009
  document.body.appendChild(infoBox);
1160
1010
 
1161
- // ========== 面板拖动 ==========
1011
+ // ========== 面板拖动 + 边缘缩放 ==========
1162
1012
  let isDragging = false;
1013
+ let isResizing = false;
1014
+ let resizeDir = '';
1163
1015
  let dragOffset = { x: 0, y: 0 };
1016
+ let startRect = null;
1017
+ let startMouse = { x: 0, y: 0 };
1164
1018
  const header = panel.querySelector('.deepspider-header');
1019
+ const MIN_W = 320, MIN_H = 300;
1165
1020
 
1021
+ // 拖动 header 移动面板
1166
1022
  header.addEventListener('mousedown', (e) => {
1167
1023
  if (e.target.tagName === 'BUTTON') return;
1168
1024
  isDragging = true;
1169
- dragOffset.x = e.clientX - panel.offsetLeft;
1170
- dragOffset.y = e.clientY - panel.offsetTop;
1025
+ // 确保 panel left/top 定位
1026
+ const rect = panel.getBoundingClientRect();
1027
+ panel.style.left = rect.left + 'px';
1028
+ panel.style.top = rect.top + 'px';
1029
+ panel.style.right = 'auto';
1030
+ dragOffset.x = e.clientX - rect.left;
1031
+ dragOffset.y = e.clientY - rect.top;
1032
+ panel.classList.add('resizing');
1033
+ e.preventDefault();
1034
+ });
1035
+
1036
+ // 边缘手柄 mousedown → 开始缩放
1037
+ panel.querySelectorAll('.deepspider-resize-handle').forEach(handle => {
1038
+ handle.addEventListener('mousedown', (e) => {
1039
+ isResizing = true;
1040
+ resizeDir = handle.className.replace('deepspider-resize-handle ', '');
1041
+ const rect = panel.getBoundingClientRect();
1042
+ startRect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
1043
+ startMouse = { x: e.clientX, y: e.clientY };
1044
+ // 切换到 left/top 定位
1045
+ panel.style.left = rect.left + 'px';
1046
+ panel.style.top = rect.top + 'px';
1047
+ panel.style.right = 'auto';
1048
+ panel.classList.add('resizing');
1049
+ e.preventDefault();
1050
+ e.stopPropagation();
1051
+ });
1171
1052
  });
1172
1053
 
1173
1054
  document.addEventListener('mousemove', (e) => {
1174
- if (!isDragging) return;
1175
- panel.style.left = (e.clientX - dragOffset.x) + 'px';
1176
- panel.style.top = (e.clientY - dragOffset.y) + 'px';
1177
- panel.style.right = 'auto';
1055
+ if (isDragging) {
1056
+ panel.style.left = (e.clientX - dragOffset.x) + 'px';
1057
+ panel.style.top = (e.clientY - dragOffset.y) + 'px';
1058
+ return;
1059
+ }
1060
+ if (!isResizing) return;
1061
+
1062
+ const dx = e.clientX - startMouse.x;
1063
+ const dy = e.clientY - startMouse.y;
1064
+ const dir = resizeDir;
1065
+ let { left, top, width, height } = startRect;
1066
+
1067
+ // 水平
1068
+ if (dir.includes('right')) {
1069
+ width = Math.max(MIN_W, startRect.width + dx);
1070
+ } else if (dir.includes('left')) {
1071
+ const newW = Math.max(MIN_W, startRect.width - dx);
1072
+ left = startRect.left + (startRect.width - newW);
1073
+ width = newW;
1074
+ }
1075
+ // 垂直
1076
+ if (dir.includes('bottom')) {
1077
+ height = Math.max(MIN_H, startRect.height + dy);
1078
+ } else if (dir.includes('top') && dir !== 'top-left' && dir !== 'top-right') {
1079
+ // 纯 top
1080
+ const newH = Math.max(MIN_H, startRect.height - dy);
1081
+ top = startRect.top + (startRect.height - newH);
1082
+ height = newH;
1083
+ }
1084
+ if (dir === 'top-left' || dir === 'top-right') {
1085
+ const newH = Math.max(MIN_H, startRect.height - dy);
1086
+ top = startRect.top + (startRect.height - newH);
1087
+ height = newH;
1088
+ }
1089
+
1090
+ panel.style.left = left + 'px';
1091
+ panel.style.top = top + 'px';
1092
+ panel.style.width = width + 'px';
1093
+ panel.style.height = height + 'px';
1178
1094
  });
1179
1095
 
1180
- document.addEventListener('mouseup', () => { isDragging = false; });
1096
+ document.addEventListener('mouseup', () => {
1097
+ if (isDragging || isResizing) {
1098
+ panel.classList.remove('resizing');
1099
+ }
1100
+ isDragging = false;
1101
+ isResizing = false;
1102
+ resizeDir = '';
1103
+ startRect = null;
1104
+ });
1181
1105
 
1182
1106
  // ========== 关闭按钮 ==========
1183
1107
  document.getElementById('deepspider-btn-close').onclick = () => {
1184
1108
  panel.classList.remove('visible');
1109
+ reopenBtn.classList.add('visible');
1185
1110
  };
1186
1111
 
1187
1112
  // ========== 最小化按钮 ==========
@@ -1231,6 +1156,7 @@ export function getAnalysisPanelScript() {
1231
1156
  document.addEventListener('keydown', onSelectKey, true);
1232
1157
  // 通知所有 iframe 进入选择模式
1233
1158
  broadcastToIframes({ type: 'deepspider-start-select' });
1159
+ document.getElementById('deepspider-select-banner').classList.add('visible');
1234
1160
  }
1235
1161
 
1236
1162
  function stopSelectMode() {
@@ -1245,6 +1171,7 @@ export function getAnalysisPanelScript() {
1245
1171
  document.removeEventListener('keydown', onSelectKey, true);
1246
1172
  // 通知所有 iframe 退出选择模式
1247
1173
  broadcastToIframes({ type: 'deepspider-stop-select' });
1174
+ document.getElementById('deepspider-select-banner').classList.remove('visible');
1248
1175
  }
1249
1176
 
1250
1177
  function onSelectMove(e) {
@@ -1304,43 +1231,142 @@ export function getAnalysisPanelScript() {
1304
1231
  // ========== 消息渲染 ==========
1305
1232
  const messagesEl = document.getElementById('deepspider-messages');
1306
1233
 
1307
- function addMessage(role, content, complete = true) {
1308
- // assistant 消息默认未完成(等待流式输出结束)
1309
- const isComplete = role === 'assistant' ? complete : true;
1310
- deepspider.chatMessages.push({ __ds__: true, role, content, time: Date.now(), complete: isComplete });
1234
+ function addStructuredMessage(type, data) {
1235
+ const role = type === 'user' ? 'user' : type === 'system' ? 'system' : 'assistant';
1236
+ deepspider.chatMessages.push({ __ds__: true, role, type, data, time: Date.now() });
1311
1237
  saveMessages();
1312
1238
  renderMessages();
1313
1239
  }
1314
1240
 
1241
+ function addMessage(role, content) {
1242
+ const type = role === 'system' ? 'system' : role === 'user' ? 'user' : 'text';
1243
+ addStructuredMessage(type, { content });
1244
+ }
1245
+
1246
+ function getEmptyStateHtml() {
1247
+ return '<div class="deepspider-empty">' +
1248
+ '<div class="deepspider-empty-icon">🔍</div>' +
1249
+ '<div style="font-size:14px;color:#c9d1d9;margin-bottom:16px;">开始分析</div>' +
1250
+ '<div style="text-align:left;display:inline-block;">' +
1251
+ '<div style="margin-bottom:10px;display:flex;gap:8px;align-items:flex-start;">' +
1252
+ '<span style="color:#63b3ed;font-weight:600;">1.</span>' +
1253
+ '<span>在网站上操作(登录、翻页等),系统自动记录数据</span></div>' +
1254
+ '<div style="margin-bottom:10px;display:flex;gap:8px;align-items:flex-start;">' +
1255
+ '<span style="color:#63b3ed;font-weight:600;">2.</span>' +
1256
+ '<span>点击 <b style="color:#63b3ed;">⦿</b> 选择目标数据元素</span></div>' +
1257
+ '<div style="display:flex;gap:8px;align-items:flex-start;">' +
1258
+ '<span style="color:#63b3ed;font-weight:600;">3.</span>' +
1259
+ '<span>选择操作或在下方提问</span></div>' +
1260
+ '</div></div>';
1261
+ }
1262
+
1315
1263
  function renderMessages() {
1316
1264
  const msgs = deepspider.chatMessages;
1317
1265
  if (msgs.length === 0) {
1318
- messagesEl.innerHTML = \`
1319
- <div class="deepspider-empty">
1320
- <div class="deepspider-empty-icon">🔍</div>
1321
- 点击上方 ⦿ 按钮选择页面元素<br>或在下方输入问题开始分析
1322
- </div>
1323
- \`;
1266
+ messagesEl.innerHTML = getEmptyStateHtml();
1324
1267
  } else {
1325
- messagesEl.innerHTML = msgs.map(m => {
1326
- let content;
1327
- // 兼容旧消息:没有 complete 字段视为已完成
1328
- const isComplete = m.complete !== false;
1329
- if (m.role === 'assistant') {
1330
- content = isComplete ? parseMarkdown(m.content) : escapeHtml(m.content);
1331
- } else {
1332
- content = escapeHtml(m.content);
1333
- }
1334
- const streamingClass = (m.role === 'assistant' && !isComplete) ? ' streaming' : '';
1335
- return '<div class="deepspider-msg deepspider-msg-' + m.role + streamingClass + '">' + content + '</div>';
1336
- }).join('');
1337
- // 在 DOM 上处理文件路径链接化
1268
+ messagesEl.innerHTML = msgs.map(m => renderSingleMessage(m)).join('');
1338
1269
  linkifyFilePaths(messagesEl);
1339
1270
  bindFilePathClicks(messagesEl);
1271
+ bindChoiceClicks(messagesEl);
1272
+ bindConfirmClicks(messagesEl);
1340
1273
  }
1341
1274
  messagesEl.scrollTop = messagesEl.scrollHeight;
1342
1275
  }
1343
1276
 
1277
+ function renderSingleMessage(m) {
1278
+ switch (m.type) {
1279
+ case 'text':
1280
+ return '<div class="deepspider-msg deepspider-msg-assistant">' + parseMarkdown(m.data.content) + '</div>';
1281
+ case 'system':
1282
+ return '<div class="deepspider-msg deepspider-msg-system">' + escapeHtml(m.data.content) + '</div>';
1283
+ case 'user':
1284
+ return '<div class="deepspider-msg deepspider-msg-user">' + escapeHtml(m.data.content) + '</div>';
1285
+ case 'choices':
1286
+ return renderChoicesMessage(m);
1287
+ case 'confirm':
1288
+ return renderConfirmMessage(m);
1289
+ default:
1290
+ return '<div class="deepspider-msg deepspider-msg-system">' + escapeHtml(JSON.stringify(m.data)) + '</div>';
1291
+ }
1292
+ }
1293
+
1294
+ function renderChoicesMessage(m) {
1295
+ const d = m.data;
1296
+ const answered = m.answered;
1297
+ let html = '<div class="deepspider-msg deepspider-msg-assistant">';
1298
+ html += '<div class="deepspider-choices-question">' + escapeHtml(d.question) + '</div>';
1299
+ html += '<div class="deepspider-choices-grid">';
1300
+ d.options.forEach(opt => {
1301
+ const selected = answered === opt.id ? ' selected' : '';
1302
+ const disabled = answered ? ' style="pointer-events:none;opacity:0.6;"' : '';
1303
+ html += '<div class="deepspider-choice-btn' + selected + '" data-choice-id="' + escapeHtml(opt.id) + '"' + disabled + '>';
1304
+ html += '<div class="deepspider-choice-label">' + escapeHtml(opt.label) + '</div>';
1305
+ if (opt.description) html += '<div class="deepspider-choice-desc">' + escapeHtml(opt.description) + '</div>';
1306
+ html += '</div>';
1307
+ });
1308
+ html += '</div></div>';
1309
+ return html;
1310
+ }
1311
+
1312
+ function renderConfirmMessage(m) {
1313
+ const d = m.data;
1314
+ const answered = m.answered !== undefined;
1315
+ let html = '<div class="deepspider-msg deepspider-msg-assistant">';
1316
+ html += '<div class="deepspider-choices-question">' + escapeHtml(d.question) + '</div>';
1317
+ if (!answered) {
1318
+ html += '<div class="deepspider-confirm-btns">';
1319
+ html += '<button class="deepspider-confirm-btn deepspider-confirm-yes" data-confirm="true">' + escapeHtml(d.confirmText || '确认') + '</button>';
1320
+ html += '<button class="deepspider-confirm-btn deepspider-confirm-no" data-confirm="false">' + escapeHtml(d.cancelText || '取消') + '</button>';
1321
+ html += '</div>';
1322
+ } else {
1323
+ html += '<div style="color:#8b949e;font-size:12px;margin-top:6px;">' + (m.answered ? '✅ 已确认' : '❌ 已取消') + '</div>';
1324
+ }
1325
+ html += '</div>';
1326
+ return html;
1327
+ }
1328
+
1329
+ function bindChoiceClicks(container) {
1330
+ container.querySelectorAll('.deepspider-choice-btn:not([style*="pointer-events"])').forEach(btn => {
1331
+ btn.onclick = () => {
1332
+ const choiceId = btn.dataset.choiceId;
1333
+ const msgs = deepspider.chatMessages;
1334
+ let chosenLabel = choiceId;
1335
+ for (let i = msgs.length - 1; i >= 0; i--) {
1336
+ if (msgs[i].type === 'choices' && !msgs[i].answered) {
1337
+ msgs[i].answered = choiceId;
1338
+ const opt = msgs[i].data.options.find(o => o.id === choiceId);
1339
+ if (opt) chosenLabel = opt.label;
1340
+ break;
1341
+ }
1342
+ }
1343
+ addStructuredMessage('user', { content: chosenLabel });
1344
+ if (typeof __deepspider_send__ === 'function') {
1345
+ __deepspider_send__(JSON.stringify({ __ds__: true, type: 'choice', value: chosenLabel }));
1346
+ }
1347
+ };
1348
+ });
1349
+ }
1350
+
1351
+ function bindConfirmClicks(container) {
1352
+ container.querySelectorAll('[data-confirm]').forEach(btn => {
1353
+ btn.onclick = () => {
1354
+ const confirmed = btn.dataset.confirm === 'true';
1355
+ const msgs = deepspider.chatMessages;
1356
+ for (let i = msgs.length - 1; i >= 0; i--) {
1357
+ if (msgs[i].type === 'confirm' && msgs[i].answered === undefined) {
1358
+ msgs[i].answered = confirmed;
1359
+ break;
1360
+ }
1361
+ }
1362
+ addStructuredMessage('user', { content: confirmed ? '确认' : '取消' });
1363
+ if (typeof __deepspider_send__ === 'function') {
1364
+ __deepspider_send__(JSON.stringify({ __ds__: true, type: 'confirm-result', confirmed }));
1365
+ }
1366
+ };
1367
+ });
1368
+ }
1369
+
1344
1370
  function escapeHtml(str) {
1345
1371
  return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
1346
1372
  }
@@ -1443,7 +1469,6 @@ export function getAnalysisPanelScript() {
1443
1469
 
1444
1470
  // ========== 对话输入 ==========
1445
1471
  const chatInput = document.getElementById('deepspider-chat-input');
1446
- const analyzeBtn = document.getElementById('deepspider-btn-analyze');
1447
1472
  const sendMsgBtn = document.getElementById('deepspider-btn-send-msg');
1448
1473
 
1449
1474
  // ========== 已选元素管理 ==========
@@ -1514,16 +1539,27 @@ export function getAnalysisPanelScript() {
1514
1539
  function updateActionButtons() {
1515
1540
  const hasElements = deepspider.selectedElements.length > 0;
1516
1541
  const hasText = chatInput.value.trim().length > 0;
1517
-
1518
- analyzeBtn.disabled = !hasElements;
1542
+ // 快捷操作:有选中元素时显示
1543
+ const quickActions = document.getElementById('deepspider-quick-actions');
1544
+ if (hasElements) quickActions.classList.add('visible');
1545
+ else quickActions.classList.remove('visible');
1519
1546
  sendMsgBtn.disabled = !hasText && !hasElements;
1520
1547
  }
1521
1548
 
1522
1549
  // 监听输入框变化
1523
- chatInput.oninput = updateActionButtons;
1550
+ chatInput.oninput = () => {
1551
+ updateActionButtons();
1552
+ chatInput.style.height = 'auto';
1553
+ chatInput.style.height = Math.min(chatInput.scrollHeight, 110) + 'px';
1554
+ };
1524
1555
 
1525
1556
  // 绑定按钮事件
1526
- analyzeBtn.onclick = sendAnalysisWithElements;
1557
+ document.querySelectorAll('.deepspider-quick-btn').forEach(btn => {
1558
+ btn.onclick = () => {
1559
+ const action = btn.dataset.action;
1560
+ sendQuickAction(action);
1561
+ };
1562
+ });
1527
1563
  sendMsgBtn.onclick = sendChat;
1528
1564
  chatInput.onkeydown = (e) => {
1529
1565
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); }
@@ -1542,6 +1578,7 @@ export function getAnalysisPanelScript() {
1542
1578
  // 纯文字对话
1543
1579
  if (!text) return;
1544
1580
  chatInput.value = '';
1581
+ chatInput.style.height = 'auto';
1545
1582
  addMessage('user', text);
1546
1583
  if (typeof __deepspider_send__ === 'function') {
1547
1584
  __deepspider_send__(JSON.stringify({ __ds__: true, type: 'chat', text }));
@@ -1572,35 +1609,41 @@ export function getAnalysisPanelScript() {
1572
1609
  }
1573
1610
 
1574
1611
  chatInput.value = '';
1612
+ chatInput.style.height = 'auto';
1575
1613
  clearSelectedElements();
1576
1614
  }
1577
1615
 
1578
- // 发送完整分析(带已选元素)
1579
- function sendAnalysisWithElements() {
1616
+ // 发送快捷操作(带已选元素)
1617
+ function sendQuickAction(action) {
1580
1618
  const elements = deepspider.selectedElements;
1581
1619
  if (elements.length === 0) return;
1582
1620
 
1583
1621
  const text = chatInput.value.trim();
1584
- const elementsText = elements.map(el => el.text.slice(0, 50)).join(', ');
1585
- const displayMsg = (text ? text + ' - ' : '完整分析: ') + elementsText.slice(0, 80);
1622
+ const labels = {
1623
+ trace: '🔍 追踪数据来源',
1624
+ decrypt: '🔓 分析加密参数',
1625
+ full: '🚀 完整分析并生成爬虫',
1626
+ extract: '📋 提取页面结构',
1627
+ };
1628
+ const elementsText = elements.map(el => el.text.slice(0, 30)).join(', ');
1629
+ const displayMsg = labels[action] + ': ' + elementsText.slice(0, 60);
1586
1630
 
1587
1631
  panel.classList.add('visible');
1588
1632
  addMessage('user', displayMsg);
1589
- addMessage('system', '分析中...');
1590
1633
 
1591
1634
  if (typeof __deepspider_send__ === 'function') {
1592
1635
  __deepspider_send__(JSON.stringify({
1593
1636
  __ds__: true,
1594
1637
  type: 'analysis',
1595
- analysisType: 'full',
1638
+ action: action,
1596
1639
  elements: elements,
1597
1640
  text: text,
1598
1641
  url: location.href
1599
1642
  }));
1600
1643
  }
1601
1644
 
1602
- // 清空已选元素和输入框
1603
1645
  chatInput.value = '';
1646
+ chatInput.style.height = 'auto';
1604
1647
  clearSelectedElements();
1605
1648
  }
1606
1649
 
@@ -1616,48 +1659,12 @@ export function getAnalysisPanelScript() {
1616
1659
  }
1617
1660
  });
1618
1661
 
1619
- // ========== 追加到最后一条消息(流式输出优化) ==========
1620
- function appendToLastMessage(role, text) {
1621
- const msgs = deepspider.chatMessages;
1622
- // 查找最后一条同角色的未完成消息
1623
- for (let i = msgs.length - 1; i >= 0; i--) {
1624
- if (msgs[i].role === role && !msgs[i].complete) {
1625
- msgs[i].content += text;
1626
- // 流式输出时直接操作 DOM,不重新渲染
1627
- const msgElements = messagesEl.querySelectorAll('.deepspider-msg-' + role);
1628
- const lastMsgEl = msgElements[msgElements.length - 1];
1629
- if (lastMsgEl) {
1630
- lastMsgEl.textContent = msgs[i].content;
1631
- messagesEl.scrollTop = messagesEl.scrollHeight;
1632
- }
1633
- return true;
1634
- }
1635
- }
1636
- // 没找到则创建新的未完成消息
1637
- addMessage(role, text, false);
1638
- return true;
1639
- }
1640
-
1641
- // 完成流式输出后调用,标记完成并渲染 Markdown
1642
- function finalizeMessage(role) {
1643
- const msgs = deepspider.chatMessages;
1644
- // 找到最后一条未完成的消息,标记为完成
1645
- for (let i = msgs.length - 1; i >= 0; i--) {
1646
- if (msgs[i].role === role && !msgs[i].complete) {
1647
- msgs[i].complete = true;
1648
- break;
1649
- }
1650
- }
1651
- saveMessages();
1652
- renderMessages();
1653
- }
1654
-
1655
1662
  // ========== 更新最后一条消息(替换内容) ==========
1656
1663
  function updateLastMessage(role, content) {
1657
1664
  const msgs = deepspider.chatMessages;
1658
1665
  for (let i = msgs.length - 1; i >= 0; i--) {
1659
1666
  if (msgs[i].role === role) {
1660
- msgs[i].content = content;
1667
+ msgs[i].data = { content };
1661
1668
  saveMessages();
1662
1669
  renderMessages();
1663
1670
  return true;
@@ -1675,15 +1682,18 @@ export function getAnalysisPanelScript() {
1675
1682
  const reportContentEl = document.getElementById('deepspider-report-content');
1676
1683
  const reportCloseBtn = document.getElementById('deepspider-report-close');
1677
1684
 
1678
- // 关闭报告模态框
1679
- reportCloseBtn.onclick = () => {
1685
+ // 关闭报告模态框(统一入口)
1686
+ function closeReportModal() {
1680
1687
  reportModal.classList.remove('visible');
1681
- };
1688
+ reportBtn.classList.add('viewed');
1689
+ }
1690
+
1691
+ reportCloseBtn.onclick = closeReportModal;
1682
1692
 
1683
1693
  // ESC 键关闭模态框
1684
1694
  document.addEventListener('keydown', (e) => {
1685
1695
  if (e.key === 'Escape' && reportModal.classList.contains('visible')) {
1686
- reportModal.classList.remove('visible');
1696
+ closeReportModal();
1687
1697
  }
1688
1698
  });
1689
1699
 
@@ -1804,11 +1814,10 @@ export function getAnalysisPanelScript() {
1804
1814
  minimizeBtn.title = '最小化';
1805
1815
  }
1806
1816
 
1807
- deepspider.showPanel = () => panel.classList.add('visible');
1808
- deepspider.hidePanel = () => panel.classList.remove('visible');
1817
+ deepspider.showPanel = () => { panel.classList.add('visible'); reopenBtn.classList.remove('visible'); };
1818
+ deepspider.hidePanel = () => { panel.classList.remove('visible'); reopenBtn.classList.add('visible'); };
1809
1819
  deepspider.addMessage = addMessage;
1810
- deepspider.appendToLastMessage = appendToLastMessage;
1811
- deepspider.finalizeMessage = finalizeMessage;
1820
+ deepspider.addStructuredMessage = addStructuredMessage;
1812
1821
  deepspider.updateLastMessage = updateLastMessage;
1813
1822
  deepspider.renderMessages = renderMessages;
1814
1823
  deepspider.clearMessages = () => { deepspider.chatMessages = []; saveMessages(); renderMessages(); };
@@ -1818,8 +1827,6 @@ export function getAnalysisPanelScript() {
1818
1827
  deepspider.setBusy = setBusy;
1819
1828
  deepspider.minimize = minimize;
1820
1829
  deepspider.maximize = maximize;
1821
- deepspider.getStages = () => deepspider.stages;
1822
- deepspider.clearStages = clearAll;
1823
1830
  deepspider.getSelectedElements = () => deepspider.selectedElements;
1824
1831
  deepspider.clearSelectedElements = clearSelectedElements;
1825
1832
 
@@ -1827,8 +1834,6 @@ export function getAnalysisPanelScript() {
1827
1834
  panel.classList.add('visible');
1828
1835
  // 渲染恢复的消息
1829
1836
  renderMessages();
1830
- // 恢复阶段面板
1831
- updateStagesPanel();
1832
1837
  // 恢复已选元素标签
1833
1838
  renderSelectedTags();
1834
1839
  updateActionButtons();