commit
ba654f9574
@ -21,6 +21,7 @@
|
|||||||
"@langchain/community": "^0.0.41",
|
"@langchain/community": "^0.0.41",
|
||||||
"@mantine/form": "^7.5.0",
|
"@mantine/form": "^7.5.0",
|
||||||
"@mantine/hooks": "^7.5.3",
|
"@mantine/hooks": "^7.5.3",
|
||||||
|
"@mozilla/readability": "^0.5.0",
|
||||||
"@plasmohq/storage": "^1.9.0",
|
"@plasmohq/storage": "^1.9.0",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
@ -37,6 +37,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "Total Search Results",
|
"label": "Total Search Results",
|
||||||
"placeholder": "Enter Total Search Results"
|
"placeholder": "Enter Total Search Results"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "Visit the website mentioned in the message"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"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": {
|
"totalSearchResults": {
|
||||||
"label": "Résultats de la recherche totaux",
|
"label": "Résultats de la recherche totaux",
|
||||||
"placeholder": "Entrez les 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": {
|
"system": {
|
||||||
|
@ -37,6 +37,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "Risultati della ricerca",
|
"label": "Risultati della ricerca",
|
||||||
"placeholder": "Inserisci il totale delle ricerche"
|
"placeholder": "Inserisci il totale delle ricerche"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "Visita il sito web menzionato nel messaggio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "合計検索結果",
|
"label": "合計検索結果",
|
||||||
"placeholder": "合計検索結果を入力する"
|
"placeholder": "合計検索結果を入力する"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "メッセージに記載されたウェブサイトを訪問してください"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "ആകെ തിരച്ചിൽ ഫലങ്ങൾ",
|
"label": "ആകെ തിരച്ചിൽ ഫലങ്ങൾ",
|
||||||
"placeholder": "ആകെ തിരച്ചിൽ ഫലങ്ങളുടെ എണ്ണം നൽകുക"
|
"placeholder": "ആകെ തിരച്ചിൽ ഫലങ്ങളുടെ എണ്ണം നൽകുക"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "സന്ദേശത്തിൽ പറയുന്ന വെബ്സൈറ്റ് സന്ദർശിക്കുക."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -37,6 +37,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "Resultados de Pesquisa Totais",
|
"label": "Resultados de Pesquisa Totais",
|
||||||
"placeholder": "Insira Resultados de Pesquisa Totais"
|
"placeholder": "Insira Resultados de Pesquisa Totais"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "Visite o site mencionado na mensagem."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -37,6 +37,9 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "Общее количество результатов поиска",
|
"label": "Общее количество результатов поиска",
|
||||||
"placeholder": "Введите общее количество результатов поиска"
|
"placeholder": "Введите общее количество результатов поиска"
|
||||||
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "Посетите веб-сайт, указанный в сообщении."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
|
@ -40,7 +40,10 @@
|
|||||||
"totalSearchResults": {
|
"totalSearchResults": {
|
||||||
"label": "总搜索结果",
|
"label": "总搜索结果",
|
||||||
"placeholder": "输入总搜索结果"
|
"placeholder": "输入总搜索结果"
|
||||||
}
|
},
|
||||||
|
"visitSpecificWebsite": {
|
||||||
|
"label": "访问消息中提到的网站。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"heading": "系统设置",
|
"heading": "系统设置",
|
||||||
|
@ -14,6 +14,7 @@ import { useTranslation } from "react-i18next"
|
|||||||
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
|
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
|
||||||
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
|
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
|
||||||
import { PiGlobe } from "react-icons/pi"
|
import { PiGlobe } from "react-icons/pi"
|
||||||
|
import { extractReadabilityContent } from "@/parser/reader"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dropedFile: File | undefined
|
dropedFile: File | undefined
|
||||||
|
@ -13,7 +13,8 @@ export const SearchModeSettings = () => {
|
|||||||
initialValues: {
|
initialValues: {
|
||||||
isSimpleInternetSearch: false,
|
isSimpleInternetSearch: false,
|
||||||
searchProvider: "",
|
searchProvider: "",
|
||||||
totalSearchResults: 0
|
totalSearchResults: 0,
|
||||||
|
visitSpecificWebsite: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ export const SearchModeSettings = () => {
|
|||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<Switch
|
<Switch
|
||||||
className="mt-4 sm:mt-0"
|
className="mt-4 sm:mt-0"
|
||||||
{...form.getInputProps("isSimpleInternetSearch", {
|
{...form.getInputProps("isSimpleInternetSearch", {
|
||||||
type: "checkbox"
|
type: "checkbox"
|
||||||
})}
|
})}
|
||||||
@ -89,6 +90,20 @@ export const SearchModeSettings = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="flex justify-end">
|
||||||
<SaveButton btnType="submit" />
|
<SaveButton btnType="submit" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,87 +1,106 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
import { useMessageOption } from "./useMessageOption"
|
||||||
|
|
||||||
export const useScrollAnchor = () => {
|
export const useScrollAnchor = () => {
|
||||||
const messagesRef = useRef<HTMLDivElement>(null)
|
const { isProcessing, messages } = useMessageOption()
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
|
||||||
const visibilityRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
|
const [isAtTop, setIsAtTop] = useState(false)
|
||||||
const [isAtBottom, setIsAtBottom] = useState(true)
|
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(() => {
|
const scrollToBottom = useCallback(() => {
|
||||||
if (messagesRef.current) {
|
isAutoScrolling.current = true
|
||||||
messagesRef.current.scrollIntoView({
|
|
||||||
block: "end",
|
setTimeout(() => {
|
||||||
behavior: "smooth"
|
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 {
|
return {
|
||||||
messagesRef,
|
messagesStartRef,
|
||||||
scrollRef,
|
messagesEndRef,
|
||||||
visibilityRef,
|
containerRef,
|
||||||
scrollToBottom,
|
isAtTop,
|
||||||
isAtBottom,
|
isAtBottom,
|
||||||
isVisible
|
userScrolled,
|
||||||
|
isOverflowing,
|
||||||
|
scrollToTop,
|
||||||
|
scrollToBottom,
|
||||||
|
setIsAtBottom
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import { ml } from "./lang/ml";
|
|||||||
import { zh } from "./lang/zh";
|
import { zh } from "./lang/zh";
|
||||||
import { ja } from "./lang/ja";
|
import { ja } from "./lang/ja";
|
||||||
import { it } from "./lang/it";
|
import { it } from "./lang/it";
|
||||||
|
import { es } from "./lang/es";
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
@ -16,6 +17,7 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
resources: {
|
resources: {
|
||||||
en: en,
|
en: en,
|
||||||
|
es: es,
|
||||||
fr: fr,
|
fr: fr,
|
||||||
"it": it,
|
"it": it,
|
||||||
ml: ml,
|
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",
|
label: "English",
|
||||||
value: "en"
|
value: "en"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Español",
|
||||||
|
value: "es"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Français",
|
label: "Français",
|
||||||
value: "fr"
|
value: "fr"
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { BaseDocumentLoader } from "langchain/document_loaders/base"
|
import { BaseDocumentLoader } from "langchain/document_loaders/base"
|
||||||
import { Document } from "@langchain/core/documents"
|
import { Document } from "@langchain/core/documents"
|
||||||
import { compile } from "html-to-text"
|
|
||||||
import { urlRewriteRuntime } from "~/libs/runtime"
|
import { urlRewriteRuntime } from "~/libs/runtime"
|
||||||
import { YtTranscript } from "yt-transcript"
|
import { YtTranscript } from "yt-transcript"
|
||||||
import { isWikipedia, parseWikipedia } from "@/parser/wiki"
|
import { isWikipedia, parseWikipedia } from "@/parser/wiki"
|
||||||
|
import { extractReadabilityContent } from "@/parser/reader"
|
||||||
|
|
||||||
const YT_REGEX =
|
const YT_REGEX =
|
||||||
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9_-]+)/
|
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9_-]+)/
|
||||||
@ -24,8 +24,7 @@ export interface WebLoaderParams {
|
|||||||
|
|
||||||
export class PageAssistHtmlLoader
|
export class PageAssistHtmlLoader
|
||||||
extends BaseDocumentLoader
|
extends BaseDocumentLoader
|
||||||
implements WebLoaderParams
|
implements WebLoaderParams {
|
||||||
{
|
|
||||||
html: string
|
html: string
|
||||||
url: string
|
url: string
|
||||||
|
|
||||||
@ -52,30 +51,14 @@ export class PageAssistHtmlLoader
|
|||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
source: this.url,
|
source: this.url,
|
||||||
|
url: this.url,
|
||||||
audio: { chunks: transcript }
|
audio: { chunks: transcript }
|
||||||
},
|
},
|
||||||
pageContent: text
|
pageContent: text
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
const metadata = { source: this.url, url: this.url, }
|
||||||
// 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 }
|
|
||||||
return [new Document({ pageContent: this.html, metadata })]
|
return [new Document({ pageContent: this.html, metadata })]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +78,7 @@ export class PageAssistHtmlLoader
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
|
url: this.url,
|
||||||
source: this.url,
|
source: this.url,
|
||||||
audio: { chunks: transcript }
|
audio: { chunks: transcript }
|
||||||
},
|
},
|
||||||
@ -103,22 +87,15 @@ export class PageAssistHtmlLoader
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
await urlRewriteRuntime(this.url, "web")
|
await urlRewriteRuntime(this.url, "web")
|
||||||
const fetchHTML = await fetch(this.url)
|
let text = "";
|
||||||
let html = await fetchHTML.text()
|
|
||||||
|
|
||||||
if (isWikipedia(this.url)) {
|
if (isWikipedia(this.url)) {
|
||||||
console.log("Wikipedia URL detected")
|
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 }
|
const metadata = { url: this.url }
|
||||||
return [new Document({ pageContent: text, metadata })]
|
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 * as cheerio from "cheerio"
|
||||||
|
import { defaultExtractContent } from "./default"
|
||||||
|
|
||||||
export const isWikipedia = (url: string) => {
|
export const isWikipedia = (url: string) => {
|
||||||
const WIKI_REGEX = /wikipedia\.org\/wiki\//g
|
const WIKI_REGEX = /wikipedia\.org\/wiki\//g
|
||||||
@ -24,5 +25,5 @@ export const parseWikipedia = (html: string) => {
|
|||||||
content?.find("div.toc")?.remove()
|
content?.find("div.toc")?.remove()
|
||||||
const newHtml = content?.html()
|
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 () => {
|
export const copilotResumeLastChat = async () => {
|
||||||
return await storage.get<boolean>("copilotResumeLastChat")
|
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"
|
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 (
|
export const setIsSimpleInternetSearch = async (
|
||||||
isSimpleInternetSearch: boolean
|
isSimpleInternetSearch: boolean
|
||||||
) => {
|
) => {
|
||||||
@ -48,32 +63,37 @@ export const setTotalSearchResults = async (totalSearchResults: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getSearchSettings = async () => {
|
export const getSearchSettings = async () => {
|
||||||
const [isSimpleInternetSearch, searchProvider, totalSearchResult] =
|
const [isSimpleInternetSearch, searchProvider, totalSearchResult, visitSpecificWebsite] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getIsSimpleInternetSearch(),
|
getIsSimpleInternetSearch(),
|
||||||
getSearchProvider(),
|
getSearchProvider(),
|
||||||
totalSearchResults()
|
totalSearchResults(),
|
||||||
|
getIsVisitSpecificWebsite()
|
||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSimpleInternetSearch,
|
isSimpleInternetSearch,
|
||||||
searchProvider,
|
searchProvider,
|
||||||
totalSearchResults: totalSearchResult
|
totalSearchResults: totalSearchResult,
|
||||||
|
visitSpecificWebsite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setSearchSettings = async ({
|
export const setSearchSettings = async ({
|
||||||
isSimpleInternetSearch,
|
isSimpleInternetSearch,
|
||||||
searchProvider,
|
searchProvider,
|
||||||
totalSearchResults
|
totalSearchResults,
|
||||||
|
visitSpecificWebsite
|
||||||
}: {
|
}: {
|
||||||
isSimpleInternetSearch: boolean
|
isSimpleInternetSearch: boolean
|
||||||
searchProvider: string
|
searchProvider: string
|
||||||
totalSearchResults: number
|
totalSearchResults: number
|
||||||
|
visitSpecificWebsite: boolean
|
||||||
}) => {
|
}) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
setIsSimpleInternetSearch(isSimpleInternetSearch),
|
setIsSimpleInternetSearch(isSimpleInternetSearch),
|
||||||
setSearchProvider(searchProvider),
|
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 { getWebSearchPrompt } from "~/services/ollama"
|
||||||
import { webGoogleSearch } from "./search-engines/google"
|
import { webGoogleSearch } from "./search-engines/google"
|
||||||
import { webDuckDuckGoSearch } from "./search-engines/duckduckgo"
|
import { webDuckDuckGoSearch } from "./search-engines/duckduckgo"
|
||||||
import { getSearchProvider } from "@/services/search"
|
import { getIsVisitSpecificWebsite, getSearchProvider } from "@/services/search"
|
||||||
import { webSogouSearch } from "./search-engines/sogou"
|
import { webSogouSearch } from "./search-engines/sogou"
|
||||||
import { webBraveSearch } from "./search-engines/brave"
|
import { webBraveSearch } from "./search-engines/brave"
|
||||||
|
import { getWebsiteFromQuery, processSingleWebsite } from "./website"
|
||||||
|
|
||||||
const getHostName = (url: string) => {
|
const getHostName = (url: string) => {
|
||||||
try {
|
try {
|
||||||
@ -29,8 +30,27 @@ const searchWeb = (provider: string, query: string) => {
|
|||||||
|
|
||||||
export const getSystemPromptForWeb = async (query: string) => {
|
export const getSystemPromptForWeb = async (query: string) => {
|
||||||
try {
|
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
|
const search_results = search
|
||||||
.map(
|
.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",
|
outDir: "build",
|
||||||
|
|
||||||
manifest: {
|
manifest: {
|
||||||
version: "1.1.12",
|
version: "1.1.13",
|
||||||
name:
|
name:
|
||||||
process.env.TARGET === "firefox"
|
process.env.TARGET === "firefox"
|
||||||
? "Page Assist - A Web UI for Local AI Models"
|
? "Page Assist - A Web UI for Local AI Models"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user