Merge branch 'next' into add-pt-locale

This commit is contained in:
Muhammed Nazeem 2024-06-01 14:17:10 +05:30 committed by GitHub
commit b3d9f3ca92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1418 additions and 7539 deletions

View File

@ -19,10 +19,10 @@ Thank you for your interest in contributing to Page Assist! We welcome contribut
3. **Install dependencies**
Page Assist uses [Yarn](https://yarnpkg.com/) for dependency management. Install the required dependencies by running the following command in the project root directory:
Page Assist uses [Bun](https://bun.sh/) for dependency management. Install the required dependencies by running the following command in the project root directory:
```
yarn install
bun install
```
4. **Start the development server**
@ -30,10 +30,16 @@ Thank you for your interest in contributing to Page Assist! We welcome contribut
To run the extension in development mode, use the following command:
```
yarn dev
bun dev
```
This will open a browser window with the extension loaded.
This will open a chrome browser window with the extension loaded.
for firefox:
```
bun dev:firefox
```
5. **Install Ollama locally**

View File

@ -30,13 +30,6 @@ Checkout the Demo (v1.0.0):
want more features? Create an issue and let me know.
<!-- ## Installation
### Chrome Web Store
You can install the extension from the [Chrome Web Store](https://chromewebstore.google.com/detail/page-assist-a-web-ui-for/jfgfiigpkhlkbnfnbobbkinehhfdhndo)
Note: You can install the extension on any Chromium-based browser. It is not limited to Chrome. -->
### Manual Installation
@ -125,6 +118,8 @@ This will start a development server and watch for changes in the source files.
| Opera | ❌ | ❌ | ✅ |
| Arc | ❌ | ❌ | ✅ |
## Local AI Provider
- [Ollama](https://github.com/ollama/ollama) (Currently the only supported provider. More providers will be added in the future.)
@ -132,8 +127,8 @@ This will start a development server and watch for changes in the source files.
## Roadmap
- [X] Firefox Support
- [ ] Code Completion support for Web based IDEs (like Colab, Jupyter, etc.)
- [ ] More Local AI Providers
- [ ] More Features
- [ ] More Customization Options
- [ ] Better UI/UX

View File

@ -8,7 +8,7 @@
"dev": "cross-env TARGET=chrome wxt",
"dev:firefox": "cross-env TARGET=firefox wxt -b firefox",
"build": "cross-env TARGET=chrome wxt build",
"build:firefox": "cross-env TARGET=chrome cross-env TARGET=firefox wxt build -b firefox",
"build:firefox": "cross-env TARGET=firefox wxt build -b firefox",
"zip": "cross-env TARGET=chrome wxt zip",
"zip:firefox": "cross-env TARGET=firefox wxt zip -b firefox",
"compile": "tsc --noEmit",

View File

@ -0,0 +1,88 @@
{
"pageAssist": "Page Assist",
"selectAModel": "Sélectionnez un modèle",
"save": "Sauvegarder",
"saved": "Enregistrée",
"cancel": "Annuler",
"retry": "Recommencez",
"share": {
"tooltip": {
"share": "Partager"
},
"modal": {
"title": "Partagez le lien vers le chat"
},
"form": {
"defaultValue": {
"name": "Anonyme",
"title": "Chat sans titre"
},
"title": {
"label": "Titre de chat",
"placeholder": "Entrez le titre du chat",
"required": "Le titre de chat est requis"
},
"name": {
"label": "Votre nom",
"placeholder": "Entrez votre nome",
"required": "Votre nom est requis"
},
"btn": {
"save": "Générer un lien",
"saving": "Génération de lien..."
}
},
"notification": {
"successGenerate": "Lien copié dans le presse-papiers",
"failGenerate": "Échec de la génération de lien"
}
},
"copyToClipboard": "Copier dans le presse-papier",
"webSearch": "Recherche sur le Web",
"regenerate": "Régénérer",
"edit": "Modifier",
"saveAndSubmit": "Enregistrer et soumettre",
"editMessage": {
"placeholder": "Tapez un message..."
},
"submit": "Soumettre",
"noData": "Pas de données",
"noHistory": "Pas d'historique de chat",
"chatWithCurrentPage": "Discuter avec la page actuelle",
"beta": "Bêta",
"tts": "Synthèse vocale",
"currentChatModelSettings":"Paramètres actuels du modèle de chat",
"modelSettings": {
"label": "Paramètres du modèle",
"description": "Définissez les options de modèle globale pour tous les chats",
"form": {
"keepAlive": {
"label": "Maintenir en mémoire",
"help": "contrôle combien de temps le modèle restera chargé en mémoire après la demande (par défaut: 5m)",
"placeholder": "Entrer la durée du maintien en mémoire (p. ex., 5 m, 10 m, 1 h)"
},
"temperature": {
"label": "Température",
"placeholder": "Entrez la valeur de la température (par exemple 0,7, 1,0)"
},
"numCtx": {
"label": "Nombre de contextes",
"placeholder": "Entrez la valeur du nombre de contextes (par défaut: 2048)"
},
"seed": {
"label": "Graine",
"placeholder": "Entrez la valeur des semences (par exemple 1234)",
"help": "Reproductibilité de la sortie du modèle"
},
"topK": {
"label": "Top K",
"placeholder": "Entrez la valeur Top K (par exemple 40, 100)"
},
"topP": {
"label": "Top P",
"placeholder": "Entrez la valeur Top P (par exemple 0,9, 0,95)"
}
},
"advanced": "Plus de paramètres du modèle"
}
}

View File

@ -0,0 +1,42 @@
{
"addBtn": "Ajouter de nouvelles connaissances",
"columns": {
"title": "Titre",
"status": "Statut",
"embeddings": "Modèle d'embedding",
"createdAt": "Créé à",
"action": "actions"
},
"expandedColumns": {
"name": "Nom"
},
"tooltip": {
"delete": "Supprimer"
},
"confirm": {
"delete": "Êtes-vous sûr de vouloir supprimer ces connaissances ?"
},
"deleteSuccess": "Connaissances supprimées avec succès",
"status": {
"pending": "En attente",
"finished": "Terminé",
"processing": "Traitement"
},
"addKnowledge": "Ajouter des connaissances",
"form": {
"title": {
"label": "Titre de la connaissance",
"placeholder": "Entrez le titre de la connaissances",
"required": "Le titre de la connaissance est requis"
},
"uploadFile": {
"label": "Téléverser un fichier",
"uploadText": "Faites glisser et déposez un fichier ici ou cliquez pour téléverser",
"uploadHint": "Types de fichiers pris en charge: .pdf, .csv, .txt, .md, .docx",
"required": "Le fichier est requis"
},
"submit": "Soumettre",
"success": "Les connaissances ont ajouté avec succès"
},
"noEmbeddingModel": "Veuillez d'abord ajouter un modèle d'embedding depuis la page des paramètres de Ollama"
}

View File

@ -0,0 +1,12 @@
{
"newChat": "Nouveau chat",
"selectAPrompt": "Sélectionnez un prompt",
"githubRepository": "Référentiel GitHub",
"settings": "Paramètres",
"sidebarTitle": "Historique de chat",
"error": "Erreur",
"somethingWentWrong": "Quelque chose s'est mal passé",
"validationSelectModel": "Veuillez sélectionner un modèle pour continuer",
"deleteHistoryConfirmation": "Êtes-vous sûr de vouloir supprimer cette historique ?",
"editHistoryTitle": "Entrez un nouveau titre"
}

View File

@ -0,0 +1,29 @@
{
"ollamaState": {
"searching": "Searching for Your Ollama 🦙",
"running": "Ollama is running 🦙",
"notRunning": "Unable to connect to Ollama 🦙",
"connectionError": "It seems like you are having a connection error. Please refer to this <anchor>documentation</anchor> for troubleshooting."
},
"formError": {
"noModel": "Please select a model",
"noEmbeddingModel": "Please set an embedding model on the Settings > Ollama page"
},
"form": {
"textarea": {
"placeholder": "Type a message..."
},
"webSearch": {
"on": "On",
"off": "Off"
}
},
"tooltip": {
"searchInternet": "Rechercher Internet",
"speechToText": "Parole en texte",
"uploadImage": "Téléverser une image",
"stopStreaming": "Arrêtez la diffusion",
"knowledge": "Connaissance"
},
"sendWhenEnter": "Envoyer en appuyant sur Entrée"
}

View File

@ -0,0 +1,286 @@
{
"generalSettings": {
"title": "Réglages généraux",
"settings": {
"heading": "Paramètres d'interface utilisateur Web",
"speechRecognitionLang": {
"label": "Langue de reconnaissance vocale",
"placeholder": "Sélectionnez une langue"
},
"language": {
"label": "Langue",
"placeholder": "Sélectionnez une langue"
},
"darkMode": {
"label": "Change le thème",
"options": {
"light": "Clair",
"dark": "Sombre"
}
},
"copilotResumeLastChat": {
"label": "Reprendre la dernière conversation lors de l'ouverture du sidepanel (Copilot)"
},
"hideCurrentChatModelSettings": {
"label": "Masquer les paramètres actuels du modèle de chat"
}
},
"webSearch": {
"heading": "Gérer la recherche Web",
"searchMode": {
"label": "Effectuer une simple recherche sur Internet"
},
"provider": {
"label": "Moteur de recherche",
"placeholder": "Sélectionnez un moteur de recherche"
},
"totalSearchResults": {
"label": "Résultats de la recherche totaux",
"placeholder": "Entrez les résultats de la recherche totaux"
}
},
"system": {
"heading": "Les paramètres du système",
"deleteChatHistory": {
"label": "Supprimer l'historique du chat",
"button": "Supprimer",
"confirm": "Êtes-vous sûr de vouloir supprimer l'historique de votre chat? Cette action ne peut pas être annulée."
},
"export": {
"label": "Exporter l'historique du chat, la base de connaissances et les invites",
"button": "Exporter des données",
"success": "Succès de l'exportation"
},
"import": {
"label": "Importer l'historique du chat, la base de connaissances et les invites",
"button": "Importer des données",
"success": "Succès d'importation",
"error": "Erreur d'importation"
}
},
"tts": {
"heading": "Paramètres de synthèse vocale",
"ttsEnabled": {
"label": "Activer la synthèse vocale"
},
"ttsProvider": {
"label": "Fournisseur de synthèse vocale",
"placeholder": "Sélectionnez un fournisseur"
},
"ttsVoice": {
"label": "Voix de synthèse vocale",
"placeholder": "Sélectionnez une voix"
},
"ssmlEnabled": {
"label": "Activer SSML (langage de balisage de synthèse vocale)"
}
}
},
"manageModels": {
"title": "Gérer les modèles",
"addBtn": "Ajouter un nouveau modèle",
"columns": {
"name": "Nom",
"digest": "Digérer",
"modifiedAt": "Modifié à",
"size": "Taille",
"actions": "Actions"
},
"expandedColumns": {
"parentModel": "Modèle parent",
"format": "Format",
"family": "Famille",
"parameterSize": "Taille du paramètre",
"quantizationLevel": "Niveau de quantification"
},
"tooltip": {
"delete": "Supprimer le modèle",
"repull": "Modèle de ré-échoue"
},
"confirm": {
"delete": "Êtes-vous sûr de vouloir supprimer ce modèle?",
"repull": "Êtes-vous sûr de vouloir rétracter ce modèle?"
},
"modal": {
"title": "Ajouter un nouveau modèle",
"placeholder": "Entrez le nom du modèle",
"pull": "Modèle de traction"
},
"notification": {
"pullModel": "Modèle de traction",
"pullModelDescription": "Tirling {{ModelName}} modèle. Pour plus de détails, vérifiez l'icône d'extension.",
"success": "Succès",
"error": "Erreur",
"successDescription": "A réussi à tirer le modèle",
"successDeleteDescription": "Supprimé avec succès le modèle",
"someError": "Quelque chose s'est mal passé.Veuillez réessayer plus tard"
}
},
"managePrompts": {
"title": "Gérer les prompts",
"addBtn": "Ajouter un nouveau prompt",
"option1": "Normale",
"option2": "RAG",
"questionPrompt": "Prompt de question",
"columns": {
"title": "Titre",
"prompt": "Prompt",
"type": "Type de prompt",
"actions": "Actions"
},
"systemPrompt": "Prompt système",
"quickPrompt": "Prompt rapide",
"tooltip": {
"delete": "Supprimer le prompt",
"edit": "Modifier le prompt"
},
"confirm": {
"delete": "Êtes-vous sûr de vouloir supprimer cette invite ? Cette action ne peut pas être annulée."
},
"modal": {
"addTitle": "Ajouter un nouveau prompt",
"editTitle": "Modifier le prompt"
},
"form": {
"title": {
"label": "Titre",
"placeholder": "Mon super prompt",
"required": "Veuillez saisir un titre"
},
"prompt": {
"label": "Prompt",
"placeholder": "Entrer Prompt",
"required": "Veuillez entrer un prompt",
"help": "Vous pouvez utiliser {key} comme variable dans votre prompt."
},
"isSystem": {
"label": "Est un prompt système"
},
"btnSave": {
"saving": "Ajout de Prompt...",
"save": "Ajouter un prompt"
},
"btnEdit": {
"saving": "Mise à jour du Prompt...",
"save": "Modifier le prompt"
}
},
"notification": {
"addSuccess": "Prompt ajouté",
"addSuccessDesc": "Le prompt a été ajoutée avec succès",
"error": "Erreur",
"someError": "Quelque chose s'est mal passé. Veuillez réessayer plus tard",
"updatedSuccess": "Prompt mise à jour",
"updatedSuccessDesc": "Le prompt a été mis à jour avec succès",
"deletedSuccess": "Prompt supprimé",
"deletedSuccessDesc": "Le prompt a été supprimé avec succès"
}
},
"manageShare": {
"title": "Gérer le partage",
"heading": "Configurer l'URL de partage de la page",
"form": {
"url": {
"label": "URL de partage de page",
"placeholder": "Entrez l'URL de partage de la page",
"required": "Veuillez saisir URL de partage de votre page!",
"help": "Pour des raisons de confidentialité, vous pouvez auto-héberger le partage de la page et fournir l'URL ici.<anchor>En savoir plus</anchor>."
}
},
"webshare": {
"heading": "Partage Web",
"columns": {
"title": "Titre",
"url": "URL",
"actions": "Actions"
},
"tooltip": {
"delete": "Supprimer le partage"
},
"confirm": {
"delete": "Êtes-vous sûr de vouloir supprimer ce partage ? Cette action ne peut pas être annulée."
},
"label": "Gérer le partage de pages",
"description": "Activer ou désactiver la fonction de partage de page"
},
"notification": {
"pageShareSuccess": "URL de partage de page mise à jour avec succès",
"someError": "Quelque chose a mal tourné. Veuillez réessayer plus tard",
"webShareDeleteSuccess": "Partage Web supprimé avec succès"
}
},
"ollamaSettings": {
"title": "Réglages de Ollama",
"heading": "Configurer Ollama",
"settings": {
"ollamaUrl": {
"label": "Url de Ollama",
"placeholder": "Entrer l'url de Ollama"
},
"ragSettings": {
"label": "Paramètres de RAG",
"model": {
"label": "Modèle d'embedding",
"required": "Veuillez sélectionner un modèle",
"help": "Fortement recommandé d'utiliser des modèles d'embedding comme «momic-embed-text».",
"placeholder": "Sélectionnez un modèle"
},
"chunkSize": {
"label": "Taille",
"placeholder": "Entrez la taille du morceau",
"required": "Veuillez saisir une taille"
},
"chunkOverlap": {
"label": "Chevauchement",
"placeholder": "Entrez le chevauchement des morceaux",
"required": "Veuillez saisir un chevauchement"
}
},
"prompt": {
"label": "Configure RAG Prompt",
"option1": "Normal",
"option2": "Web",
"alert": "La configuration du prompt système ici est déconseillée. Veuillez utiliser la section Gérer les prompts pour ajouter...",
"systemPrompt": "Prompt système",
"systemPromptPlaceholder": "Entrez le prompt système",
"webSearchPrompt": "Prompt de recherche Web",
"webSearchPromptHelp": "Ne supprimez pas `{search_results}` du prompt.",
"webSearchPromptError": "Veuillez saisir un prompt de recherche Web",
"webSearchPromptPlaceholder": "Entrez le prompt de recherche Web",
"webSearchFollowUpPrompt": "Prompt de suivi de recherche Web",
"webSearchFollowUpPromptHelp": "Ne supprimez pas `{chat_history}` et `{question}` du prompt.",
"webSearchFollowUpPromptError": "Veuillez saisir votre prompt de suivi de recherche Web!",
"webSearchFollowUpPromptPlaceholder": "Votre prompt de suivi de recherche Web"
},
"advanced": {
"label": "Configuration avancée de l'URL de Ollama",
"urlRewriteEnabled": {
"label": "Activer ou désactiver l'URL d'origine personnalisée"
},
"rewriteUrl": {
"label": "URL d'origine personnalisée",
"placeholder": "Entrez l'URL d'origine personnalisée"
},
"help": "Si vous avez des problèmes de connexion avec OLLAMA sur Page Assist, vous pouvez configurer une URL d'origine personnalisée. Pour en savoir plus sur la configuration, <anchor>cliquez ici</anchor>."
}
}
},
"manageSearch": {
"title": "Gérer la recherche Web",
"heading": "Configurer la recherche Web"
},
"about": {
"title": "À propos",
"heading": "À propos",
"chromeVersion": "Version de Page Assist",
"ollamaVersion": "Version de Ollama",
"support": "Vous pouvez supporter le projet Page Assist en donnant ou parrainant via les plateformes suivantes:",
"koFi": "Supporter sur ko-fi",
"githubSponsor": "Sponsoriser sur github",
"githubRepo": "Référentiel GitHub"
},
"manageKnowledge": {
"title": "Gérer les connaissances",
"heading": "Configurer la base de connaissances"
}
}

View File

@ -0,0 +1,7 @@
{
"tooltip": {
"embed": "Cela peut prendre quelques minutes pour intégrer la page. S'il vous plaît, patientez...",
"clear": "Effacer l'historique du chat",
"history": "Historique du chat"
}
}

View File

@ -0,0 +1,88 @@
{
"pageAssist": "Page Assist",
"selectAModel": "Seleziona un Modello",
"save": "Salva",
"saved": "Salvato",
"cancel": "Annulla",
"retry": "Riprova",
"share": {
"tooltip": {
"share": "Condividi"
},
"modal": {
"title": "Condividi Collegamento alla Chat"
},
"form": {
"defaultValue": {
"name": "Anonimo",
"title": "Chat Senza Titolo"
},
"title": {
"label": "Titolo della Chat",
"placeholder": "Inserisci il Titolo della Chat",
"required": "Titolo della Chat obbligatorio"
},
"name": {
"label": "Il tuo Nome",
"placeholder": "Inserisci il tuo Nome",
"required": "Nome obbligatorio"
},
"btn": {
"save": "Genera Link",
"saving": "Sto generando il Link..."
}
},
"notification": {
"successGenerate": "Link copiato negli appunti",
"failGenerate": "Impossibile generare il link"
}
},
"copyToClipboard": "Copia negli Appunti",
"webSearch": "Ricerca nel Web",
"regenerate": "Rigenera",
"edit": "Modifica",
"saveAndSubmit": "Salva e Invia",
"editMessage": {
"placeholder": "Scrivi un messaggio..."
},
"submit": "Invia",
"noData": "Nessun Dato",
"noHistory": "Nessuna Cronologia Chat",
"chatWithCurrentPage": "Chatta con la Pagina Corrente",
"beta": "Beta",
"tts": "Leggi ad Alta Voce",
"currentChatModelSettings": "Impostazioni del Modello Corrente",
"modelSettings": {
"label": "Impostazioni del Modello",
"description": "Imposta le opzioni del modello globalmente per tutte le chat",
"form": {
"keepAlive": {
"label": "Keep Alive",
"help": "Imposta il tempo per cui il modello deve rimanere caricato in memoria (default: 5m)",
"placeholder": "Inserisci la durata del Keep Alive (e.g. 5m, 10m, 1h)"
},
"temperature": {
"label": "Temperatura",
"placeholder": "Inserisci la Temperatura (e.g. 0.7, 1.0)"
},
"numCtx": {
"label": "Dimensione del Contesto",
"placeholder": "Inserisci la Dimensione del Contesto (default: 2048)"
},
"seed": {
"label": "Seed",
"placeholder": "Inserisci il Valore Seed (e.g. 1234)",
"help": "Riproducibilità dell'output del modello"
},
"topK": {
"label": "Top K",
"placeholder": "Inserisci il Valore Top K (e.g. 40, 100)"
},
"topP": {
"label": "Top P",
"placeholder": "Inserisci il Valore Top P (e.g. 0.9, 0.95)"
}
},
"advanced": "Altre Impostazioni del Modello"
}
}

View File

@ -0,0 +1,42 @@
{
"addBtn": "Aggiungi nuova Knowledge Base",
"columns": {
"title": "Titolo",
"status": "Stato",
"embeddings": "Modello di Embedding",
"createdAt": "Creato da",
"action": "Azioni"
},
"expandedColumns": {
"name": "Nome"
},
"tooltip": {
"delete": "Elimina"
},
"confirm": {
"delete": "Sei sicuro di voler eliminare questa Knowledge Base?"
},
"deleteSuccess": "Knowledge Base eliminata correttamente",
"status": {
"pending": "In attesa",
"finished": "Completato",
"processing": "In corso"
},
"addKnowledge": "Aggiungi Knowledge Base",
"form": {
"title": {
"label": "Titolo Knowledge Base",
"placeholder": "Inserisci il titolo della Knowledge Base",
"required": "Il Titolo è obbligatorio"
},
"uploadFile": {
"label": "Carica File",
"uploadText": "Trascina un file qui or scegli upload",
"uploadHint": "Tipi di file supportati: .pdf, .csv, .txt, .md, .docx",
"required": "File è obbligatorio"
},
"submit": "Invia",
"success": "Knowledge Base aggiunta correttamente"
},
"noEmbeddingModel": "Aggiungi prima un modello dalla pagina di impostazione di Ollama"
}

View File

@ -0,0 +1,12 @@
{
"newChat": "Nuova Chat",
"selectAPrompt": "Scegli un Prompt",
"githubRepository": "GitHub Repository",
"settings": "Impsotazioni",
"sidebarTitle": "Cronologia Chat",
"error": "Errore",
"somethingWentWrong": "Qualcosa è andato storto",
"validationSelectModel": "Scegliere un modello per continuare",
"deleteHistoryConfirmation": "Sei sicuro che vuoi eliminare la cronologia?",
"editHistoryTitle": "Inserisci un nuovo titolo"
}

View File

@ -0,0 +1,29 @@
{
"ollamaState": {
"searching": "Sto cercando Ollama 🦙",
"running": "Ollama è attivo 🦙",
"notRunning": "Impossibile connettersi a Ollama 🦙",
"connectionError": "C'è stato un problema di connessione. Controlla la <anchor>documentazione</anchor> per investigare."
},
"formError": {
"noModel": "Seleziona un modello",
"noEmbeddingModel": "Imposta un modello di embedding da Impostazioni > Ollama"
},
"form": {
"textarea": {
"placeholder": "Scrivi un messaggio..."
},
"webSearch": {
"on": "Attivo",
"off": "Disattivato"
}
},
"tooltip": {
"searchInternet": "Cerca su Internet",
"speechToText": "Speech to Text",
"uploadImage": "Carica immagine",
"stopStreaming": "Ferma lo Streaming",
"knowledge": "Conoscenza"
},
"sendWhenEnter": "Invia subito dopo Enter"
}

View File

@ -0,0 +1,286 @@
{
"generalSettings": {
"title": "Impostazioni Generali",
"settings": {
"heading": "Impostazioni Web UI",
"speechRecognitionLang": {
"label": "Lingua per il riconoscimento vocale",
"placeholder": "Scegli una lingua"
},
"language": {
"label": "Lingua",
"placeholder": "Scegli una lingua"
},
"darkMode": {
"label": "Cambia il Tema",
"options": {
"light": "Chiaro",
"dark": "Scuro"
}
},
"copilotResumeLastChat": {
"label": "Riprendi l'ultima chat quando apri il Pannello Laterale (Copilot)"
},
"hideCurrentChatModelSettings": {
"label": "Nascondi le impostazioni correnti del modello Chat"
}
},
"webSearch": {
"heading": "Gestione ricerca Web",
"searchMode": {
"label": "Effettua ricerca web Internet semplice"
},
"provider": {
"label": "Motori di ricerca",
"placeholder": "Scegli un motore di ricerca"
},
"totalSearchResults": {
"label": "Risultati della ricerca",
"placeholder": "Inserisci il totale delle ricerche"
}
},
"system": {
"heading": "Impostazioni di Sistema",
"deleteChatHistory": {
"label": "Elimina cronologia Chat",
"button": "Elimina",
"confirm": "Sei sicuro che vuoi eliminare la tua cronologia delle chat? Questa azione non può essere annullata."
},
"export": {
"label": "Esporta la cronologia Chat, Base di Conoscenza, e Prompts",
"button": "Esporta Dati",
"success": "Esportato con Successo"
},
"import": {
"label": "Imposta la cronologia Chat, Base di Conoscenza, e Prompts",
"button": "Importa Dati",
"success": "Importato con Successo",
"error": "Errore Importazione"
}
},
"tts": {
"heading": "Impostazioni Text-to-Speech",
"ttsEnabled": {
"label": "Abilita Text-to-Speech"
},
"ttsProvider": {
"label": "Text-to-Speech Provider",
"placeholder": "Seleziona un provider"
},
"ttsVoice": {
"label": "Text-to-Speech Voce",
"placeholder": "Seleziona una voce"
},
"ssmlEnabled": {
"label": "Abilita SSML (Speech Synthesis Markup Language)"
}
}
},
"manageModels": {
"title": "Gestione Modelli",
"addBtn": "Aggiungi un nuovo Modello",
"columns": {
"name": "Nome",
"digest": "Digest",
"modifiedAt": "Modificato il",
"size": "Dimensioni",
"actions": "Azioni"
},
"expandedColumns": {
"parentModel": "Modello Padre",
"format": "Formato",
"family": "Famiglia",
"parameterSize": "Numero di Parametri",
"quantizationLevel": "Livello di Quantizzazione"
},
"tooltip": {
"delete": "Elimina Modello",
"repull": "Ri-Scarica Modello"
},
"confirm": {
"delete": "Sei sicuro di voler eliminare questo modello?",
"repull": "Se sicuro che vuoi ri-scaricare questo modello?"
},
"modal": {
"title": "Aggiungi Nuovo Modello",
"placeholder": "Inserisci il Nome Modello",
"pull": "Scarico del Modello"
},
"notification": {
"pullModel": "Scarico del Modello",
"pullModelDescription": "Scaricando il modello {{modelName}}. Per ulteriori dettagli visualizza l'icona dell'estensione.",
"success": "Completato",
"error": "Errore",
"successDescription": "Scarico del modello completato",
"successDeleteDescription": "Eliminazione del modello completato",
"someError": "Qualcosa è andato storto. Riprova più tardi"
}
},
"managePrompts": {
"title": "Gestisci Prompts",
"addBtn": "Aggiungi nuovo Prompt",
"option1": "Normale",
"option2": "RAG",
"questionPrompt": "Question Prompt",
"columns": {
"title": "Titolo",
"prompt": "Prompt",
"type": "Tipo di Prompt",
"actions": "Azioni"
},
"systemPrompt": "Prompt di Sistema",
"quickPrompt": "Prompt Veloce",
"tooltip": {
"delete": "Elimina Prompt",
"edit": "Modifica Prompt"
},
"confirm": {
"delete": "Sei sicuro di voler eliminare questo prompt? L'azione non può essere annullata."
},
"modal": {
"addTitle": "Aggiungi Nuovo Prompt",
"editTitle": "Modifica Prompt"
},
"form": {
"title": {
"label": "Titolo",
"placeholder": "I Miei Prompt",
"required": "Inserisci il Titolo"
},
"prompt": {
"label": "Prompt",
"placeholder": "Inserisci Prompt",
"required": "Scrivi il prompt",
"help": "Puoi usare {key} come variabile nel tuo prompt."
},
"isSystem": {
"label": "Prompt di Sistema"
},
"btnSave": {
"saving": "Aggiungendo Prompt...",
"save": "Aggiungi Prompt"
},
"btnEdit": {
"saving": "Aggiornando Prompt...",
"save": "Aggiorna Prompt"
}
},
"notification": {
"addSuccess": "Prompt Aggiunto",
"addSuccessDesc": "Il Prompt è stato aggiunto correttamente",
"error": "Errore",
"someError": "Qualcosa è andato storto. Riprova più tardi",
"updatedSuccess": "Prompt Aggiornato",
"updatedSuccessDesc": "Il Prompt è stato aggiornato correttmante",
"deletedSuccess": "Prompt Eliminato",
"deletedSuccessDesc": "Il Prompt è stato eliminato correttamente"
}
},
"manageShare": {
"title": "Gestione Condivisioni",
"heading": "Configura l'URL della Pagina di Condivisione",
"form": {
"url": {
"label": "URL Pagina di Condivisione",
"placeholder": "Inserisci URL Pagina di Condivisione",
"required": "Inserisci l'url della pagina di condivisione!",
"help": "Per ragioni di privacy, tu puoi ospitare in self-host la paginacon il seguente URL. <anchor>Leggi altro</anchor>."
}
},
"webshare": {
"heading": "Condivisioni Web",
"columns": {
"title": "Titolo",
"url": "URL",
"actions": "Azioni"
},
"tooltip": {
"delete": "Elimina Condivisione"
},
"confirm": {
"delete": "Sei sicuro che vuoi eliminare questa condivisione? L'azione non può essere annullata."
},
"label": "Gestione Condivisioni",
"description": "Abilita o Disattiva la funzionalità di condivisione"
},
"notification": {
"pageShareSuccess": " URL di condivisione aggiornato correttamente",
"someError": "Qualcosa è andato storto. Riprova più tardi",
"webShareDeleteSuccess": "Condivisione eliminata correttamente"
}
},
"ollamaSettings": {
"title": "Impostazioni Ollama",
"heading": "Configura Ollama",
"settings": {
"ollamaUrl": {
"label": "Ollama URL",
"placeholder": "Inserici l'URL di Ollama"
},
"ragSettings": {
"label": "Impostazioni RAG",
"model": {
"label": "Modello di Embedding",
"required": "Scegliere il modello",
"help": "E' raccomandato l'uso di modelli come `nomic-embed-text`.",
"placeholder": "Seleziona un modello"
},
"chunkSize": {
"label": "Dimensione del Blocco (Chunk Size)",
"placeholder": "Inserisci la Dimensione del Blocco (Chunk Size)",
"required": "Inserisci la Dimensione del Blocco (chunk size)"
},
"chunkOverlap": {
"label": "Sovrapposizione del Blocco (Chunk Overlap)",
"placeholder": "Inserisci la Sovrapposizione del Blocco (Chunk Overlap)",
"required": "Inserisci la Sovrapposizione del Blocco"
}
},
"prompt": {
"label": "Configura il Prompt RAG",
"option1": "Normale",
"option2": "Web",
"alert": "La configurazione del prompt di sistema qui è deprecato. Usa la sezione Gestione Prompt per aggiungere o modificare i prompts.Questa sezione sarà eliminata nelle prossime release",
"systemPrompt": "Prompt di Sistema",
"systemPromptPlaceholder": "Inserisci il Prompt di Sistema",
"webSearchPrompt": "Prompt per la Ricerca Web",
"webSearchPromptHelp": "Non rimuovere `{search_results}` dal prompt.",
"webSearchPromptError": "Inserisci il prompt per la ricerca web",
"webSearchPromptPlaceholder": "Imserosco il Prompt per la Ricerca Web",
"webSearchFollowUpPrompt": "Prompt di Follow Up sulla Ricerca Web",
"webSearchFollowUpPromptHelp": "Non rimuovere `{chat_history}` e `{question}` dal prompt.",
"webSearchFollowUpPromptError": "Inserisci il Prompt di Follow Up della Ricerca Web!",
"webSearchFollowUpPromptPlaceholder": "I tuoi Prompt di Follow Up delle Ricerche Web"
},
"advanced": {
"label": "Configurazione Avanzata Ollama URL",
"urlRewriteEnabled": {
"label": "Abilita o Disabilita l'URL di Origine Personalizzato"
},
"rewriteUrl": {
"label": "URL di Origine Personalizzato",
"placeholder": "Inserisci URL di Origine Personalizzato"
},
"help": "Se hai problemi di connessione con Ollama su Page Assist, puoi configurare un URL di origine personalizzato. Per saperne di più sulla configurazione, <anchor>clicca qui</anchor>."
}
}
},
"manageSearch": {
"title": "Gestisci Ricerca Web",
"heading": "Configura Ricerca Web"
},
"about": {
"title": "Informazioni",
"heading": "Informazioni",
"chromeVersion": "Versione di Page Assist",
"ollamaVersion": "Versione di Ollama",
"support": "Puoi supportare il progetto Page Assist donando o sponsorizzando attraverso le seguenti piattaforme:",
"koFi": "Supporta su Ko-fi",
"githubSponsor": "Sponsorizza su GitHub",
"githubRepo": "Repository GitHub"
},
"manageKnowledge": {
"title": "Gestisci Conoscenza",
"heading": "Configura Base di Conoscenza"
}
}

View File

@ -0,0 +1,7 @@
{
"tooltip": {
"embed": "L'inserimento della pagina potrebbe richiedere alcuni minuti. Attendere prego...",
"clear": "Cancella la cronologia della chat",
"history": "Cronologia della chat"
}
}

View File

@ -0,0 +1,66 @@
import { Tooltip } from "antd"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { FC, memo, useState } from "react"
import { useTranslation } from "react-i18next"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"
interface Props {
language: string
value: string
}
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
const [isBtnPressed, setIsBtnPressed] = useState(false)
const { t } = useTranslation("common")
return (
<>
<div className="code relative text-base font-sans codeblock bg-zinc-950 rounded-md overflow-hidden">
<div className="flex bg-gray-800 items-center justify-between py-1.5 px-4">
<span className="text-xs lowercase text-gray-200">{language}</span>
<div className="flex items-center">
<Tooltip title={t("copyToClipboard")}>
<button
onClick={() => {
navigator.clipboard.writeText(value)
setIsBtnPressed(true)
setTimeout(() => {
setIsBtnPressed(false)
}, 4000)
}}
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100">
{!isBtnPressed ? (
<ClipboardIcon className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4 text-green-400" />
)}
</button>
</Tooltip>
</div>
</div>
<SyntaxHighlighter
language={language}
style={coldarkDark}
PreTag="div"
customStyle={{
margin: 0,
width: "100%",
background: "transparent",
padding: "1.5rem 1rem"
}}
lineNumberStyle={{
userSelect: "none"
}}
codeTagProps={{
style: {
fontSize: "0.9rem",
fontFamily: "var(--font-mono)"
}
}}>
{value}
</SyntaxHighlighter>
</div>
</>
)
})

View File

@ -1,68 +1,38 @@
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import remarkGfm from "remark-gfm"
import { nightOwl } from "react-syntax-highlighter/dist/cjs/styles/prism"
import remarkMath from "remark-math"
import ReactMarkdown from "react-markdown"
import ReactMarkdown, { Options } from "react-markdown"
import "property-information"
import React from "react"
import { Tooltip } from "antd"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { useTranslation } from "react-i18next"
import { FC, memo } from "react"
import { CodeBlock } from "./CodeBlock"
export const MemoizedReactMarkdown: FC<Options> = memo(
ReactMarkdown,
(prevProps, nextProps) =>
prevProps.children === nextProps.children &&
prevProps.className === nextProps.className
)
export default function Markdown({ message }: { message: string }) {
const [isBtnPressed, setIsBtnPressed] = React.useState(false)
const { t } = useTranslation("common")
return (
<React.Fragment>
<ReactMarkdown
<MemoizedReactMarkdown
className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
remarkPlugins={[remarkGfm, remarkMath]}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || "")
return !inline ? (
<div className="code relative text-base bg-gray-800 rounded-md overflow-hidden">
<div className="flex items-center justify-between py-1.5 px-4">
<span className="text-xs lowercase text-gray-200">
{className && className.replace("language-", "")}
</span>
<div className="flex items-center">
<Tooltip title={t("copyToClipboard")}>
<button
onClick={() => {
navigator.clipboard.writeText(children[0] as string)
setIsBtnPressed(true)
setTimeout(() => {
setIsBtnPressed(false)
}, 4000)
}}
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100">
{!isBtnPressed ? (
<ClipboardIcon className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4 text-green-400" />
)}
</button>
</Tooltip>
</div>
</div>
<SyntaxHighlighter
{...props}
children={String(children).replace(/\n$/, "")}
style={nightOwl}
key={Math.random()}
customStyle={{
margin: 0,
fontSize: "1rem",
lineHeight: "1.5rem"
}}
language={(match && match[1]) || ""}
codeTagProps={{
className: "text-sm"
}}
<CodeBlock
language={match ? match[1] : ""}
value={String(children).replace(/\n$/, "")}
/>
</div>
) : (
<code className={`${className} font-semibold`} {...props}>
{children}
@ -85,7 +55,7 @@ export default function Markdown({ message }: { message: string }) {
}
}}>
{message}
</ReactMarkdown>
</MemoizedReactMarkdown>
</React.Fragment>
)
}

