zerochat 7.3.0__py3-none-any.whl

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 (130) hide show
  1. zerochat-7.3.0.dist-info/METADATA +70 -0
  2. zerochat-7.3.0.dist-info/RECORD +130 -0
  3. zerochat-7.3.0.dist-info/WHEEL +5 -0
  4. zerochat-7.3.0.dist-info/entry_points.txt +2 -0
  5. zerochat-7.3.0.dist-info/licenses/LICENSE +34 -0
  6. zerochat-7.3.0.dist-info/top_level.txt +2 -0
  7. zerochat.py +2004 -0
  8. zerochat_runtime/__init__.py +1 -0
  9. zerochat_runtime/assets/css/base.css +199 -0
  10. zerochat_runtime/assets/css/components/composer.css +1258 -0
  11. zerochat_runtime/assets/css/components/debug.css +777 -0
  12. zerochat_runtime/assets/css/components/header.css +97 -0
  13. zerochat_runtime/assets/css/components/icons.css +39 -0
  14. zerochat_runtime/assets/css/components/markdown.css +685 -0
  15. zerochat_runtime/assets/css/components/messages.css +473 -0
  16. zerochat_runtime/assets/css/components/modals.css +1319 -0
  17. zerochat_runtime/assets/css/components/sidebar.css +622 -0
  18. zerochat_runtime/assets/css/components/tools.css +1223 -0
  19. zerochat_runtime/assets/css/layout.css +171 -0
  20. zerochat_runtime/assets/css/print.css +120 -0
  21. zerochat_runtime/assets/css/styles.css +18 -0
  22. zerochat_runtime/assets/css/theme-overrides.css +555 -0
  23. zerochat_runtime/assets/css/tokens.css +165 -0
  24. zerochat_runtime/assets/help/architecture.html +247 -0
  25. zerochat_runtime/assets/help/composer.html +134 -0
  26. zerochat_runtime/assets/help/conversations.html +105 -0
  27. zerochat_runtime/assets/help/debug.html +108 -0
  28. zerochat_runtime/assets/help/en/architecture.html +247 -0
  29. zerochat_runtime/assets/help/en/composer.html +134 -0
  30. zerochat_runtime/assets/help/en/conversations.html +104 -0
  31. zerochat_runtime/assets/help/en/debug.html +105 -0
  32. zerochat_runtime/assets/help/en/gemini-free.html +39 -0
  33. zerochat_runtime/assets/help/en/index.html +191 -0
  34. zerochat_runtime/assets/help/en/learning.html +71 -0
  35. zerochat_runtime/assets/help/en/mcp.html +143 -0
  36. zerochat_runtime/assets/help/en/openrouter-free.html +39 -0
  37. zerochat_runtime/assets/help/en/profiles.html +173 -0
  38. zerochat_runtime/assets/help/en/rag.html +130 -0
  39. zerochat_runtime/assets/help/en/reasoning-telemetry.html +108 -0
  40. zerochat_runtime/assets/help/en/tools-agent.html +133 -0
  41. zerochat_runtime/assets/help/en/webllm.html +355 -0
  42. zerochat_runtime/assets/help/gemini-free.html +39 -0
  43. zerochat_runtime/assets/help/help.css +604 -0
  44. zerochat_runtime/assets/help/help.js +51 -0
  45. zerochat_runtime/assets/help/index.html +194 -0
  46. zerochat_runtime/assets/help/learning.html +71 -0
  47. zerochat_runtime/assets/help/mcp.html +143 -0
  48. zerochat_runtime/assets/help/openrouter-free.html +39 -0
  49. zerochat_runtime/assets/help/profiles.html +174 -0
  50. zerochat_runtime/assets/help/rag.html +130 -0
  51. zerochat_runtime/assets/help/reasoning-telemetry.html +111 -0
  52. zerochat_runtime/assets/help/tools-agent.html +133 -0
  53. zerochat_runtime/assets/help/webllm.html +359 -0
  54. zerochat_runtime/assets/js/agent-core.js +1553 -0
  55. zerochat_runtime/assets/js/api.js +844 -0
  56. zerochat_runtime/assets/js/app.js +2558 -0
  57. zerochat_runtime/assets/js/attachments.js +220 -0
  58. zerochat_runtime/assets/js/charts.js +338 -0
  59. zerochat_runtime/assets/js/chat-engine.js +566 -0
  60. zerochat_runtime/assets/js/config-store.js +207 -0
  61. zerochat_runtime/assets/js/context-manager.js +584 -0
  62. zerochat_runtime/assets/js/conversation-service.js +484 -0
  63. zerochat_runtime/assets/js/cookies.js +688 -0
  64. zerochat_runtime/assets/js/data-reset-service.js +136 -0
  65. zerochat_runtime/assets/js/debug.js +508 -0
  66. zerochat_runtime/assets/js/defaults.js +16 -0
  67. zerochat_runtime/assets/js/export.js +179 -0
  68. zerochat_runtime/assets/js/file-parser.js +2088 -0
  69. zerochat_runtime/assets/js/generation-controller.js +417 -0
  70. zerochat_runtime/assets/js/i18n.js +1483 -0
  71. zerochat_runtime/assets/js/icons.js +156 -0
  72. zerochat_runtime/assets/js/ingestionEngine.js +436 -0
  73. zerochat_runtime/assets/js/markdown.js +588 -0
  74. zerochat_runtime/assets/js/mcp.js +1271 -0
  75. zerochat_runtime/assets/js/message-turns.js +62 -0
  76. zerochat_runtime/assets/js/profile-backup.js +118 -0
  77. zerochat_runtime/assets/js/profile-export-bundle.js +481 -0
  78. zerochat_runtime/assets/js/profile-repository.js +213 -0
  79. zerochat_runtime/assets/js/providers-webllm.js +486 -0
  80. zerochat_runtime/assets/js/providers.js +1560 -0
  81. zerochat_runtime/assets/js/rag-index.js +295 -0
  82. zerochat_runtime/assets/js/rag-service.js +530 -0
  83. zerochat_runtime/assets/js/rag-ui.js +997 -0
  84. zerochat_runtime/assets/js/ragStorage.js +676 -0
  85. zerochat_runtime/assets/js/sandbox.js +449 -0
  86. zerochat_runtime/assets/js/state.js +704 -0
  87. zerochat_runtime/assets/js/storage-db.js +144 -0
  88. zerochat_runtime/assets/js/tool-cards.js +330 -0
  89. zerochat_runtime/assets/js/tool-security.js +653 -0
  90. zerochat_runtime/assets/js/tools/README.md +50 -0
  91. zerochat_runtime/assets/js/tools/builtin/agent-checkpoint.tool.js +212 -0
  92. zerochat_runtime/assets/js/tools/builtin/download-pdf.tool.js +111 -0
  93. zerochat_runtime/assets/js/tools/builtin/execute-javascript.tool.js +219 -0
  94. zerochat_runtime/assets/js/tools/builtin/fetch-web-page.tool.js +112 -0
  95. zerochat_runtime/assets/js/tools/builtin/list-documents.tool.js +117 -0
  96. zerochat_runtime/assets/js/tools/builtin/read-knowledge-chunk.tool.js +129 -0
  97. zerochat_runtime/assets/js/tools/builtin/read-knowledge-image.tool.js +95 -0
  98. zerochat_runtime/assets/js/tools/builtin/render-chart.tool.js +90 -0
  99. zerochat_runtime/assets/js/tools/builtin/search-knowledge-base.tool.js +124 -0
  100. zerochat_runtime/assets/js/tools/builtin/search-web.tool.js +125 -0
  101. zerochat_runtime/assets/js/tools/tool-manifest.js +53 -0
  102. zerochat_runtime/assets/js/tools/tool-runtime.js +77 -0
  103. zerochat_runtime/assets/js/ui-composer.js +319 -0
  104. zerochat_runtime/assets/js/ui-conversation.js +655 -0
  105. zerochat_runtime/assets/js/ui-dialogs.js +132 -0
  106. zerochat_runtime/assets/js/ui-generation-status.js +119 -0
  107. zerochat_runtime/assets/js/ui-inspector.js +804 -0
  108. zerochat_runtime/assets/js/ui-mcp.js +604 -0
  109. zerochat_runtime/assets/js/ui-profiles.js +824 -0
  110. zerochat_runtime/assets/js/ui-reasoning.js +146 -0
  111. zerochat_runtime/assets/js/ui-settings.js +825 -0
  112. zerochat_runtime/assets/js/ui-shell.js +215 -0
  113. zerochat_runtime/assets/js/ui-sidebar.js +429 -0
  114. zerochat_runtime/assets/js/ui-telemetry.js +404 -0
  115. zerochat_runtime/assets/js/ui-transfer.js +262 -0
  116. zerochat_runtime/assets/js/utils.js +216 -0
  117. zerochat_runtime/assets/js/vendor/orama.browser.js +11 -0
  118. zerochat_runtime/assets/js/web-browser.js +569 -0
  119. zerochat_runtime/assets/js/web-search.js +519 -0
  120. zerochat_runtime/assets/manifest.webmanifest +20 -0
  121. zerochat_runtime/assets/services/dummy_mcp/dummy_mcp_server.py +47 -0
  122. zerochat_runtime/assets/services/dummy_mcp/service.json +22 -0
  123. zerochat_runtime/assets/services/lsp/installer.json +9 -0
  124. zerochat_runtime/assets/services/lsp/service.json +22 -0
  125. zerochat_runtime/assets/services/memory/installer.json +9 -0
  126. zerochat_runtime/assets/services/memory/service.json +24 -0
  127. zerochat_runtime/assets/services/playwright/installer.json +10 -0
  128. zerochat_runtime/assets/services/playwright/service.json +39 -0
  129. zerochat_runtime/assets/sw.js +164 -0
  130. zerochat_runtime/assets/zerochat.html +671 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Catálogo central de Iconos Vectoriales SVG para ZeroChat.
