commit
						ba654f9574
					
				| @ -21,6 +21,7 @@ | ||||
|     "@langchain/community": "^0.0.41", | ||||
|     "@mantine/form": "^7.5.0", | ||||
|     "@mantine/hooks": "^7.5.3", | ||||
|     "@mozilla/readability": "^0.5.0", | ||||
|     "@plasmohq/storage": "^1.9.0", | ||||
|     "@tailwindcss/forms": "^0.5.7", | ||||
|     "@tailwindcss/typography": "^0.5.10", | ||||
|  | ||||
| @ -37,6 +37,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Total Search Results", | ||||
|                 "placeholder": "Enter Total Search Results" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Visit the website mentioned in the message" | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
							
								
								
									
										88
									
								
								src/assets/locale/es/common.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/assets/locale/es/common.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | ||||
| { | ||||
|     "pageAssist": "Page Assist", | ||||
|     "selectAModel": "Selecione un Modelo", | ||||
|     "save": "Guardar", | ||||
|     "saved": "Guardado", | ||||
|     "cancel": "Cancelar", | ||||
|     "retry": "Reintentar", | ||||
|     "share": { | ||||
|         "tooltip": { | ||||
|             "share": "Compartir" | ||||
|         }, | ||||
|         "modal": { | ||||
|             "title": "Compartir enlace para chat" | ||||
|         }, | ||||
|         "form": { | ||||
|             "defaultValue": { | ||||
|                 "name": "Anónimo", | ||||
|                 "title": "Chat sin título" | ||||
|             }, | ||||
|             "title": { | ||||
|                 "label": "Título del Chat", | ||||
|                 "placeholder": "Ingresar el título del Chat", | ||||
|                 "required": "El título del Chat es obligatorio" | ||||
|             }, | ||||
|             "name": { | ||||
|                 "label": "Tu nombre", | ||||
|                 "placeholder": "Ingresar tu nombre", | ||||
|                 "required": "Tu nombre es obligatorio" | ||||
|             }, | ||||
|             "btn": { | ||||
|                 "save": "Generar enlace", | ||||
|                 "saving": "Generando enlace..." | ||||
|             } | ||||
|         }, | ||||
|         "notification": { | ||||
|             "successGenerate": "Enlace copiado al Clipboard", | ||||
|             "failGenerate": "Fallo al generar el enlace" | ||||
|         } | ||||
|     }, | ||||
|     "copyToClipboard": "Copiar al clipboard", | ||||
|     "webSearch": "Buscando en la web", | ||||
|     "regenerate": "Regenerar", | ||||
|     "edit": "Editar", | ||||
|     "saveAndSubmit": "Guardar y Enviar", | ||||
|     "editMessage": { | ||||
|         "placeholder": "Ingresar un mensaje..." | ||||
|     }, | ||||
|     "submit": "Enviar", | ||||
|     "noData": "Sin datos", | ||||
|     "noHistory": "Chat sin histórico", | ||||
|     "chatWithCurrentPage": "Conversar con la página actual", | ||||
|     "beta": "Beta", | ||||
|     "tts": "Leer en voz alta", | ||||
|     "currentChatModelSettings": "Configuraciones del Modelo de Chat Actual", | ||||
|     "modelSettings": { | ||||
|         "label": "Configuraciones del Modelo", | ||||
|         "description": "Definir las opciones del modelo globalmente para todos los chats", | ||||
|         "form": { | ||||
|             "keepAlive": { | ||||
|                 "label": "Mantener vivo", | ||||
|                 "help": "controlar cuanto tiempo el modelo permanecera cargado en la memoria luego de su utilización (por defecto: 5m)", | ||||
|                 "placeholder": "Ingresar duración para mantenerlo vivo (ej: 5m, 10m, 1h)" | ||||
|             }, | ||||
|             "temperature": { | ||||
|                 "label": "Temperatura", | ||||
|                 "placeholder": "Ingresar valor de la Temperatura (ej: 0.7, 1.0)" | ||||
|             }, | ||||
|             "numCtx": { | ||||
|                 "label": "Cantidad de contextos", | ||||
|                 "placeholder": "Ingresar el valor de tamaño de la ventana de contexto (por defecto: 2048)" | ||||
|             }, | ||||
|             "seed": { | ||||
|                 "label": "Semilla", | ||||
|                 "placeholder": "Ingresar el valor de la semilla (ej: 1234)", | ||||
|                 "help": "Reproductibilidad de la salida del modelo" | ||||
|             }, | ||||
|             "topK": { | ||||
|                 "label": "Top K", | ||||
|                 "placeholder": "Ingresar el valor de Top K (ej: 40, 100)" | ||||
|             }, | ||||
|             "topP": { | ||||
|                 "label": "Top P", | ||||
|                 "placeholder": "Ingresar el valor de Top P (ej: 0.9, 0.95)" | ||||
|             } | ||||
|         }, | ||||
|         "advanced": "Más Configuraciones del Modelo" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								src/assets/locale/es/knowledge.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/assets/locale/es/knowledge.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| { | ||||
|     "addBtn": "Agregar Nuevo Conocimiento", | ||||
|     "columns": { | ||||
|         "title": "Título", | ||||
|         "status": "Estado", | ||||
|         "embeddings": "Modelo de Embedding", | ||||
|         "createdAt": "Creado", | ||||
|         "action": "Acciones" | ||||
|     }, | ||||
|     "expandedColumns": { | ||||
|         "name": "Nombre" | ||||
|     }, | ||||
|     "tooltip": { | ||||
|         "delete": "Borrar" | ||||
|     }, | ||||
|     "confirm": { | ||||
|         "delete": "¿Esta seguro que desea borrar este conocimiento?" | ||||
|     }, | ||||
|     "deleteSuccess": "Conocimiento borrado", | ||||
|     "status": { | ||||
|         "pending": "Pendiente", | ||||
|         "finished": "Finalizado", | ||||
|         "processing": "Procesando" | ||||
|     }, | ||||
|     "addKnowledge": "Agregar Conocimiento", | ||||
|     "form": { | ||||
|         "title": { | ||||
|             "label": "Título del Conocimiento", | ||||
|             "placeholder": "Ingresar un título de conocimiento", | ||||
|             "required": "El Título de conocimiento es obligatorio" | ||||
|         }, | ||||
|         "uploadFile": { | ||||
|             "label": "Subir un Archivo", | ||||
|             "uploadText": "Arraste y suelte un archivo aquí o haga click para subirlo", | ||||
|             "uploadHint": "Tipos de archivo soportados: .pdf, .csv, .txt, .md, .docx", | ||||
|             "required": "El archivo es obligatorio" | ||||
|         }, | ||||
|         "submit": "Enviar", | ||||
|         "success": "Conocimiento agregado exitosamente" | ||||
|     }, | ||||
|     "noEmbeddingModel": "Por favor, agregue un modelo de embedding de la página de configuraciones de RAG primero" | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/assets/locale/es/option.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/assets/locale/es/option.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| { | ||||
|     "newChat": "Nuevo Chat", | ||||
|     "selectAPrompt": "Selecione un Prompt", | ||||
|     "githubRepository": "Repositorio de GitHub", | ||||
|     "settings": "Configuraciones", | ||||
|     "sidebarTitle": "Histórico del Chat", | ||||
|     "error": "Error", | ||||
|     "somethingWentWrong": "Hubo un error", | ||||
|     "validationSelectModel": "Selecione un modelo para continuar", | ||||
|     "deleteHistoryConfirmation": "¿Esta seguro que quiere borrar éste histórico?", | ||||
|     "editHistoryTitle": "Ingrese un nuevo título" | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/assets/locale/es/playground.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/assets/locale/es/playground.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| { | ||||
|     "ollamaState": { | ||||
|         "searching": "Buscando tu Ollama 🦙", | ||||
|         "running": "Ollama está funcionando 🦙", | ||||
|         "notRunning": "No fue posible conectar con Ollama 🦙", | ||||
|         "connectionError": "Hubo un error de conexión. Por favor, consulte la <anchor>documentación</anchor> para solucionar el problema." | ||||
|     }, | ||||
|     "formError": { | ||||
|         "noModel": "Por favor, selecione un modelo", | ||||
|         "noEmbeddingModel": "Por favor, defina un modelo de embedding para la página de configuraciones > RAG" | ||||
|     }, | ||||
|     "form": { | ||||
|         "textarea": { | ||||
|             "placeholder": "Ingrese un mensaje..." | ||||
|         }, | ||||
|         "webSearch": { | ||||
|             "on": "On", | ||||
|             "off": "Off" | ||||
|         } | ||||
|     }, | ||||
|     "tooltip": { | ||||
|         "searchInternet": "Buscar en Internet", | ||||
|         "speechToText": "Voz a Texto", | ||||
|         "uploadImage": "Subir Imagén", | ||||
|         "stopStreaming": "Parar Transmisión", | ||||
|         "knowledge": "Conocimiento" | ||||
|     }, | ||||
|     "sendWhenEnter": "Enviar cuando presione Enter" | ||||
| } | ||||
							
								
								
									
										292
									
								
								src/assets/locale/es/settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								src/assets/locale/es/settings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,292 @@ | ||||
| { | ||||
|     "generalSettings": { | ||||
|         "title": "Configuraciones Generales", | ||||
|         "settings": { | ||||
|             "heading": "Configuraciones de la Interfaz Web", | ||||
|             "speechRecognitionLang": { | ||||
|                 "label": "Idioma de Reconocimiento de Voz", | ||||
|                 "placeholder": "Selecione un idioma" | ||||
|             }, | ||||
|             "language": { | ||||
|                 "label": "Idioma", | ||||
|                 "placeholder": "Selecione un idioma" | ||||
|             }, | ||||
|             "darkMode": { | ||||
|                 "label": "Cambiar Tema", | ||||
|                 "options": { | ||||
|                     "light": "Claro", | ||||
|                     "dark": "Oscuro" | ||||
|                 } | ||||
|             }, | ||||
|             "copilotResumeLastChat": { | ||||
|                 "label": "Retomar el último chat al abrir el Panel Lateral (Copilot)" | ||||
|             }, | ||||
|             "hideCurrentChatModelSettings": { | ||||
|                 "label": "Ocultar Configuraciones del Modelo de Chat Actual" | ||||
|             } | ||||
|         }, | ||||
|         "webSearch": { | ||||
|             "heading": "Manejo de la busqueda Web", | ||||
|             "searchMode": { | ||||
|                 "label": "Realizar busquedas Simples en Internet" | ||||
|             }, | ||||
|             "provider": { | ||||
|                 "label": "Motor de Busqueda", | ||||
|                 "placeholder": "Selecione un motor de busqueda" | ||||
|             }, | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Resultados totales de la busqueda", | ||||
|                 "placeholder": "Ingresar el total de Resultados de la busqueda" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Visita el sitio web mencionado en el mensaje" | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|             "heading": "Configuraciones del Sistema", | ||||
|             "deleteChatHistory": { | ||||
|                 "label": "Borrar Histórico del Chat", | ||||
|                 "button": "Borrar", | ||||
|                 "confirm": "¿Esta seguro que desea borrar su histórico del chat? Esta acción no podra ser desecha." | ||||
|             }, | ||||
|             "export": { | ||||
|                 "label": "Exportar Histórico del Chat, Base de Conocimiento y Prompts", | ||||
|                 "button": "Exportar Datos", | ||||
|                 "success": "Exportación exitosa" | ||||
|             }, | ||||
|             "import": { | ||||
|                 "label": "Importar Histórico del Chat, Base de Conocimiento y Prompts", | ||||
|                 "button": "Importar Datos", | ||||
|                 "success": "Importación existosa", | ||||
|                 "error": "Error de importación" | ||||
|             } | ||||
|         }, | ||||
|         "tts": { | ||||
|             "heading": "Configuraciones de Text-to-speech", | ||||
|             "ttsEnabled": { | ||||
|                 "label": "Habilitar Texto-a-Voz" | ||||
|             }, | ||||
|             "ttsProvider": { | ||||
|                 "label": "Proveedor de Text-to-speech", | ||||
|                 "placeholder": "Selecione un proveedor" | ||||
|             }, | ||||
|             "ttsVoice": { | ||||
|                 "label": "Voz de Text-to-speech", | ||||
|                 "placeholder": "Selecione una voz" | ||||
|             }, | ||||
|             "ssmlEnabled": { | ||||
|                 "label": "Habilitar SSML (Speech Synthesis Markup Language)" | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "manageModels": { | ||||
|         "title": "Administar de Modelos", | ||||
|         "addBtn": "Agregar Nuevo Modelo", | ||||
|         "columns": { | ||||
|             "name": "Nombre", | ||||
|             "digest": "Resumen", | ||||
|             "modifiedAt": "Modificado", | ||||
|             "size": "Tamaño", | ||||
|             "actions": "Acciones" | ||||
|         }, | ||||
|         "expandedColumns": { | ||||
|             "parentModel": "Modelo Padre", | ||||
|             "format": "Formato", | ||||
|             "family": "Familia", | ||||
|             "parameterSize": "Tamaño de Parametros", | ||||
|             "quantizationLevel": "Nível de Quantización" | ||||
|         }, | ||||
|         "tooltip": { | ||||
|             "delete": "Borrar Modelo", | ||||
|             "repull": "Traer nuevamente el Modelo" | ||||
|         }, | ||||
|         "confirm": { | ||||
|             "delete": "¿Esta seguro que desea borrar este modelos?", | ||||
|             "repull": "¿Esta seguro que desea traer nuevamente este modelo?" | ||||
|         }, | ||||
|         "modal": { | ||||
|             "title": "Traer Nuevo Modelo", | ||||
|             "placeholder": "Ingresar el nombre del modelo", | ||||
|             "pull": "Traer Modelo" | ||||
|         }, | ||||
|         "notification": { | ||||
|             "pullModel": "Trayendo Modelo", | ||||
|             "pullModelDescription": "Trayendo modelo {{modelName}}. Para más detalles, verifique el ícono de la extensión.", | ||||
|             "success": "Exito", | ||||
|             "error": "Error", | ||||
|             "successDescription": "Modelo traido exitosamente", | ||||
|             "successDeleteDescription": "Modelo borrado exitosamente", | ||||
|             "someError": "Hubo un error. Intente nuevamente más tarde" | ||||
|         } | ||||
|     }, | ||||
|     "managePrompts": { | ||||
|         "title": "Administrar de Prompts", | ||||
|         "addBtn": "Agregar Nuevo Prompt", | ||||
|         "option1": "Normal", | ||||
|         "option2": "RAG", | ||||
|         "questionPrompt": "Prompt de Pregunta", | ||||
|         "columns": { | ||||
|             "title": "Título", | ||||
|             "prompt": "Prompt", | ||||
|             "type": "Tipo de Prompt", | ||||
|             "actions": "Acciones" | ||||
|         }, | ||||
|         "systemPrompt": "Prompt del Sistema", | ||||
|         "quickPrompt": "Prompt Rápido", | ||||
|         "tooltip": { | ||||
|             "delete": "Borrar Prompt", | ||||
|             "edit": "Editar Prompt" | ||||
|         }, | ||||
|         "confirm": { | ||||
|             "delete": "¿Esta seguro que desea borrar este prompt? Esta acción no tiene vuelta a atrás." | ||||
|         }, | ||||
|         "modal": { | ||||
|             "addTitle": "Agregar Nuevo Prompt", | ||||
|             "editTitle": "Editar Prompt" | ||||
|         }, | ||||
|         "form": { | ||||
|             "title": { | ||||
|                 "label": "Título", | ||||
|                 "placeholder": "Mi Prompt genial", | ||||
|                 "required": "Por favor, ingrese un título" | ||||
|             }, | ||||
|             "prompt": { | ||||
|                 "label": "Prompt", | ||||
|                 "placeholder": "Ingrese un prompt", | ||||
|                 "required": "Por favor, ingrese un prompt", | ||||
|                 "help": "Puede usar {key} como variable en su prompt." | ||||
|             }, | ||||
|             "isSystem": { | ||||
|                 "label": "Es un Prompt del Sistema" | ||||
|             }, | ||||
|             "btnSave": { | ||||
|                 "saving": "Agregando un Prompt...", | ||||
|                 "save": "Agregar Prompt" | ||||
|             }, | ||||
|             "btnEdit": { | ||||
|                 "saving": "Actualizando Prompt...", | ||||
|                 "save": "Actualizar Prompt" | ||||
|             } | ||||
|         }, | ||||
|         "notification": { | ||||
|             "addSuccess": "Prompt Agregado", | ||||
|             "addSuccessDesc": "Prompt agregado exitosamente", | ||||
|             "error": "Error", | ||||
|             "someError": "Hubo un error. Intente nuevamente más tarde", | ||||
|             "updatedSuccess": "Prompt Actualizado", | ||||
|             "updatedSuccessDesc": "Prompt actualizado exitosamente", | ||||
|             "deletedSuccess": "Prompt Borrado", | ||||
|             "deletedSuccessDesc": "Prompt borrado exitosamente" | ||||
|         } | ||||
|     }, | ||||
|     "manageShare": { | ||||
|         "title": "Administrar los recursos compartidos", | ||||
|         "heading": "Configurar URL de Página Compartida", | ||||
|         "form": { | ||||
|             "url": { | ||||
|                 "label": "URL de Página compartida", | ||||
|                 "placeholder": "Ingresar URL de Página compartida", | ||||
|                 "required": "Por favor, ingrese URL de Página compartida", | ||||
|                 "help": "Por motivos de privacidad, podes hacer self-host de la página compartida y proveer una URL aqui. <anchor>Aprende más</anchor>." | ||||
|             } | ||||
|         }, | ||||
|         "webshare": { | ||||
|             "heading": "Compartir una Web", | ||||
|             "columns": { | ||||
|                 "title": "Título", | ||||
|                 "url": "URL", | ||||
|                 "actions": "Acciones" | ||||
|             }, | ||||
|             "tooltip": { | ||||
|                 "delete": "Borrar lo compartido" | ||||
|             }, | ||||
|             "confirm": { | ||||
|                 "delete": "¿Esta seguro de desear borrar esta web compartida? Esta acción no tiene vuelta a atrás." | ||||
|             }, | ||||
|             "label": "Administrar páginas compartidas", | ||||
|             "description": "Habilitar o deshabilitar el recurso de páginas compartidas" | ||||
|         }, | ||||
|         "notification": { | ||||
|             "pageShareSuccess": "URL compartida actualizada exitosamente", | ||||
|             "someError": "Hubo un error. Intente nuevamente más tarde", | ||||
|             "webShareDeleteSuccess": "Web compartida borrada exitosamente com sucesso" | ||||
|         } | ||||
|     }, | ||||
|     "ollamaSettings": { | ||||
|         "title": "Configuraciones de Ollama", | ||||
|         "heading": "Configurar Ollama", | ||||
|         "settings": { | ||||
|             "ollamaUrl": { | ||||
|                 "label": "URL de Ollama", | ||||
|                 "placeholder": "Ingrese la URL de Ollama" | ||||
|             }, | ||||
|             "advanced": { | ||||
|                 "label": "Configuración avanzada de URL de Ollama", | ||||
|                 "urlRewriteEnabled": { | ||||
|                     "label": "Habilitar o Deshabilitar URL Personalizada" | ||||
|                 }, | ||||
|                 "rewriteUrl": { | ||||
|                     "label": "URL Personalizada", | ||||
|                     "placeholder": "Ingresar URL Personalizada" | ||||
|                 }, | ||||
|                 "help": "Si tenes problemas de conexión con Ollama en Page Assist, podes configurar una URL de personalizada. Para saber más sobre la configuración, <anchor>click aqui</anchor>." | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "manageSearch": { | ||||
|         "title": "Administrar Busqueda Web", | ||||
|         "heading": "Configurar Busqueda Web" | ||||
|     }, | ||||
|     "about": { | ||||
|         "title": "Sobre", | ||||
|         "heading": "Sobre", | ||||
|         "chromeVersion": "Versión de Page Assist", | ||||
|         "ollamaVersion": "Versión de Ollama", | ||||
|         "support": "Podes apoyar el proyecto Page Assist haciendo donaciones o patrocinarnos a través de las seguientes plataformas:", | ||||
|         "koFi": "Apoyar en Ko-fi", | ||||
|         "githubSponsor": "Patrocinarnos en GitHub", | ||||
|         "githubRepo": "Repositorio de GitHub" | ||||
|     }, | ||||
|     "manageKnowledge": { | ||||
|         "title": "Administrar Conocimiento", | ||||
|         "heading": "Configurar Bases de Conocimiento" | ||||
|     }, | ||||
|     "rag": { | ||||
|         "title": "Configuraciones de RAG", | ||||
|         "ragSettings": { | ||||
|             "label": "Configuraciones de RAG", | ||||
|             "model": { | ||||
|                 "label": "Modelo de embeddings", | ||||
|                 "required": "Por favor, selecione un modelo", | ||||
|                 "help": "Es recomendable usar modelos de embeddings como `nomic-embed-text`.", | ||||
|                 "placeholder": "Selecione un modelo" | ||||
|             }, | ||||
|             "chunkSize": { | ||||
|                 "label": "Tamaño del Chunk", | ||||
|                 "placeholder": "Ingresar el tamaño del chunk", | ||||
|                 "required": "Por favor, ingrese el tamaño del chunk" | ||||
|             }, | ||||
|             "chunkOverlap": { | ||||
|                 "label": "Solapamiento del Chunk", | ||||
|                 "placeholder": "Ingrese el solapamiento del chunk", | ||||
|                 "required": "Por favor, ingresar el solapamiento del chunk" | ||||
|             } | ||||
|         }, | ||||
|         "prompt": { | ||||
|             "label": "Configurar el Prompt del RAG", | ||||
|             "option1": "Normal", | ||||
|             "option2": "Web", | ||||
|             "alert": "Es obsoleto configurar aquí el prompt del sistema. Por favor, use la sección de Administrar Prompts para agregar o editar prompts. Esta sección se quitará en una versión futura", | ||||
|             "systemPrompt": "Prompt del Sistema", | ||||
|             "systemPromptPlaceholder": "Ingresar el prompt del sistema", | ||||
|             "webSearchPrompt": "Prompt de la busqueda Web", | ||||
|             "webSearchPromptHelp": "No borre `{search_results}` del prompt.", | ||||
|             "webSearchPromptError": "Por favor, ingresar un prompt de busqueda web", | ||||
|             "webSearchPromptPlaceholder": "Ingrese un prompt de busqueda web", | ||||
|             "webSearchFollowUpPrompt": "Prompt de Seguimiento de busqueda Web", | ||||
|             "webSearchFollowUpPromptHelp": "No borre `{chat_history}` y `{question}` del prompt.", | ||||
|             "webSearchFollowUpPromptError": "Por favor, ingrese el prompt de seguimiento de la busqueda web", | ||||
|             "webSearchFollowUpPromptPlaceholder": "Su prompt de seguimiento de busqueda web" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/assets/locale/es/sidepanel.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/assets/locale/es/sidepanel.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| { | ||||
|     "tooltip": { | ||||
|         "embed": "Puede demorar algunos minutos para incluir la página. Por favor, aguarde...", | ||||
|         "clear": "Borrar el histórico de conversación", | ||||
|         "history": "Histórico de la conversación" | ||||
|     } | ||||
| } | ||||
| @ -37,6 +37,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Résultats de la recherche totaux", | ||||
|                 "placeholder": "Entrez les résultats de la recherche totaux" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Visitez le site web mentionné dans le message" | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -37,6 +37,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Risultati della ricerca", | ||||
|                 "placeholder": "Inserisci il totale delle ricerche" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Visita il sito web menzionato nel messaggio" | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -40,6 +40,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "合計検索結果", | ||||
|                 "placeholder": "合計検索結果を入力する" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "メッセージに記載されたウェブサイトを訪問してください" | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -40,6 +40,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "ആകെ തിരച്ചിൽ ഫലങ്ങൾ", | ||||
|                 "placeholder": "ആകെ തിരച്ചിൽ ഫലങ്ങളുടെ എണ്ണം നൽകുക" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "സന്ദേശത്തിൽ പറയുന്ന വെബ്സൈറ്റ് സന്ദർശിക്കുക." | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -37,6 +37,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Resultados de Pesquisa Totais", | ||||
|                 "placeholder": "Insira Resultados de Pesquisa Totais" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Visite o site mencionado na mensagem." | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -37,6 +37,9 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "Общее количество результатов поиска", | ||||
|                 "placeholder": "Введите общее количество результатов поиска" | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|                 "label": "Посетите веб-сайт, указанный в сообщении." | ||||
|             } | ||||
|         }, | ||||
|         "system": { | ||||
|  | ||||
| @ -40,7 +40,10 @@ | ||||
|             "totalSearchResults": { | ||||
|                 "label": "总搜索结果", | ||||
|                 "placeholder": "输入总搜索结果" | ||||
|             } | ||||
|             }, | ||||
|             "visitSpecificWebsite": { | ||||
|         "label": "访问消息中提到的网站。" | ||||
|     } | ||||
|         }, | ||||
|         "system": { | ||||
|             "heading": "系统设置", | ||||
|  | ||||
| @ -14,6 +14,7 @@ import { useTranslation } from "react-i18next" | ||||
| import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect" | ||||
| import { useSpeechRecognition } from "@/hooks/useSpeechRecognition" | ||||
| import { PiGlobe } from "react-icons/pi" | ||||
| import { extractReadabilityContent } from "@/parser/reader" | ||||
| 
 | ||||
| type Props = { | ||||
|   dropedFile: File | undefined | ||||
|  | ||||
| @ -13,7 +13,8 @@ export const SearchModeSettings = () => { | ||||
|     initialValues: { | ||||
|       isSimpleInternetSearch: false, | ||||
|       searchProvider: "", | ||||
|       totalSearchResults: 0 | ||||
|       totalSearchResults: 0, | ||||
|       visitSpecificWebsite: false | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
| @ -67,7 +68,7 @@ export const SearchModeSettings = () => { | ||||
|           </span> | ||||
|           <div> | ||||
|             <Switch | ||||
|             className="mt-4 sm:mt-0" | ||||
|               className="mt-4 sm:mt-0" | ||||
|               {...form.getInputProps("isSimpleInternetSearch", { | ||||
|                 type: "checkbox" | ||||
|               })} | ||||
| @ -89,6 +90,20 @@ export const SearchModeSettings = () => { | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className="flex sm:flex-row flex-col space-y-4 sm:space-y-0 sm:justify-between"> | ||||
|           <span className="text-gray-700 dark:text-neutral-50 "> | ||||
|             {t("generalSettings.webSearch.visitSpecificWebsite.label")} | ||||
|           </span> | ||||
|           <div> | ||||
|             <Switch | ||||
|               className="mt-4 sm:mt-0" | ||||
|               {...form.getInputProps("visitSpecificWebsite", { | ||||
|                 type: "checkbox" | ||||
|               })} | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className="flex justify-end"> | ||||
|           <SaveButton btnType="submit" /> | ||||
|         </div> | ||||
|  | ||||
| @ -1,87 +1,106 @@ | ||||
| import { useCallback, useEffect, useRef, useState } from "react" | ||||
| import { useMessageOption } from "./useMessageOption" | ||||
| 
 | ||||
| export const useScrollAnchor = () => { | ||||
|   const messagesRef = useRef<HTMLDivElement>(null) | ||||
|   const scrollRef = useRef<HTMLDivElement>(null) | ||||
|   const visibilityRef = useRef<HTMLDivElement>(null) | ||||
|   const { isProcessing, messages } = useMessageOption() | ||||
| 
 | ||||
|   const [isAtTop, setIsAtTop] = useState(false) | ||||
|   const [isAtBottom, setIsAtBottom] = useState(true) | ||||
|   const [isVisible, setIsVisible] = useState(false) | ||||
|   const [userScrolled, setUserScrolled] = useState(false) | ||||
|   const [isOverflowing, setIsOverflowing] = useState(false) | ||||
| 
 | ||||
|   const messagesStartRef = useRef<HTMLDivElement>(null) | ||||
|   const messagesEndRef = useRef<HTMLDivElement>(null) | ||||
|   const containerRef = useRef<HTMLDivElement>(null) | ||||
|   const isAutoScrolling = useRef(false) | ||||
| 
 | ||||
|   console.log(`isAtTop: ${isAtTop}, isAtBottom: ${isAtBottom}, userScrolled: ${userScrolled}, isOverflowing: ${isOverflowing}`) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (!isProcessing && userScrolled) { | ||||
|       console.log("userScrolled") | ||||
|       setUserScrolled(false) | ||||
|     } | ||||
|   }, [isProcessing]) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (isProcessing && !userScrolled) { | ||||
|       scrollToBottom() | ||||
|     } | ||||
|   }, [messages]) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const container = containerRef.current | ||||
|     if (!container) return | ||||
| 
 | ||||
|     const topObserver = new IntersectionObserver( | ||||
|       ([entry]) => { | ||||
|         setIsAtTop(entry.isIntersecting) | ||||
|       }, | ||||
|       { threshold: 1 } | ||||
|     ) | ||||
| 
 | ||||
|     const bottomObserver = new IntersectionObserver( | ||||
|       ([entry]) => { | ||||
|         setIsAtBottom(entry.isIntersecting) | ||||
|         if (entry.isIntersecting) { | ||||
|           setUserScrolled(false) | ||||
|         } else if (!isAutoScrolling.current) { | ||||
|           setUserScrolled(true) | ||||
|         } | ||||
|       }, | ||||
|       { threshold: 1 } | ||||
|     ) | ||||
| 
 | ||||
|     if (messagesStartRef.current) { | ||||
|       topObserver.observe(messagesStartRef.current) | ||||
|     } | ||||
| 
 | ||||
|     if (messagesEndRef.current) { | ||||
|       bottomObserver.observe(messagesEndRef.current) | ||||
|     } | ||||
| 
 | ||||
|     const resizeObserver = new ResizeObserver(() => { | ||||
|       setIsOverflowing(container.scrollHeight > container.clientHeight) | ||||
|     }) | ||||
| 
 | ||||
|     resizeObserver.observe(container) | ||||
| 
 | ||||
|     return () => { | ||||
|       topObserver.disconnect() | ||||
|       bottomObserver.disconnect() | ||||
|       resizeObserver.disconnect() | ||||
|     } | ||||
|   }, []) | ||||
| 
 | ||||
|   const scrollToTop = useCallback(() => { | ||||
|     if (messagesStartRef.current) { | ||||
|       messagesStartRef.current.scrollIntoView({ behavior: "smooth" }) | ||||
|     } | ||||
|   }, []) | ||||
| 
 | ||||
|   const scrollToBottom = useCallback(() => { | ||||
|     if (messagesRef.current) { | ||||
|       messagesRef.current.scrollIntoView({ | ||||
|         block: "end", | ||||
|         behavior: "smooth" | ||||
|       }) | ||||
|     } | ||||
|     isAutoScrolling.current = true | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|       if (messagesEndRef.current) { | ||||
|         messagesEndRef.current.scrollIntoView({ behavior: "smooth" }) | ||||
|       } | ||||
| 
 | ||||
|       isAutoScrolling.current = false | ||||
|     }, 100) | ||||
|   }, []) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (messagesRef.current) { | ||||
|       if (isAtBottom && !isVisible) { | ||||
|         messagesRef.current.scrollIntoView({ | ||||
|           block: "end" | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   }, [isAtBottom, isVisible]) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const { current } = scrollRef | ||||
| 
 | ||||
|     if (current) { | ||||
|       const handleScroll = (event: Event) => { | ||||
|         const target = event.target as HTMLDivElement | ||||
|         const offset = 25 | ||||
|         const isAtBottom = | ||||
|           target.scrollTop + target.clientHeight >= target.scrollHeight - offset | ||||
|         console.log(target.scrollTop, target.clientHeight, target.scrollHeight) | ||||
|         setIsAtBottom(isAtBottom) | ||||
|       } | ||||
| 
 | ||||
|       current.addEventListener("scroll", handleScroll, { | ||||
|         passive: true | ||||
|       }) | ||||
| 
 | ||||
|       return () => { | ||||
|         current.removeEventListener("scroll", handleScroll) | ||||
|       } | ||||
|     } | ||||
|   }, []) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (visibilityRef.current) { | ||||
|       let observer = new IntersectionObserver( | ||||
|         (entries) => { | ||||
|           entries.forEach((entry) => { | ||||
|             console.log(entry.isIntersecting) | ||||
|             if (entry.isIntersecting) { | ||||
|               setIsVisible(true) | ||||
|             } else { | ||||
|               setIsVisible(false) | ||||
|             } | ||||
|           }) | ||||
|         }, | ||||
|         { | ||||
|           rootMargin: "0px 0px -100px 0px" | ||||
|         } | ||||
|       ) | ||||
| 
 | ||||
|       observer.observe(visibilityRef.current) | ||||
| 
 | ||||
|       return () => { | ||||
|         observer.disconnect() | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
|   return { | ||||
|     messagesRef, | ||||
|     scrollRef, | ||||
|     visibilityRef, | ||||
|     scrollToBottom, | ||||
|     messagesStartRef, | ||||
|     messagesEndRef, | ||||
|     containerRef, | ||||
|     isAtTop, | ||||
|     isAtBottom, | ||||
|     isVisible | ||||
|     userScrolled, | ||||
|     isOverflowing, | ||||
|     scrollToTop, | ||||
|     scrollToBottom, | ||||
|     setIsAtBottom | ||||
|   } | ||||
| } | ||||
| @ -8,6 +8,7 @@ import { ml } from "./lang/ml"; | ||||
| import { zh } from "./lang/zh"; | ||||
| import { ja } from "./lang/ja"; | ||||
| import { it } from "./lang/it"; | ||||
| import { es } from "./lang/es"; | ||||
| import LanguageDetector from 'i18next-browser-languagedetector'; | ||||
| 
 | ||||
| i18n | ||||
| @ -16,6 +17,7 @@ i18n | ||||
|     .init({ | ||||
|         resources: { | ||||
|             en: en, | ||||
|             es: es, | ||||
|             fr: fr, | ||||
|             "it": it, | ||||
|             ml: ml, | ||||
|  | ||||
							
								
								
									
										15
									
								
								src/i18n/lang/es.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/i18n/lang/es.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| import option from "@/assets/locale/es/option.json"; | ||||
| import playground from "@/assets/locale/es/playground.json"; | ||||
| import common from "@/assets/locale/es/common.json"; | ||||
| import sidepanel from "@/assets/locale/es/sidepanel.json"; | ||||
| import settings from "@/assets/locale/es/settings.json"; | ||||
| import knowledge from "@/assets/locale/es/knowledge.json"; | ||||
| 
 | ||||
| export const es = { | ||||
|     option, | ||||
|     playground, | ||||
|     common, | ||||
|     sidepanel, | ||||
|     settings, | ||||
|     knowledge | ||||
| } | ||||
| @ -4,6 +4,10 @@ export const supportLanguage = [ | ||||
|         label: "English", | ||||
|         value: "en" | ||||
|     }, | ||||
|     { | ||||
|         label: "Español", | ||||
|         value: "es" | ||||
|     }, | ||||
|     { | ||||
|         label: "Français", | ||||
|         value: "fr" | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { BaseDocumentLoader } from "langchain/document_loaders/base" | ||||
| import { Document } from "@langchain/core/documents" | ||||
| import { compile } from "html-to-text" | ||||
| import { urlRewriteRuntime } from "~/libs/runtime" | ||||
| import { YtTranscript } from "yt-transcript" | ||||
| import { isWikipedia, parseWikipedia } from "@/parser/wiki" | ||||
| import { extractReadabilityContent } from "@/parser/reader" | ||||
| 
 | ||||
| const YT_REGEX = | ||||
|   /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9_-]+)/ | ||||
| @ -24,8 +24,7 @@ export interface WebLoaderParams { | ||||
| 
 | ||||
| export class PageAssistHtmlLoader | ||||
|   extends BaseDocumentLoader | ||||
|   implements WebLoaderParams | ||||
| { | ||||
|   implements WebLoaderParams { | ||||
|   html: string | ||||
|   url: string | ||||
| 
 | ||||
| @ -52,30 +51,14 @@ export class PageAssistHtmlLoader | ||||
|         { | ||||
|           metadata: { | ||||
|             source: this.url, | ||||
|             url: this.url, | ||||
|             audio: { chunks: transcript } | ||||
|           }, | ||||
|           pageContent: text | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
| 
 | ||||
|     // let html = this.html
 | ||||
| 
 | ||||
|     // if (isWikipedia(this.url)) {
 | ||||
|     //   console.log("Wikipedia URL detected")
 | ||||
|     //   html = parseWikipedia(html)
 | ||||
|     // }
 | ||||
| 
 | ||||
|     // // else if (isTwitter(this.url)) {
 | ||||
|     // //   console.log("Twitter URL detected")
 | ||||
|     // //   html = parseTweet(html, this.url)
 | ||||
|     // // }
 | ||||
| 
 | ||||
|     // const htmlCompiler = compile({
 | ||||
|     //   wordwrap: false
 | ||||
|     // })
 | ||||
|     // const text = htmlCompiler(html)
 | ||||
|     const metadata = { source: this.url } | ||||
|     const metadata = { source: this.url, url: this.url, } | ||||
|     return [new Document({ pageContent: this.html, metadata })] | ||||
|   } | ||||
| 
 | ||||
| @ -95,6 +78,7 @@ export class PageAssistHtmlLoader | ||||
|       return [ | ||||
|         { | ||||
|           metadata: { | ||||
|             url: this.url, | ||||
|             source: this.url, | ||||
|             audio: { chunks: transcript } | ||||
|           }, | ||||
| @ -103,22 +87,15 @@ export class PageAssistHtmlLoader | ||||
|       ] | ||||
|     } | ||||
|     await urlRewriteRuntime(this.url, "web") | ||||
|     const fetchHTML = await fetch(this.url) | ||||
|     let html = await fetchHTML.text() | ||||
| 
 | ||||
|     let text = ""; | ||||
|     if (isWikipedia(this.url)) { | ||||
|       console.log("Wikipedia URL detected") | ||||
|       html = parseWikipedia(await fetchHTML.text()) | ||||
|       const fetchHTML = await fetch(this.url) | ||||
|       text = parseWikipedia(await fetchHTML.text()) | ||||
|     } else { | ||||
|       text = await extractReadabilityContent(this.url) | ||||
|     } | ||||
| 
 | ||||
|     const htmlCompiler = compile({ | ||||
|       wordwrap: false, | ||||
|       selectors: [ | ||||
|         { selector: "img", format: "skip" }, | ||||
|         { selector: "script", format: "skip" } | ||||
|       ] | ||||
|     }) | ||||
|     const text = htmlCompiler(html) | ||||
|     const metadata = { url: this.url } | ||||
|     return [new Document({ pageContent: text, metadata })] | ||||
|   } | ||||
|  | ||||
							
								
								
									
										19
									
								
								src/parser/reader.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/parser/reader.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| import { Readability } from "@mozilla/readability" | ||||
| import { defaultExtractContent } from "./default" | ||||
| export const extractReadabilityContent = async (url: string) => { | ||||
|     const response = await fetch(url) | ||||
|     if (!response.ok) { | ||||
|         throw new Error(`Failed to fetch ${url}`) | ||||
|     } | ||||
| 
 | ||||
|     const html = await response.text() | ||||
| 
 | ||||
|     // create a fake dom for Readability
 | ||||
|     const doc = new DOMParser().parseFromString(html, "text/html") | ||||
|     const reader = new Readability(doc) | ||||
|     const article = reader.parse() | ||||
| 
 | ||||
|     // convert the article to markdown
 | ||||
|     const markdown = defaultExtractContent(article.content) | ||||
|     return markdown | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| import * as cheerio from "cheerio" | ||||
| import { defaultExtractContent } from "./default" | ||||
| 
 | ||||
| export const isWikipedia = (url: string) => { | ||||
|   const WIKI_REGEX = /wikipedia\.org\/wiki\//g | ||||
| @ -24,5 +25,5 @@ export const parseWikipedia = (html: string) => { | ||||
|   content?.find("div.toc")?.remove() | ||||
|   const newHtml = content?.html() | ||||
| 
 | ||||
|   return `<div>TITLE: ${title?.text()}</div><div>${newHtml}</div>` | ||||
|   return defaultExtractContent(`<div>TITLE: ${title?.text()}</div><div>${newHtml}</div>`) | ||||
| } | ||||
|  | ||||
| @ -39,3 +39,17 @@ export const getAdvancedOllamaSettings = async () => { | ||||
| export const copilotResumeLastChat = async () => { | ||||
|   return await storage.get<boolean>("copilotResumeLastChat") | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export const defaultSidebarOpen = async () => { | ||||
|   const sidebarOpen = await storage.get("sidebarOpen") | ||||
|   if (!sidebarOpen || sidebarOpen === "") { | ||||
|     return "right_clk" | ||||
|   } | ||||
|   return sidebarOpen | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export const setSidebarOpen = async (sidebarOpen: string) => { | ||||
|   await storage.set("sidebarOpen", sidebarOpen) | ||||
| } | ||||
| @ -15,6 +15,21 @@ export const getIsSimpleInternetSearch = async () => { | ||||
|   return isSimpleInternetSearch === "true" | ||||
| } | ||||
| 
 | ||||
| export const getIsVisitSpecificWebsite = async () => { | ||||
|   const isVisitSpecificWebsite = await storage.get("isVisitSpecificWebsite") | ||||
|   if (!isVisitSpecificWebsite || isVisitSpecificWebsite.length === 0) { | ||||
|     return true | ||||
|   } | ||||
|   return isVisitSpecificWebsite === "true" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export const setIsVisitSpecificWebsite = async ( | ||||
|   isVisitSpecificWebsite: boolean | ||||
| ) => { | ||||
|   await storage.set("isVisitSpecificWebsite", isVisitSpecificWebsite.toString()) | ||||
| } | ||||
| 
 | ||||
| export const setIsSimpleInternetSearch = async ( | ||||
|   isSimpleInternetSearch: boolean | ||||
| ) => { | ||||
| @ -48,32 +63,37 @@ export const setTotalSearchResults = async (totalSearchResults: number) => { | ||||
| } | ||||
| 
 | ||||
| export const getSearchSettings = async () => { | ||||
|   const [isSimpleInternetSearch, searchProvider, totalSearchResult] = | ||||
|   const [isSimpleInternetSearch, searchProvider, totalSearchResult, visitSpecificWebsite] = | ||||
|     await Promise.all([ | ||||
|       getIsSimpleInternetSearch(), | ||||
|       getSearchProvider(), | ||||
|       totalSearchResults() | ||||
|       totalSearchResults(), | ||||
|       getIsVisitSpecificWebsite() | ||||
|     ]) | ||||
| 
 | ||||
|   return { | ||||
|     isSimpleInternetSearch, | ||||
|     searchProvider, | ||||
|     totalSearchResults: totalSearchResult | ||||
|     totalSearchResults: totalSearchResult, | ||||
|     visitSpecificWebsite | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export const setSearchSettings = async ({ | ||||
|   isSimpleInternetSearch, | ||||
|   searchProvider, | ||||
|   totalSearchResults | ||||
|   totalSearchResults, | ||||
|   visitSpecificWebsite | ||||
| }: { | ||||
|   isSimpleInternetSearch: boolean | ||||
|   searchProvider: string | ||||
|   totalSearchResults: number | ||||
|   visitSpecificWebsite: boolean | ||||
| }) => { | ||||
|   await Promise.all([ | ||||
|     setIsSimpleInternetSearch(isSimpleInternetSearch), | ||||
|     setSearchProvider(searchProvider), | ||||
|     setTotalSearchResults(totalSearchResults) | ||||
|     setTotalSearchResults(totalSearchResults), | ||||
|     setIsVisitSpecificWebsite(visitSpecificWebsite) | ||||
|   ]) | ||||
| } | ||||
|  | ||||
							
								
								
									
										44
									
								
								src/utils/rerank.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/utils/rerank.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| import type { Embeddings } from "@langchain/core/embeddings" | ||||
| import type { Document } from "@langchain/core/documents" | ||||
| import * as ml_distance from "ml-distance" | ||||
| 
 | ||||
| export const rerankDocs = async ({ | ||||
|   query, | ||||
|   docs, | ||||
|   embedding | ||||
| }: { | ||||
|   query: string | ||||
|   docs: Document[] | ||||
|   embedding: Embeddings | ||||
| }) => { | ||||
|   if (docs.length === 0) { | ||||
|     return docs | ||||
|   } | ||||
| 
 | ||||
|   const docsWithContent = docs.filter( | ||||
|     (doc) => doc.pageContent && doc.pageContent.length > 0 | ||||
|   ) | ||||
| 
 | ||||
|   const [docEmbeddings, queryEmbedding] = await Promise.all([ | ||||
|     embedding.embedDocuments(docsWithContent.map((doc) => doc.pageContent)), | ||||
|     embedding.embedQuery(query) | ||||
|   ]) | ||||
| 
 | ||||
|   const similarity = docEmbeddings.map((docEmbedding, i) => { | ||||
|     // perform cosine similarity between query and document
 | ||||
|     const sim = ml_distance.similarity.cosine(queryEmbedding, docEmbedding) | ||||
| 
 | ||||
|     return { | ||||
|       index: i, | ||||
|       similarity: sim | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
|   const sortedDocs = similarity | ||||
|     .sort((a, b) => b.similarity - a.similarity) | ||||
|     .filter((sim) => sim.similarity > 0.5) | ||||
|     .slice(0, 15) | ||||
|     .map((sim) => docsWithContent[sim.index]) | ||||
| 
 | ||||
|   return sortedDocs | ||||
| } | ||||
| @ -1,9 +1,10 @@ | ||||
| import { getWebSearchPrompt } from "~/services/ollama" | ||||
| import { webGoogleSearch } from "./search-engines/google" | ||||
| import { webDuckDuckGoSearch } from "./search-engines/duckduckgo" | ||||
| import { getSearchProvider } from "@/services/search" | ||||
| import { getIsVisitSpecificWebsite, getSearchProvider } from "@/services/search" | ||||
| import { webSogouSearch } from "./search-engines/sogou" | ||||
| import { webBraveSearch } from "./search-engines/brave" | ||||
| import { getWebsiteFromQuery, processSingleWebsite } from "./website" | ||||
| 
 | ||||
| const getHostName = (url: string) => { | ||||
|   try { | ||||
| @ -29,8 +30,27 @@ const searchWeb = (provider: string, query: string) => { | ||||
| 
 | ||||
| export const getSystemPromptForWeb = async (query: string) => { | ||||
|   try { | ||||
|     const searchProvider = await getSearchProvider() | ||||
|     const search = await searchWeb(searchProvider, query) | ||||
| 
 | ||||
|     const websiteVisit = getWebsiteFromQuery(query) | ||||
|     let search: { | ||||
|       url: any; | ||||
|       content: string; | ||||
|     }[] = [] | ||||
| 
 | ||||
|     const isVisitSpecificWebsite = await getIsVisitSpecificWebsite() | ||||
| 
 | ||||
|     if (isVisitSpecificWebsite && websiteVisit.hasUrl) { | ||||
| 
 | ||||
|       const url = websiteVisit.url | ||||
|       const queryWithoutUrl = websiteVisit.queryWithouUrls | ||||
|       search = await processSingleWebsite(url, queryWithoutUrl) | ||||
| 
 | ||||
|     } else { | ||||
|       const searchProvider = await getSearchProvider() | ||||
|       search = await searchWeb(searchProvider, query) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     const search_results = search | ||||
|       .map( | ||||
|  | ||||
							
								
								
									
										76
									
								
								src/web/website/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/web/website/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| import { cleanUrl } from "@/libs/clean-url" | ||||
| import { PageAssistHtmlLoader } from "@/loader/html" | ||||
| import { defaultEmbeddingChunkOverlap, defaultEmbeddingChunkSize, defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama" | ||||
| import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama" | ||||
| import { RecursiveCharacterTextSplitter } from "langchain/text_splitter" | ||||
| import { MemoryVectorStore } from "langchain/vectorstores/memory" | ||||
| 
 | ||||
| export const processSingleWebsite = async (url: string, query: string) => { | ||||
|     const loader = new PageAssistHtmlLoader({ | ||||
|         html: "", | ||||
|         url | ||||
|     }) | ||||
|     const docs = await loader.loadByURL() | ||||
| 
 | ||||
|     const ollamaUrl = await getOllamaURL() | ||||
| 
 | ||||
|     const embeddingModle = await defaultEmbeddingModelForRag() | ||||
|     const ollamaEmbedding = new OllamaEmbeddings({ | ||||
|         model: embeddingModle || "", | ||||
|         baseUrl: cleanUrl(ollamaUrl) | ||||
|     }) | ||||
| 
 | ||||
|     const chunkSize = await defaultEmbeddingChunkSize() | ||||
|     const chunkOverlap = await defaultEmbeddingChunkOverlap() | ||||
|     const textSplitter = new RecursiveCharacterTextSplitter({ | ||||
|         chunkSize, | ||||
|         chunkOverlap | ||||
|     }) | ||||
| 
 | ||||
|     const chunks = await textSplitter.splitDocuments(docs) | ||||
| 
 | ||||
|     const store = new MemoryVectorStore(ollamaEmbedding) | ||||
| 
 | ||||
|     await store.addDocuments(chunks) | ||||
| 
 | ||||
|     const resultsWithEmbeddings = await store.similaritySearch(query, 4) | ||||
| 
 | ||||
|     const searchResult = resultsWithEmbeddings.map((result) => { | ||||
|         return { | ||||
|             url: result.metadata.url, | ||||
|             content: result.pageContent | ||||
|         } | ||||
|     }) | ||||
| 
 | ||||
|     return searchResult | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export const getWebsiteFromQuery = (query: string): { | ||||
|     queryWithouUrls: string, | ||||
|     url: string, | ||||
|     hasUrl: boolean | ||||
| } => { | ||||
| 
 | ||||
|     const urlRegex = /https?:\/\/[^\s]+/g | ||||
| 
 | ||||
|     const urls = query.match(urlRegex) | ||||
| 
 | ||||
|     if (!urls) { | ||||
|         return { | ||||
|             queryWithouUrls: query, | ||||
|             url: "", | ||||
|             hasUrl: false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const url = urls[0] | ||||
| 
 | ||||
|     const queryWithouUrls = query.replace(url, "") | ||||
| 
 | ||||
|     return { | ||||
|         queryWithouUrls, | ||||
|         url, | ||||
|         hasUrl: true | ||||
|     } | ||||
| } | ||||
| @ -48,7 +48,7 @@ export default defineConfig({ | ||||
|   outDir: "build", | ||||
| 
 | ||||
|   manifest: { | ||||
|     version: "1.1.12", | ||||
|     version: "1.1.13", | ||||
|     name: | ||||
|       process.env.TARGET === "firefox" | ||||
|         ? "Page Assist - A Web UI for Local AI Models" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user