View File

@ -49,7 +49,7 @@ export const EditMessageForm = (props: Props) => {
<div className="flex justify-center space-x-2 mt-2">
<button
aria-label={t("save")}
className="bg-white dark:bg-black px-2.5 py-2 rounded-md text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900">
className="bg-black px-2.5 py-2 rounded-md text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-900">
{props.isBot ? t("save") : t("saveAndSubmit")}
</button>
<button

View File

@ -0,0 +1,221 @@
import { useStorage } from "@plasmohq/storage/hook"
import {
BrainCog,
ChevronLeft,
CogIcon,
ComputerIcon,
GithubIcon,
PanelLeftIcon,
SquarePen,
ZapIcon
} from "lucide-react"
import { useTranslation } from "react-i18next"
import { useLocation, NavLink } from "react-router-dom"
import { OllamaIcon } from "../Icons/Ollama"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnwledge"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import { Select, Tooltip } from "antd"
import { getAllPrompts } from "@/db"
import { ShareBtn } from "~/components/Common/ShareBtn"
type Props = {
setSidebarOpen: (open: boolean) => void
setOpenModelSettings: (open: boolean) => void
}
export const Header: React.FC<Props> = ({
setOpenModelSettings,
setSidebarOpen
}) => {
const { t } = useTranslation(["option", "common"])
const [shareModeEnabled] = useStorage("shareMode", false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
isFetching: isModelsFetching
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchInterval: 15000
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
return (
<div className="sticky top-0 z-[999] flex h-16 p-3 bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600">
<div className="flex gap-2 items-center">
{pathname !== "/" && (
<div>
<NavLink
to="/"
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<ChevronLeft className="w-4 h-4" />
</NavLink>
</div>
)}
<div>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen(true)}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<div>
<button
onClick={clearChat}
className="inline-flex dark:bg-transparent bg-white items-center rounded-lg border dark:border-gray-700 bg-transparent px-3 py-2.5 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white">
<SquarePen className="h-5 w-5 " />
<span className=" truncate ml-3">{t("newChat")}</span>
</button>
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
size="large"
loading={isModelsLoading || isModelsFetching}
filterOption={(input, option) =>
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
showSearch
placeholder={t("common:selectAModel")}
className="w-72"
options={models?.map((model) => ({
label: (
<span
key={model.model}
className="flex flex-row gap-3 items-center truncate">
<OllamaIcon className="w-5 h-5" />
<span className="truncate">{model.name}</span>
</span>
),
value: model.model
}))}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect />
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
{pathname === "/" &&
messages.length > 0 &&
!streaming &&
shareModeEnabled && <ShareBtn messages={messages} />}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,30 +1,12 @@
import React, { useState } from "react"
import { useLocation, NavLink } from "react-router-dom"
import { Sidebar } from "../Option/Sidebar"
import { Drawer, Select, Tooltip } from "antd"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels, getAllModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import {
BrainCog,
ChevronLeft,
CogIcon,
ComputerIcon,
GithubIcon,
PanelLeftIcon,
SquarePen,
ZapIcon
} from "lucide-react"
import { getAllPrompts } from "@/db"
import { ShareBtn } from "~/components/Common/ShareBtn"
import { Drawer } from "antd"
import { useTranslation } from "react-i18next"
import { OllamaIcon } from "../Icons/Ollama"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnwledge"
import { useStorage } from "@plasmohq/storage/hook"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
import { Header } from "./Header"
export default function OptionLayout({
children
@ -33,205 +15,17 @@ export default function OptionLayout({
}) {
const [sidebarOpen, setSidebarOpen] = useState(false)
const { t } = useTranslation(["option", "common"])
const [shareModeEnabled] = useStorage("shareMode", false)
const [openModelSettings, setOpenModelSettings] = useState(false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
isFetching: isModelsFetching
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchInterval: 15000
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
return (
<div>
<div>
<div className="flex flex-col">
<div className="sticky top-0 z-[999] flex h-16 p-3 bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600">
<div className="flex gap-2 items-center">
{pathname !== "/" && (
<div>
<NavLink
to="/"
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<ChevronLeft className="w-4 h-4" />
</NavLink>
</div>
)}
<div>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen(true)}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<div>
<button
onClick={clearChat}
className="inline-flex dark:bg-transparent bg-white items-center rounded-lg border dark:border-gray-700 bg-transparent px-3 py-2.5 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white">
<SquarePen className="h-5 w-5 " />
<span className=" truncate ml-3">{t("newChat")}</span>
</button>
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
size="large"
loading={isModelsLoading || isModelsFetching}
filterOption={(input, option) =>
option.label.key
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
showSearch
placeholder={t("common:selectAModel")}
className="w-64 "
options={models?.map((model) => ({
label: (
<span
key={model.model}
className="flex flex-row gap-3 items-center">
<OllamaIcon className="w-5 h-5" />
{model.name}
</span>
),
value: model.model
}))}
<>
<div className="flex flex-col min-h-screen">
<Header
setSidebarOpen={setSidebarOpen}
setOpenModelSettings={setOpenModelSettings}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect />
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
{pathname === "/" &&
messages.length > 0 &&
!streaming &&
shareModeEnabled && <ShareBtn messages={messages} />}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div>
</div>
<main className="flex-1">{children}</main>
</div>
</div>
<Drawer
title={t("sidebarTitle")}
@ -246,6 +40,6 @@ export default function OptionLayout({
open={openModelSettings}
setOpen={setOpenModelSettings}
/>
</div>
</>
)
}

View File

@ -0,0 +1,87 @@
import { useCallback, useEffect, useRef, useState } from "react"
export const useScrollAnchor = () => {
const messagesRef = useRef<HTMLDivElement>(null)
const scrollRef = useRef<HTMLDivElement>(null)
const visibilityRef = useRef<HTMLDivElement>(null)
const [isAtBottom, setIsAtBottom] = useState(true)
const [isVisible, setIsVisible] = useState(false)
const scrollToBottom = useCallback(() => {
if (messagesRef.current) {
messagesRef.current.scrollIntoView({
block: "end",
behavior: "smooth"
})
}
}, [])
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,
isAtBottom,
isVisible
}
}

View File

@ -2,10 +2,12 @@ import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { en } from "./lang/en";
import { pt } from "./lang/pt";
import { fr } from "./lang/fr";
import { ru } from "./lang/ru";
import { ml } from "./lang/ml";
import { zh } from "./lang/zh";
import { ja } from "./lang/ja";
import { it } from "./lang/it";
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
@ -14,6 +16,8 @@ i18n
.init({
resources: {
en: en,
fr: fr,
"it": it,
ml: ml,
"pt-BR": pt,
"zh-CN": zh,

15
src/i18n/lang/fr.ts Normal file
View File

@ -0,0 +1,15 @@
import option from "@/assets/locale/fr/option.json";
import playground from "@/assets/locale/fr/playground.json";
import common from "@/assets/locale/fr/common.json";
import sidepanel from "@/assets/locale/fr/sidepanel.json";
import settings from "@/assets/locale/fr/settings.json";
import knowledge from "@/assets/locale/fr/knowledge.json";
export const fr = {
option,
playground,
common,
sidepanel,
settings,
knowledge
}

15
src/i18n/lang/it.ts Normal file
View File

@ -0,0 +1,15 @@
import option from "@/assets/locale/it/option.json";
import playground from "@/assets/locale/it/playground.json";
import common from "@/assets/locale/it/common.json";
import sidepanel from "@/assets/locale/it/sidepanel.json";
import settings from "@/assets/locale/it/settings.json";
import knowledge from "@/assets/locale/it/knowledge.json";
export const it = {
option,
playground,
common,
sidepanel,
settings,
knowledge
}

View File

@ -4,6 +4,14 @@ export const supportLanguage = [
label: "English",
value: "en"
},
{
label: "Français",
value: "fr"
},
{
label: "Italiano",
value: "it"
},
{
label: "Russian",
value: "ru"

View File

@ -2,7 +2,8 @@ import { getKnowledgeById, updateKnowledgeStatus } from "@/db/knowledge"
import { PageAssistPDFUrlLoader } from "@/loader/pdf-url"
import {
defaultEmbeddingChunkOverlap,
defaultEmbeddingChunkSize
defaultEmbeddingChunkSize,
getOllamaURL
} from "@/services/ollama"
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
@ -10,22 +11,14 @@ import { PageAssistVectorStore } from "./PageAssistVectorStore"
import { PageAssisCSVUrlLoader } from "@/loader/csv"
import { PageAssisTXTUrlLoader } from "@/loader/txt"
import { PageAssistDocxLoader } from "@/loader/docx"
import { cleanUrl } from "./clean-url"
const readAsArrayBuffer = (file: File): Promise<ArrayBuffer> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
resolve(reader.result as ArrayBuffer)
}
reader.onerror = reject
reader.readAsArrayBuffer(file)
})
}
export const processKnowledge = async (msg: any, id: string): Promise<void> => {
console.log(`Processing knowledge with id: ${id}`)
try {
const knowledge = await getKnowledgeById(id)
const ollamaUrl = await getOllamaURL()
if (!knowledge) {
console.error(`Knowledge with id ${id} not found`)
@ -35,6 +28,7 @@ export const processKnowledge = async (msg: any, id: string): Promise<void> => {
await updateKnowledgeStatus(id, "processing")
const ollamaEmbedding = new OllamaEmbeddings({
baseUrl: cleanUrl(ollamaUrl),
model: knowledge.embedding_model
})
const chunkSize = await defaultEmbeddingChunkSize()

View File

@ -0,0 +1,11 @@
{
"extName": {
"message": "Page Assist - Une interface Web pour les modèles d'IA locaux"
},
"extDescription": {
"message": "Utilisez vos modèles d'IA locaux pour vous aider dans votre navigation web."
},
"openSidePanelToChat": {
"message": "Ouvrir Copilot pour discuter"
}
}

View File

@ -0,0 +1,11 @@
{
"extName": {
"message": "Page Assist - Un'interfaccia web per modelli AI locali"
},
"extDescription": {
"message": "Usa i tuoi modelli AI in esecuzione locale per assisterti nella navigazione web."
},
"openSidePanelToChat": {
"message": "Apri Copilot per chattare"
}
}

View File

@ -48,7 +48,7 @@ export default defineConfig({
outDir: "build",
manifest: {
version: "1.1.10",
version: "1.1.11",
name:
process.env.TARGET === "firefox"
? "Page Assist - A Web UI for Local AI Models"

7246
yarn.lock

File diff suppressed because it is too large Load Diff