3
+ * ZeroChat - js/icons.js
4
+ *
5
+ * Provee glifos vectoriales monocromáticos consistentes (estilo Lucide/Feather)
6
+ * con `stroke="currentColor"`, eliminando emojis Unicode para una apariencia
7
+ * profesional, limpia y uniforme en cualquier plataforma y tema.
8
+ */
9
+ (function (root, factory) {
10
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
11
+ module.exports = factory();
12
+ } else {
13
+ root.ChatIcons = factory();
14
+ }
15
+ })(typeof self !== 'undefined' ? self : this, function () {
16
+ 'use strict';
17
+
18
+ const GLYPHS = {
19
+ // Acciones y Navegación
20
+ 'panel-left': '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line>',
21
+ 'panel-right': '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="15" y1="3" x2="15" y2="21"></line>',
22
+ 'menu': '<line x1="4" y1="6" x2="20" y2="6"></line><line x1="4" y1="12" x2="20" y2="12"></line><line x1="4" y1="18" x2="20" y2="18"></line>',
23
+ 'arrow-left': '<line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline>',
24
+ 'shield': '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>',
25
+ 'plus': '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>',
26
+ 'square-pen': '<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"></path>',
27
+ 'search': '<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>',
28
+ 'trash': '<polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>',
29
+ 'edit': '<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>',
30
+ 'close': '<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>',
31
+ 'copy': '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>',
32
+ 'git-branch': '<line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path>',
33
+ 'check': '<polyline points="20 6 9 17 4 12"></polyline>',
34
+ 'check-circle': '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>',
35
+ 'alert-circle': '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>',
36
+ 'alert-triangle': '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',
37
+ 'x-circle': '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',
38
+ 'refresh': '<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>',
39
+ 'download': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line>',
40
+ 'upload': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line>',
41
+ 'share': '<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line>',
42
+ 'external-link': '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line>',
43
+ 'link': '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>',
44
+ 'chevron-down': '<polyline points="6 9 12 15 18 9"></polyline>',
45
+ 'chevron-up': '<polyline points="18 15 12 9 6 15"></polyline>',
46
+ 'chevron-right': '<polyline points="9 18 15 12 9 6"></polyline>',
47
+ 'maximize': '<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>',
48
+ 'minimize': '<path d="M4 14h6m0 0v6m0-6L3 21m17-7h-6m0 0V8m0 6l7 7M14 4h6m-6 0v6m0-6L21 3M4 10h6m0 0V4m0 6L3 3"></path>',
49
+ 'send': '<line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>',
50
+ 'square': '<rect x="6" y="6" width="12" height="12" rx="2"></rect>',
51
+ 'stop': '<rect x="6" y="6" width="12" height="12" rx="2"></rect>',
52
+ 'paperclip': '<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>',
53
+ 'user-plus': '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><line x1="19" y1="8" x2="19" y2="14"></line><line x1="16" y1="11" x2="22" y2="11"></line>',
54
+ 'save': '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline>',
55
+ 'help-circle': '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line>',
56
+ 'arrow-down': '<line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline>',
57
+ 'shuffle': '<polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line>',
58
+
59
+ // Inteligencia Artificial, Agentes y Razonamiento
60
+ 'brain': '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-2.04zm5 0a2.5 2.5 0 0 0-2.5 2.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-2.04z"></path>',
61
+ 'sparkles': '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"></path><path d="M19 17v4"></path><path d="M21 19h-4"></path>',
62
+ 'bookmark': '<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"></path>',
63
+ 'milestone': '<path d="M18 6H5a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h13l4-3.5L18 6Z"></path><line x1="12" y1="13" x2="12" y2="21"></line>',
64
+ 'bot': '<rect width="18" height="12" x="3" y="10" rx="2"></rect><circle cx="12" cy="4" r="2"></circle><line x1="12" y1="6" x2="12" y2="10"></line><circle cx="8" cy="15" r="1"></circle><circle cx="16" cy="15" r="1"></circle>',
65
+ 'zap': '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>',
66
+ 'activity': '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>',
67
+ 'gauge': '<path d="m12 14 4-4"></path><path d="M3.34 19a10 10 0 1 1 17.32 0"></path>',
68
+ 'terminal': '<polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line>',
69
+ 'code': '<polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>',
70
+ 'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>',
71
+ 'image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><circle cx="9" cy="9" r="2"></circle><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path>',
72
+ 'book-open': '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>',
73
+ 'flame': '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path>',
74
+ 'circle': '<circle cx="12" cy="12" r="9"></circle>',
75
+ 'circle-dot': '<circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle>',
76
+ 'folder': '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>',
77
+ 'globe': '<circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>',
78
+ 'compass': '<circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>',
79
+ 'database': '<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>',
80
+ 'layers': '<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline>',
81
+ 'bar-chart': '<line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line>',
82
+ 'radio': '<path d="M4.93 19.07a10 10 0 0 1 0-14.14"></path><path d="M7.76 16.24a6 6 0 0 1 0-8.48"></path><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.48"></path><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>',
83
+
84
+ // Configuración, Sistema y Tema
85
+ 'settings': '<circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>',
86
+ 'sliders': '<line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line>',
87
+ 'palette': '<circle cx="13.5" cy="6.5" r=".5"></circle><circle cx="17.5" cy="10.5" r=".5"></circle><circle cx="8.5" cy="7.5" r=".5"></circle><circle cx="6.5" cy="12.5" r=".5"></circle><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"></path>',
88
+ 'sun': '<circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>',
89
+ 'moon': '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>',
90
+ 'eye': '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>',
91
+ 'eye-off': '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>',
92
+ 'clock': '<circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>',
93
+ 'hard-drive': '<line x1="22" y1="12" x2="2" y2="12"></line><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path><line x1="6" y1="16" x2="6.01" y2="16"></line><line x1="10" y1="16" x2="10.01" y2="16"></line>',
94
+ 'printer': '<polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect>',
95
+ 'package': '<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line>',
96
+ 'server': '<rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line>',
97
+ 'plug': '<path d="M12 22v-5"></path><path d="M9 8V2"></path><path d="M15 8V2"></path><path d="M18 8v5a6 6 0 0 1-12 0V8z"></path>',
98
+ 'hash': '<line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line>',
99
+ 'lightbulb': '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path>',
100
+ 'spinner': '<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>'
101
+ };
102
+
103
+ /**
104
+ * Genera el marcado SVG para un icono dado.
105
+ * @param {string} name - Nombre identificador del icono.
106
+ * @param {Object} [options] - Opciones de renderizado.
107
+ * @param {number} [options.size=16] - Ancho y alto en píxeles.
108
+ * @param {string} [options.className=''] - Clases CSS adicionales.
109
+ * @param {number} [options.strokeWidth=2] - Grosor del trazo.
110
+ * @param {string} [options.title=''] - Tooltip o accesibilidad.
111
+ * @returns {string} Código HTML del elemento <svg>.
112
+ */
113
+ function get(name, options = {}) {
114
+ const key = name === 'new-chat' ? 'square-pen' : name;
115
+ const glyph = GLYPHS[key];
116
+ if (!glyph) {
117
+ if (typeof console !== 'undefined' && console.warn) {
118
+ console.warn(`[ChatIcons] Icono no encontrado en catálogo: "${name}"`);
119
+ }
120
+ // Fallback sutil a círculo si el glifo no existe
121
+ return `<svg class="ui-icon ${options.className || ''}" width="${options.size || 16}" height="${options.size || 16}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${options.strokeWidth || 2}" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle></svg>`;
122
+ }
123
+
124
+ const size = options.size || 16;
125
+ const strokeWidth = options.strokeWidth || 2;
126
+ const cls = ['ui-icon', options.className || ''].filter(Boolean).join(' ');
127
+ const safeTitle = options.title ? String(options.title).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') : '';
128
+ const titleAttr = safeTitle ? `<title>${safeTitle}</title>` : '';
129
+
130
+ return `<svg class="${cls}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${titleAttr}${glyph}</svg>`;
131
+ }
132
+
133
+ /**
134
+ * Comprueba si un icono existe en el catálogo.
135
+ * @param {string} name
136
+ * @returns {boolean}
137
+ */
138
+ function has(name) {
139
+ return Object.prototype.hasOwnProperty.call(GLYPHS, name);
140
+ }
141
+
142
+ /**
143
+ * Lista todos los nombres de iconos disponibles.
144
+ * @returns {string[]}
145
+ */
146
+ function list() {
147
+ return Object.keys(GLYPHS);
148
+ }
149
+
150
+ return {
151
+ get,
152
+ has,
153
+ list,
154
+ GLYPHS
155
+ };
156
+ });
@@ -0,0 +1,436 @@
1
+ /** Deterministic, local document ingestion for the ZeroChat knowledge base. */
2
+ (function (root, factory) {
3
+ if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = factory();
4
+ else root.ChatIngestionEngine = factory();
5
+ })(typeof self !== 'undefined' ? self : this, function () {
6
+ 'use strict';
7
+
8
+ function getRagStorage() {
9
+ if (typeof window !== 'undefined') return window.ChatRagStorage;
10
+ try { return require('./ragStorage.js'); } catch (_) { return null; }
11
+ }
12
+ function getRagIndex() {
13
+ if (typeof window !== 'undefined') return window.ChatRagIndex;
14
+ try { return require('./rag-index.js'); } catch (_) { return null; }
15
+ }
16
+ function getFileParser() {
17
+ if (typeof window !== 'undefined') return window.ChatFileParser;
18
+ try { return require('./file-parser.js'); } catch (_) { return null; }
19
+ }
20
+
21
+ function normalizeExtractedText(rawText) {
22
+ if (!rawText) return '';
23
+ let text = typeof rawText === 'string' ? rawText : String(rawText);
24
+ if (text.normalize) text = text.normalize('NFKC');
25
+ return text.replace(/\r\n?/g, '\n').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
26
+ .replace(/[ \t]+\n/g, '\n').replace(/\n{4,}/g, '\n\n\n').trim();
27
+ }
28
+
29
+ async function readPlainText(file) {
30
+ if (!file) return '';
31
+ if (typeof file === 'string') return file;
32
+ if (typeof file.content === 'string') return file.content;
33
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) return file.toString('utf8');
34
+ if (typeof file.text === 'function') return file.text();
35
+ if (typeof FileReader !== 'undefined') {
36
+ return new Promise((resolve, reject) => {
37
+ const reader = new FileReader();
38
+ reader.onload = () => resolve(String(reader.result || ''));
39
+ reader.onerror = () => reject(reader.error || new Error('No se pudo leer el archivo.'));
40
+ reader.readAsText(file, 'utf-8');
41
+ });
42
+ }
43
+ throw new Error('No se pudo leer el archivo de texto.');
44
+ }
45
+
46
+ async function extractTextFromPlainText(file) {
47
+ return normalizeExtractedText(await readPlainText(file));
48
+ }
49
+
50
+ async function toArrayBuffer(file) {
51
+ if (file instanceof ArrayBuffer) return file;
52
+ if (file instanceof Uint8Array) return file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
53
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) return file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
54
+ if (file && typeof file.arrayBuffer === 'function') return file.arrayBuffer();
55
+ if (typeof FileReader !== 'undefined') {
56
+ return new Promise((resolve, reject) => {
57
+ const reader = new FileReader();
58
+ reader.onload = () => resolve(reader.result);
59
+ reader.onerror = () => reject(reader.error || new Error('No se pudo leer el PDF.'));
60
+ reader.readAsArrayBuffer(file);
61
+ });
62
+ }
63
+ throw new Error('No se pudo obtener el contenido binario del archivo.');
64
+ }
65
+
66
+ const MAX_DECOMPRESSED_TEXT_BYTES = 50 * 1024 * 1024;
67
+
68
+ function archiveFormatFromName(fileName) {
69
+ const name = String(fileName || '').toLowerCase();
70
+ if (/\.(?:tar\.gz|tgz|tar\.bz2|tbz2|tar\.xz|txz|tar\.zst|tzst)$/.test(name)) return 'tar';
71
+ if (/\.zip$/.test(name)) return 'zip';
72
+ if (/\.(?:gz|gzip)$/.test(name)) return 'gzip';
73
+ if (/\.(?:bz2|xz|zst)$/.test(name)) return 'compressed';
74
+ return '';
75
+ }
76
+
77
+ function archiveFormatFromBytes(bytes) {
78
+ if (!bytes || bytes.length < 2) return '';
79
+ if (bytes[0] === 0x1F && bytes[1] === 0x8B) return 'gzip';
80
+ if (bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4B && [0x03, 0x05, 0x07].includes(bytes[2]) && [0x04, 0x06, 0x08].includes(bytes[3])) return 'zip';
81
+ if (bytes[0] === 0x42 && bytes[1] === 0x5A && bytes[2] === 0x68) return 'compressed';
82
+ if (bytes.length >= 6 && bytes[0] === 0xFD && bytes[1] === 0x37 && bytes[2] === 0x7A && bytes[3] === 0x58 && bytes[4] === 0x5A && bytes[5] === 0x00) return 'compressed';
83
+ if (bytes.length >= 4 && bytes[0] === 0x28 && bytes[1] === 0xB5 && bytes[2] === 0x2F && bytes[3] === 0xFD) return 'compressed';
84
+ return '';
85
+ }
86
+
87
+ async function getArchiveFormat(file) {
88
+ const byName = archiveFormatFromName(file?.name);
89
+ if (byName) return byName;
90
+ try {
91
+ const header = file?.slice ? file.slice(0, 16) : file;
92
+ const bytes = new Uint8Array(await toArrayBuffer(header));
93
+ return archiveFormatFromBytes(bytes);
94
+ } catch (_) {
95
+ return '';
96
+ }
97
+ }
98
+
99
+ async function readTextStream(stream, maxBytes = MAX_DECOMPRESSED_TEXT_BYTES) {
100
+ const reader = stream.getReader();
101
+ const decoder = new TextDecoder('utf-8');
102
+ let total = 0;
103
+ let text = '';
104
+ while (true) {
105
+ const { done, value } = await reader.read();
106
+ if (done) break;
107
+ total += value.byteLength;
108
+ if (total > maxBytes) {
109
+ await reader.cancel();
110
+ throw new Error(`El contenido descomprimido supera el límite de ${Math.round(maxBytes / 1024 / 1024)} MB.`);
111
+ }
112
+ text += decoder.decode(value, { stream: true });
113
+ }
114
+ return text + decoder.decode();
115
+ }
116
+
117
+ async function readGzipText(file, options = {}) {
118
+ if (typeof DecompressionStream === 'undefined' || typeof Blob === 'undefined') {
119
+ throw new Error('Este navegador no puede descomprimir archivos gzip.');
120
+ }
121
+ const bytes = await toArrayBuffer(file);
122
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'));
123
+ return readTextStream(stream, options.maxDecompressedBytes || MAX_DECOMPRESSED_TEXT_BYTES);
124
+ }
125
+
126
+ function unsupportedArchiveMessage(format) {
127
+ if (format === 'tar') return 'El archivo es un contenedor TAR comprimido. Extrae los logs o usa archivos .gz individuales.';
128
+ if (format === 'zip') return 'El archivo es un ZIP. Extrae los logs o usa archivos .gz individuales.';
129
+ return 'El archivo está comprimido en un formato no compatible. Extrae su contenido antes de indexarlo.';
130
+ }
131
+
132
+ async function extractTextFromPDF(file) {
133
+ const bytes = await toArrayBuffer(file);
134
+ const parser = getFileParser();
135
+ if (!parser?.extractTextFromPdf) throw new Error('El extractor de PDF no está disponible.');
136
+ return normalizeExtractedText(await parser.extractTextFromPdf(bytes));
137
+ }
138
+
139
+ async function extractDocumentContent(file, fileType) {
140
+ const archiveFormat = await getArchiveFormat(file);
141
+ if (archiveFormat && archiveFormat !== 'gzip') throw new Error(unsupportedArchiveMessage(archiveFormat));
142
+ if (archiveFormat === 'gzip') {
143
+ let rawText;
144
+ try {
145
+ rawText = await readGzipText(file);
146
+ } catch (error) {
147
+ if (String(error?.message || error).includes('navegador no puede')) throw error;
148
+ throw new Error('No se pudo descomprimir el archivo gzip. El archivo puede estar dañado.');
149
+ }
150
+ if (!isLikelyText(rawText)) throw new Error('El contenido del archivo gzip no parece contener texto legible.');
151
+ return { text: normalizeExtractedText(rawText), images: [] };
152
+ }
153
+ if (fileType === 'pdf') {
154
+ const bytes = await toArrayBuffer(file);
155
+ const parser = getFileParser();
156
+ if (!parser) throw new Error('El extractor de PDF no está disponible.');
157
+ if (typeof parser.parsePdfDocument === 'function') {
158
+ const parsed = await parser.parsePdfDocument(bytes);
159
+ return {
160
+ text: normalizeExtractedText(parsed.text),
161
+ images: parsed.images || []
162
+ };
163
+ }
164
+ const rawText = await parser.extractTextFromPdf(bytes);
165
+ return { text: normalizeExtractedText(rawText), images: [] };
166
+ }
167
+
168
+ if (fileType === 'md') {
169
+ let rawText = await extractTextFromPlainText(file);
170
+ const images = [];
171
+ let imgCounter = 1;
172
+ rawText = rawText.replace(/!\[([^\]]*)\]\((data:image\/(?:png|jpeg|jpg|webp|gif|svg\+xml);base64,[A-Za-z0-9+/=\s]+)\)/gi, (match, alt, dataUrl) => {
173
+ const imgId = `img_${imgCounter++}`;
174
+ const cleanDataUrl = dataUrl.replace(/\s+/g, '');
175
+ const mimeMatch = cleanDataUrl.match(/^data:(image\/[a-zA-Z+]+);base64,/);
176
+ const mimeType = mimeMatch ? mimeMatch[1] : 'image/png';
177
+ const label = (alt && alt.trim()) || `Imagen ${imgCounter - 1}`;
178
+ images.push({
179
+ id: imgId,
180
+ page: 1,
181
+ width: 0,
182
+ height: 0,
183
+ mimeType,
184
+ dataUrl: cleanDataUrl,
185
+ label
186
+ });
187
+ return `![${label}](rag-image://__DOC_ID__:${imgId})`;
188
+ });
189
+ return { text: normalizeExtractedText(rawText), images };
190
+ }
191
+
192
+ const rawText = await readPlainText(file);
193
+ if (!isLikelyText(rawText)) throw new Error('El archivo no parece contener texto legible.');
194
+ return { text: normalizeExtractedText(rawText), images: [] };
195
+ }
196
+
197
+ function detectSectionHeading(line) {
198
+ const value = String(line || '').trim();
199
+ if (!value || value.length > 140) return '';
200
+ const markdown = value.match(/^#{1,6}\s+(.+)$/);
201
+ if (markdown) return markdown[1].trim();
202
+ const numbered = value.match(/^(?:\d+(?:\.\d+)*|[IVXLCDM]+)[.)]?\s+(.+)$/i);
203
+ if (numbered) return numbered[1].trim();
204
+ if (/^(?:cap[ií]tulo|chapter|secci[oó]n|section|anexo|appendix)\s+[\w.-]+/i.test(value)) return value;
205
+ if (value.length <= 80 && /^[A-ZÁÉÍÓÚÜÑ0-9][^.!?]*$/.test(value) && value.split(/\s+/).length <= 12) return value;
206
+ return '';
207
+ }
208
+
209
+ function findPageRange(content) {
210
+ const pages = Array.from(String(content).matchAll(/---\s*P[aá]gina\s+(\d+)\s*---/gi), match => Number(match[1]));
211
+ return pages.length ? { pageStart: Math.min(...pages), pageEnd: Math.max(...pages) } : { pageStart: null, pageEnd: null };
212
+ }
213
+
214
+ function findSafeCut(text, maxChars) {
215
+ if (text.length <= maxChars) return text.length;
216
+ const floor = Math.floor(maxChars * 0.65);
217
+ for (const separator of ['\n\n', '\n', '. ', '; ', ', ', ' ']) {
218
+ const index = text.lastIndexOf(separator, maxChars);
219
+ if (index >= floor) return index + separator.length;
220
+ }
221
+ return maxChars;
222
+ }
223
+
224
+ function tailOverlap(text, overlapChars) {
225
+ if (!overlapChars || text.length <= overlapChars) return '';
226
+ const tail = text.slice(-overlapChars);
227
+ const boundary = tail.search(/(?:\n\n|[.!?]\s)/);
228
+ return (boundary >= 0 ? tail.slice(boundary).trimStart() : tail).trim();
229
+ }
230
+
231
+ function partitionTextIntoChunks(rawText, options = {}) {
232
+ const text = normalizeExtractedText(rawText);
233
+ if (!text) return [];
234
+ const maxChars = Math.max(1000, Number(options.maxChars) || 6000);
235
+ const requestedOverlap = options.overlapChars === undefined ? 400 : Number(options.overlapChars);
236
+ const overlapChars = Math.max(0, Math.min(Math.floor(maxChars * 0.2), requestedOverlap || 0));
237
+ const chunks = [];
238
+ let remaining = text;
239
+ let overlap = '';
240
+ while (remaining) {
241
+ const cut = findSafeCut(remaining, maxChars);
242
+ const body = remaining.slice(0, cut).trim();
243
+ const content = [overlap, body].filter(Boolean).join('\n\n');
244
+ const firstHeading = content.split('\n').map(detectSectionHeading).find(Boolean);
245
+ chunks.push({ order: chunks.length, title: firstHeading || `Fragmento ${chunks.length + 1}`, content, ...findPageRange(content) });
246
+ remaining = remaining.slice(cut).trimStart();
247
+ overlap = remaining ? tailOverlap(body, overlapChars) : '';
248
+ }
249
+ return chunks;
250
+ }
251
+
252
+ function detectFileType(file) {
253
+ const name = String(file?.name || '').toLowerCase();
254
+ if (name.endsWith('.pdf') || file?.type === 'application/pdf') return 'pdf';
255
+ if (name.endsWith('.md') || name.endsWith('.markdown') || file?.type === 'text/markdown') return 'md';
256
+ if (/^(?:image|audio|video)\//i.test(String(file?.type || ''))) return null;
257
+ return 'txt';
258
+ }
259
+
260
+ function isLikelyText(value) {
261
+ const text = String(value || '');
262
+ if (!text || text.includes('\0')) return false;
263
+ const controls = (text.match(/[\x01-\x08\x0B\x0C\x0E-\x1F\x7F]/g) || []).length;
264
+ return controls <= Math.max(1, Math.floor(text.length * 0.01));
265
+ }
266
+
267
+ const MAX_DOCUMENT_SIZE = 50 * 1024 * 1024; // 50 MB límite por documento
268
+
269
+ async function processDocumentQueue(files, branchId, onProgress, options = {}) {
270
+ const queue = Array.isArray(files) ? files : Array.from(files || []);
271
+ const storage = getRagStorage();
272
+ const index = getRagIndex();
273
+ if (!storage) throw new Error('El almacenamiento RAG no está disponible.');
274
+ if (!branchId) throw new Error('Se requiere una rama de destino.');
275
+ const result = {
276
+ total: queue.length,
277
+ processed: 0,
278
+ replaced: 0,
279
+ skipped: 0,
280
+ cancelled: 0,
281
+ failed: 0,
282
+ documents: [],
283
+ errors: []
284
+ };
285
+ const maxFileSize = Number(options.maxFileSize) || MAX_DOCUMENT_SIZE;
286
+ let rememberedDuplicatePolicy = null;
287
+ const processedInSession = new Map();
288
+
289
+ const emit = (fileIndex, fileName, status, message, percent, errorDetails) => {
290
+ if (typeof onProgress === 'function') {
291
+ const finishedFiles = result.processed + result.replaced + result.skipped + result.cancelled + result.failed;
292
+ const isFinished = status === 'completed' || status === 'skipped' || status === 'cancelled' || status === 'error';
293
+ const overallPercent = queue.length
294
+ ? Math.min(100, ((finishedFiles + (isFinished ? 0 : (Number(percent) || 0) / 100)) / queue.length) * 100)
295
+ : 100;
296
+ onProgress({
297
+ fileIndex, totalFiles: queue.length, fileName, status, message, percent, errorDetails,
298
+ processedFiles: result.processed, replacedFiles: result.replaced, skippedFiles: result.skipped,
299
+ cancelledFiles: result.cancelled, failedFiles: result.failed, finishedFiles, overallPercent
300
+ });
301
+ }
302
+ };
303
+
304
+ for (let fileIndex = 0; fileIndex < queue.length; fileIndex++) {
305
+ if (options.signal?.aborted) {
306
+ for (let c = fileIndex; c < queue.length; c++) {
307
+ const f = queue[c];
308
+ const fName = f?.name || `documento_${c + 1}.txt`;
309
+ result.cancelled++;
310
+ emit(c, fName, 'cancelled', 'Ingesta cancelada por el usuario', 0);
311
+ }
312
+ break;
313
+ }
314
+
315
+ const file = queue[fileIndex];
316
+ const fullPath = String(file?.webkitRelativePath || file?.path || file?.name || `documento_${fileIndex + 1}.txt`).trim();
317
+ const fileName = file?.name || fullPath;
318
+ const fileType = detectFileType(file);
319
+
320
+ try {
321
+ if (file && typeof file.size === 'number' && file.size > maxFileSize) {
322
+ throw new Error('El archivo supera el tamaño máximo permitido (50 MB).');
323
+ }
324
+ if (!fileType) throw new Error('El archivo no parece contener texto legible.');
325
+
326
+ // Comprobación de duplicados por nombre completo con su ruta idéntica
327
+ const existingDoc = processedInSession.get(fullPath) || (storage.findDocumentByPath ? await storage.findDocumentByPath(branchId, fullPath) : null);
328
+ let action = null;
329
+
330
+ if (existingDoc) {
331
+ if (rememberedDuplicatePolicy) {
332
+ action = rememberedDuplicatePolicy;
333
+ } else if (typeof options.onDuplicateConflict === 'function') {
334
+ const decision = await options.onDuplicateConflict({ fullPath, fileName, existingDoc });
335
+ action = (decision?.action === 'replace') ? 'replace' : 'ignore';
336
+ if (decision?.applyToAll) {
337
+ rememberedDuplicatePolicy = action;
338
+ }
339
+ } else {
340
+ action = 'ignore';
341
+ }
342
+
343
+ if (options.signal?.aborted) {
344
+ for (let c = fileIndex; c < queue.length; c++) {
345
+ const f = queue[c];
346
+ const fName = f?.name || `documento_${c + 1}.txt`;
347
+ result.cancelled++;
348
+ emit(c, fName, 'cancelled', 'Ingesta cancelada por el usuario', 0);
349
+ }
350
+ break;
351
+ }
352
+
353
+ if (action === 'ignore') {
354
+ result.skipped++;
355
+ emit(fileIndex, fileName, 'skipped', `${fileName} omitido (duplicado).`, 100);
356
+ continue;
357
+ }
358
+ }
359
+
360
+ emit(fileIndex, fileName, 'extracting', `Extrayendo texto de ${fileName}…`, 10);
361
+ const { text: extracted, images } = await extractDocumentContent(file, fileType);
362
+ if (options.signal?.aborted) {
363
+ for (let c = fileIndex; c < queue.length; c++) {
364
+ const f = queue[c];
365
+ const fName = f?.name || `documento_${c + 1}.txt`;
366
+ result.cancelled++;
367
+ emit(c, fName, 'cancelled', 'Ingesta cancelada por el usuario', 0);
368
+ }
369
+ break;
370
+ }
371
+ if (!extracted) throw new Error('El archivo no contiene texto extraíble.');
372
+
373
+ emit(fileIndex, fileName, 'chunking', `Particionando ${fileName}…`, 45);
374
+ const documentId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
375
+ ? `doc_${crypto.randomUUID()}`
376
+ : `doc_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
377
+ const preparedText = extracted.replace(/rag-image:\/\/__DOC_ID__:/g, `rag-image://${documentId}:`);
378
+
379
+ const chunks = partitionTextIntoChunks(preparedText, options);
380
+ if (!chunks.length) throw new Error('No se pudieron generar fragmentos del documento.');
381
+
382
+ if (options.signal?.aborted) {
383
+ for (let c = fileIndex; c < queue.length; c++) {
384
+ const f = queue[c];
385
+ const fName = f?.name || `documento_${c + 1}.txt`;
386
+ result.cancelled++;
387
+ emit(c, fName, 'cancelled', 'Ingesta cancelada por el usuario', 0);
388
+ }
389
+ break;
390
+ }
391
+
392
+ emit(fileIndex, fileName, 'saving', `Guardando ${fileName} en IndexedDB…`, 75);
393
+ const docPayload = {
394
+ id: documentId,
395
+ branchId,
396
+ title: fileName,
397
+ path: fullPath,
398
+ fileType,
399
+ mimeType: file?.type || '',
400
+ fileSize: Number(file?.size) || preparedText.length,
401
+ chunks
402
+ };
403
+
404
+ let document;
405
+ if (action === 'replace' && existingDoc) {
406
+ if (storage.replaceDocument) {
407
+ document = await storage.replaceDocument(existingDoc.id, docPayload, images);
408
+ } else {
409
+ await storage.deleteDocument(existingDoc.id);
410
+ document = await storage.saveDocument(docPayload, images);
411
+ }
412
+ result.replaced++;
413
+ } else {
414
+ document = await storage.saveDocument(docPayload, images);
415
+ result.processed++;
416
+ }
417
+
418
+ processedInSession.set(fullPath, document);
419
+ if (index?.invalidateBranch) index.invalidateBranch(branchId);
420
+ result.documents.push(document);
421
+ const statusMsg = action === 'replace'
422
+ ? `${fileName} reemplazado (${chunks.length} fragmentos, ${(images && images.length) || 0} imágenes).`
423
+ : `${fileName} indexado (${chunks.length} fragmentos, ${(images && images.length) || 0} imágenes).`;
424
+ emit(fileIndex, fileName, 'completed', statusMsg, 100);
425
+ } catch (error) {
426
+ const message = error?.message || String(error);
427
+ result.failed++;
428
+ result.errors.push({ fileName, error: message });
429
+ emit(fileIndex, fileName, 'error', `Error en ${fileName}: ${message}`, 0, message);
430
+ }
431
+ }
432
+ return result;
433
+ }
434
+
435
+ return { normalizeExtractedText, extractTextFromPlainText, extractTextFromPDF, detectSectionHeading, partitionTextIntoChunks, detectFileType, isLikelyText, getArchiveFormat, readGzipText, processDocumentQueue, MAX_DOCUMENT_SIZE };
436
+ });