Merge pull request #174 from n4ze3m/next

v1.2.2
This commit is contained in:
Muhammed Nazeem 2024-08-31 23:47:38 +05:30 committed by GitHub
commit 0542b8a6d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 809 additions and 104 deletions

View File

@ -0,0 +1,13 @@
{
"heading": "Chrome AI konfigurieren",
"status": {
"label": "Chrome AI-Unterstützung für Page Assist aktivieren oder deaktivieren"
},
"error": {
"browser_not_supported": "Diese Version von Chrome wird vom Gemini Nano-Modell nicht unterstützt. Bitte aktualisieren Sie auf Version 127 oder höher",
"ai_not_supported": "Die Einstellung chrome://flags/#prompt-api-for-gemini-nano ist nicht aktiviert. Bitte aktivieren Sie sie.",
"ai_not_ready": "Gemini Nano ist noch nicht bereit; Sie müssen die Chrome-Einstellungen überprüfen.",
"internal_error": "Ein interner Fehler ist aufgetreten. Bitte versuchen Sie es später erneut."
},
"errorDescription": "Um Chrome AI zu nutzen, benötigen Sie eine Browserversion höher als 127, die derzeit in den Dev- und Canary-Kanälen verfügbar ist. Nach dem Herunterladen der unterstützten Version folgen Sie diesen Schritten:\n\n1. Gehen Sie zu `chrome://flags/#prompt-api-for-gemini-nano` und wählen Sie \"Aktivieren\".\n2. Gehen Sie zu `chrome://flags/#optimization-guide-on-device-model` und wählen Sie \"EnabledBypassPrefRequirement\".\n3. Gehen Sie zu `chrome://components`, suchen Sie nach \"Optimization Guide On Device Model\" und klicken Sie auf \"Nach Update suchen\". Dies wird das Modell herunterladen. Wenn Sie die Einstellungen nicht sehen, wiederholen Sie die Schritte 1 und 2 und starten Sie Ihren Browser neu."
}

View File

@ -0,0 +1,100 @@
{
"pageAssist": "Page Assist",
"selectAModel": "Modell auswählen",
"save": "Speichern",
"saved": "Gespeichert",
"cancel": "Abbrechen",
"retry": "Erneut versuchen",
"share": {
"tooltip": {
"share": "Teilen"
},
"modal": {
"title": "Link zum Chat teilen"
},
"form": {
"defaultValue": {
"name": "Anonym",
"title": "Unbenannter Chat"
},
"title": {
"label": "Chat-Titel",
"placeholder": "Chat-Titel eingeben",
"required": "Chat-Titel ist erforderlich"
},
"name": {
"label": "Ihr Name",
"placeholder": "Geben Sie Ihren Namen ein",
"required": "Ihr Name ist erforderlich"
},
"btn": {
"save": "Link generieren",
"saving": "Link wird generiert..."
}
},
"notification": {
"successGenerate": "Link in die Zwischenablage kopiert",
"failGenerate": "Link konnte nicht generiert werden"
}
},
"copyToClipboard": "In die Zwischenablage kopieren",
"webSearch": "Web durchsuchen",
"regenerate": "Neu generieren",
"edit": "Bearbeiten",
"saveAndSubmit": "Speichern & Absenden",
"editMessage": {
"placeholder": "Nachricht eingeben..."
},
"submit": "Absenden",
"noData": "Keine Daten",
"noHistory": "Kein Chat-Verlauf",
"chatWithCurrentPage": "Mit aktueller Seite chatten",
"beta": "Beta",
"tts": "Vorlesen",
"currentChatModelSettings": "Aktuelle Chat-Modell-Einstellungen",
"modelSettings": {
"label": "Modell-Einstellungen",
"description": "Legen Sie die Modelloptionen global für alle Chats fest",
"form": {
"keepAlive": {
"label": "Aktiv halten",
"help": "Steuert, wie lange das Modell nach der Anfrage im Speicher geladen bleibt (Standard: 5m)",
"placeholder": "Geben Sie die Dauer für Aktiv halten ein (z.B. 5m, 10m, 1h)"
},
"temperature": {
"label": "Temperatur",
"placeholder": "Geben Sie den Temperaturwert ein (z.B. 0.7, 1.0)"
},
"numCtx": {
"label": "Anzahl der Kontexte",
"placeholder": "Geben Sie die Anzahl der Kontexte ein (Standard: 2048)"
},
"seed": {
"label": "Seed",
"placeholder": "Geben Sie den Seed-Wert ein (z.B. 1234)",
"help": "Reproduzierbarkeit der Modellausgabe"
},
"topK": {
"label": "Top K",
"placeholder": "Geben Sie den Top-K-Wert ein (z.B. 40, 100)"
},
"topP": {
"label": "Top P",
"placeholder": "Geben Sie den Top-P-Wert ein (z.B. 0.9, 0.95)"
},
"numGpu": {
"label": "Anzahl GPUs",
"placeholder": "Geben Sie die Anzahl der Ebenen ein, die an GPU(s) gesendet werden sollen"
}
},
"advanced": "Weitere Modell-Einstellungen"
},
"copilot": {
"summary": "Zusammenfassen",
"explain": "Erklären",
"rephrase": "Umformulieren",
"translate": "Übersetzen",
"custom": "Benutzerdefiniert"
},
"citations": "Zitate"
}

View File

@ -0,0 +1,43 @@
{
"addBtn": "Neues Wissen hinzufügen",
"columns": {
"title": "Titel",
"status": "Status",
"embeddings": "Einbettungsmodell",
"createdAt": "Erstellt am",
"action": "Aktionen"
},
"expandedColumns": {
"name": "Name"
},
"tooltip": {
"delete": "Löschen"
},
"confirm": {
"delete": "Sind Sie sicher, dass Sie dieses Wissen löschen möchten?"
},
"deleteSuccess": "Wissen erfolgreich gelöscht",
"status": {
"pending": "Ausstehend",
"finished": "Abgeschlossen",
"processing": "In Bearbeitung",
"failed": "Fehlgeschlagen"
},
"addKnowledge": "Wissen hinzufügen",
"form": {
"title": {
"label": "Wissenstitel",
"placeholder": "Wissenstitel eingeben",
"required": "Wissenstitel ist erforderlich"
},
"uploadFile": {
"label": "Datei hochladen",
"uploadText": "Ziehen Sie eine Datei hierher oder klicken Sie zum Hochladen",
"uploadHint": "Unterstützte Dateitypen: .pdf, .csv, .txt, .md, .docx",
"required": "Datei ist erforderlich"
},
"submit": "Absenden",
"success": "Wissen erfolgreich hinzugefügt"
},
"noEmbeddingModel": "Bitte fügen Sie zuerst ein Einbettungsmodell von der RAG-Einstellungsseite hinzu"
}

View File

@ -0,0 +1,12 @@
{
"newChat": "Neuer Chat",
"selectAPrompt": "Wähle eine Eingabeaufforderung",
"githubRepository": "GitHub-Repository",
"settings": "Einstellungen",
"sidebarTitle": "Chat-Verlauf",
"error": "Fehler",
"somethingWentWrong": "Etwas ist schiefgelaufen",
"validationSelectModel": "Bitte wähle ein Modell aus, um fortzufahren",
"deleteHistoryConfirmation": "Bist du sicher, dass du diesen Verlauf löschen möchtest?",
"editHistoryTitle": "Gib einen neuen Titel ein"
}

View File

@ -0,0 +1,29 @@
{
"ollamaState": {
"searching": "Suche nach Ihrem Ollama 🦙",
"running": "Ollama läuft 🦙",
"notRunning": "Verbindung zu Ollama nicht möglich 🦙",
"connectionError": "Es scheint, dass Sie ein Verbindungsproblem haben. Bitte beachten Sie diese <anchor>Dokumentation</anchor> zur Fehlerbehebung."
},
"formError": {
"noModel": "Bitte wählen Sie ein Modell aus",
"noEmbeddingModel": "Bitte legen Sie ein Embedding-Modell auf der Seite Einstellungen > RAG fest"
},
"form": {
"textarea": {
"placeholder": "Nachricht eingeben..."
},
"webSearch": {
"on": "An",
"off": "Aus"
}
},
"tooltip": {
"searchInternet": "Internet durchsuchen",
"speechToText": "Sprache zu Text",
"uploadImage": "Bild hochladen",
"stopStreaming": "Streaming stoppen",
"knowledge": "Wissen"
},
"sendWhenEnter": "Senden bei Drücken der Eingabetaste"
}

View File

@ -0,0 +1,341 @@
{
"generalSettings": {
"title": "Allgemeine Einstellungen",
"settings": {
"heading": "Web-UI-Einstellungen",
"speechRecognitionLang": {
"label": "Spracherkennungssprache",
"placeholder": "Sprache auswählen"
},
"language": {
"label": "Sprache",
"placeholder": "Sprache auswählen"
},
"darkMode": {
"label": "Design ändern",
"options": {
"light": "Hell",
"dark": "Dunkel"
}
},
"copilotResumeLastChat": {
"label": "Letzten Chat beim Öffnen des Seitenpanels fortsetzen (Copilot)"
},
"hideCurrentChatModelSettings": {
"label": "Aktuelle Chat-Modell-Einstellungen ausblenden"
},
"restoreLastChatModel": {
"label": "Zuletzt verwendetes Modell für vorherige Chats wiederherstellen"
},
"sendNotificationAfterIndexing": {
"label": "Benachrichtigung nach Abschluss der Wissensbasis-Verarbeitung senden"
},
"generateTitle" :{
"label": "Titel mit KI generieren"
}
},
"sidepanelRag": {
"heading": "Copilot Chat mit Website-Einstellungen",
"ragEnabled": {
"label": "Chat mit Website unter Verwendung von Vektor-Embeddings"
},
"maxWebsiteContext": {
"label": "Normaler Modus Website-Inhaltsgröße",
"placeholder": "Inhaltsgröße (Standard 4028)"
}
},
"webSearch": {
"heading": "Websuche verwalten",
"searchMode": {
"label": "Einfache Internetsuche durchführen"
},
"provider": {
"label": "Suchmaschine",
"placeholder": "Suchmaschine auswählen"
},
"totalSearchResults": {
"label": "Gesamtanzahl der Suchergebnisse",
"placeholder": "Gesamtanzahl der Suchergebnisse eingeben"
},
"visitSpecificWebsite": {
"label": "Die in der Nachricht erwähnte Website besuchen"
}
},
"system": {
"heading": "Systemeinstellungen",
"deleteChatHistory": {
"label": "Chatverlauf löschen",
"button": "Löschen",
"confirm": "Sind Sie sicher, dass Sie Ihren Chatverlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
},
"export": {
"label": "Chatverlauf, Wissensbasis und Prompts exportieren",
"button": "Daten exportieren",
"success": "Export erfolgreich"
},
"import": {
"label": "Chatverlauf, Wissensbasis und Prompts importieren",
"button": "Daten importieren",
"success": "Import erfolgreich",
"error": "Importfehler"
}
},
"tts": {
"heading": "Text-zu-Sprache-Einstellungen",
"ttsEnabled": {
"label": "Text-zu-Sprache aktivieren"
},
"ttsProvider": {
"label": "Text-zu-Sprache-Anbieter",
"placeholder": "Anbieter auswählen"
},
"ttsVoice": {
"label": "Text-zu-Sprache-Stimme",
"placeholder": "Stimme auswählen"
},
"ssmlEnabled": {
"label": "SSML (Speech Synthesis Markup Language) aktivieren"
}
}
},
"manageModels": {
"title": "Modelle verwalten",
"addBtn": "Neues Modell hinzufügen",
"columns": {
"name": "Name",
"digest": "Digest",
"modifiedAt": "Zuletzt geändert",
"size": "Größe",
"actions": "Aktionen"
},
"expandedColumns": {
"parentModel": "Übergeordnetes Modell",
"format": "Format",
"family": "Familie",
"parameterSize": "Parametergröße",
"quantizationLevel": "Quantisierungsstufe"
},
"tooltip": {
"delete": "Modell löschen",
"repull": "Modell erneut herunterladen"
},
"confirm": {
"delete": "Sind Sie sicher, dass Sie dieses Modell löschen möchten?",
"repull": "Sind Sie sicher, dass Sie dieses Modell erneut herunterladen möchten?"
},
"modal": {
"title": "Neues Modell hinzufügen",
"placeholder": "Modellnamen eingeben",
"pull": "Modell herunterladen"
},
"notification": {
"pullModel": "Modell wird heruntergeladen",
"pullModelDescription": "Das Modell {{modelName}} wird heruntergeladen. Weitere Details finden Sie im Erweiterungssymbol.",
"success": "Erfolgreich",
"error": "Fehler",
"successDescription": "Das Modell wurde erfolgreich heruntergeladen",
"successDeleteDescription": "Das Modell wurde erfolgreich gelöscht",
"someError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut"
}
},
"managePrompts": {
"title": "Prompts verwalten",
"addBtn": "Neuen Prompt hinzufügen",
"option1": "Normal",
"option2": "RAG",
"questionPrompt": "Frage-Prompt",
"segmented": {
"custom": "Benutzerdefinierte Prompts",
"copilot": "Copilot-Prompts"
},
"columns": {
"title": "Titel",
"prompt": "Prompt",
"type": "Prompt-Typ",
"actions": "Aktionen"
},
"systemPrompt": "System-Prompt",
"quickPrompt": "Schnell-Prompt",
"tooltip": {
"delete": "Prompt löschen",
"edit": "Prompt bearbeiten"
},
"confirm": {
"delete": "Sind Sie sicher, dass Sie diesen Prompt löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
},
"modal": {
"addTitle": "Neuen Prompt hinzufügen",
"editTitle": "Prompt bearbeiten"
},
"form": {
"title": {
"label": "Titel",
"placeholder": "Mein toller Prompt",
"required": "Bitte geben Sie einen Titel ein"
},
"prompt": {
"label": "Prompt",
"placeholder": "Prompt eingeben",
"required": "Bitte geben Sie einen Prompt ein",
"help": "Sie können {key} als Variable in Ihrem Prompt verwenden.",
"missingTextPlaceholder": "Die Variable {text} fehlt im Prompt. Bitte fügen Sie sie hinzu."
},
"isSystem": {
"label": "Ist System-Prompt"
},
"btnSave": {
"saving": "Prompt wird hinzugefügt...",
"save": "Prompt hinzufügen"
},
"btnEdit": {
"saving": "Prompt wird aktualisiert...",
"save": "Prompt aktualisieren"
}
},
"notification": {
"addSuccess": "Prompt hinzugefügt",
"addSuccessDesc": "Prompt wurde erfolgreich hinzugefügt",
"error": "Fehler",
"someError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut",
"updatedSuccess": "Prompt aktualisiert",
"updatedSuccessDesc": "Prompt wurde erfolgreich aktualisiert",
"deletedSuccess": "Prompt gelöscht",
"deletedSuccessDesc": "Prompt wurde erfolgreich gelöscht"
}
},
"manageShare": {
"title": "Freigabe verwalten",
"heading": "Seiten-Freigabe-URL konfigurieren",
"form": {
"url": {
"label": "Seiten-Freigabe-URL",
"placeholder": "Seiten-Freigabe-URL eingeben",
"required": "Bitte geben Sie Ihre Seiten-Freigabe-URL ein!",
"help": "Aus Datenschutzgründen können Sie die Seitenfreigabe selbst hosten und die URL hier angeben. <anchor>Mehr erfahren</anchor>."
}
},
"webshare": {
"heading": "Web-Freigabe",
"columns": {
"title": "Titel",
"url": "URL",
"actions": "Aktionen"
},
"tooltip": {
"delete": "Freigabe löschen"
},
"confirm": {
"delete": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
},
"label": "Seitenfreigabe verwalten",
"description": "Seitenfreigabe-Funktion aktivieren oder deaktivieren"
},
"notification": {
"pageShareSuccess": "Seiten-Freigabe-URL erfolgreich aktualisiert",
"someError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut",
"webShareDeleteSuccess": "Web-Freigabe erfolgreich gelöscht"
}
},
"ollamaSettings": {
"title": "Ollama-Einstellungen",
"heading": "Ollama konfigurieren",
"settings": {
"ollamaUrl": {
"label": "Ollama-URL",
"placeholder": "Ollama-URL eingeben"
},
"advanced": {
"label": "Erweiterte Ollama-URL-Konfiguration",
"urlRewriteEnabled": {
"label": "Benutzerdefinierte Ursprungs-URL aktivieren oder deaktivieren"
},
"rewriteUrl": {
"label": "Benutzerdefinierte Ursprungs-URL",
"placeholder": "Benutzerdefinierte Ursprungs-URL eingeben"
},
"headers": {
"label": "Benutzerdefinierte Header",
"add": "Header hinzufügen",
"key": {
"label": "Header-Schlüssel",
"placeholder": "Autorisierung"
},
"value": {
"label": "Header-Wert",
"placeholder": "Bearer-Token"
}
},
"help": "Wenn Sie Verbindungsprobleme mit Ollama auf Page Assist haben, können Sie eine benutzerdefinierte Ursprungs-URL konfigurieren. Um mehr über die Konfiguration zu erfahren, <anchor>klicken Sie hier</anchor>."
}
}
},
"manageSearch": {
"title": "Web-Suche verwalten",
"heading": "Web-Suche konfigurieren"
},
"about": {
"title": "Über",
"heading": "Über",
"chromeVersion": "Page Assist Version",
"ollamaVersion": "Ollama Version",
"support": "Sie können das Page Assist-Projekt durch Spenden oder Sponsoring über die folgenden Plattformen unterstützen:",
"koFi": "Unterstützen Sie uns auf Ko-fi",
"githubSponsor": "Sponsern Sie uns auf GitHub",
"githubRepo": "GitHub-Repository"
},
"manageKnowledge": {
"title": "Wissen verwalten",
"heading": "Wissensbasis konfigurieren"
},
"rag": {
"title": "RAG-Einstellungen",
"ragSettings": {
"label": "RAG-Einstellungen",
"model": {
"label": "Embedding-Modell",
"required": "Bitte wählen Sie ein Modell aus",
"help": "Es wird dringend empfohlen, Embedding-Modelle wie `nomic-embed-text` zu verwenden.",
"placeholder": "Wählen Sie ein Modell aus"
},
"chunkSize": {
"label": "Chunk-Größe",
"placeholder": "Chunk-Größe eingeben",
"required": "Bitte geben Sie eine Chunk-Größe ein"
},
"chunkOverlap": {
"label": "Chunk-Überlappung",
"placeholder": "Chunk-Überlappung eingeben",
"required": "Bitte geben Sie eine Chunk-Überlappung ein"
},
"totalFilePerKB": {
"label": "Standard-Datei-Upload-Limit für die Wissensbasis",
"placeholder": "Geben Sie das Standard-Datei-Upload-Limit ein (z.B. 10)",
"required": "Bitte geben Sie das Standard-Datei-Upload-Limit ein"
},
"noOfRetrievedDocs": {
"label": "Anzahl der abgerufenen Dokumente",
"placeholder": "Anzahl der abgerufenen Dokumente eingeben",
"required": "Bitte geben Sie die Anzahl der abgerufenen Dokumente ein"
}
},
"prompt": {
"label": "RAG-Prompt konfigurieren",
"option1": "Normal",
"option2": "Web",
"alert": "Die Konfiguration des System-Prompts hier ist veraltet. Bitte verwenden Sie den Abschnitt 'Prompts verwalten', um Prompts hinzuzufügen oder zu bearbeiten. Dieser Abschnitt wird in einer zukünftigen Version entfernt",
"systemPrompt": "System-Prompt",
"systemPromptPlaceholder": "System-Prompt eingeben",
"webSearchPrompt": "Web-Suche-Prompt",
"webSearchPromptHelp": "Entfernen Sie `{search_results}` nicht aus dem Prompt.",
"webSearchPromptError": "Bitte geben Sie einen Web-Suche-Prompt ein",
"webSearchPromptPlaceholder": "Web-Suche-Prompt eingeben",
"webSearchFollowUpPrompt": "Web-Suche-Folgeprompt",
"webSearchFollowUpPromptHelp": "Entfernen Sie `{chat_history}` und `{question}` nicht aus dem Prompt.",
"webSearchFollowUpPromptError": "Bitte geben Sie Ihren Web-Suche-Folgeprompt ein!",
"webSearchFollowUpPromptPlaceholder": "Ihr Web-Suche-Folgeprompt"
}
},
"chromeAiSettings": {
"title": "Chrome AI-Einstellungen"
}
}

View File

@ -0,0 +1,7 @@
{
"tooltip": {
"embed": "Es kann einige Minuten dauern, die Seite einzubetten. Bitte warten Sie...",
"clear": "Chatverlauf löschen",
"history": "Chatverlauf"
}
}

View File

@ -95,5 +95,6 @@
"rephrase": "Rephrase",
"translate": "Translate",
"custom": "Custom"
}
},
"citations": "Citations"
}

View File

@ -94,5 +94,6 @@
"explain": "Explicar",
"rephrase": "Reformular",
"translate": "Traducir"
}
},
"citations": "Citas"
}

View File

@ -88,5 +88,6 @@
}
},
"advanced": "تنظیمات بیشتر مدل"
}
},
"citations": "منابع"
}

View File

@ -94,5 +94,6 @@
"explain": "Expliquer",
"rephrase": "Reformuler",
"translate": "Traduire"
}
},
"citations": "Citations"
}

View File

@ -93,5 +93,6 @@
"explain": "Spiegare",
"rephrase": "Riformulare",
"translate": "Tradurre"
}
},
"citations": "Citazioni"
}

View File

@ -94,5 +94,6 @@
"explain": "説明",
"rephrase": "言い換え",
"translate": "翻訳"
}
},
"citations": "引用"
}

View File

@ -93,5 +93,6 @@
"explain": "വിശദീകരിക്കുക",
"rephrase": "പുനഃരൂപീകരിക്കുക",
"translate": "വിവർത്തനം ചെയ്യുക"
}
},
"citations": "ഉദ്ധരണികൾ"
}

View File

@ -94,5 +94,6 @@
"explain": "Explicar",
"rephrase": "Reformular",
"translate": "Traduzir"
}
},
"citations": "Citações"
}

View File

@ -93,5 +93,6 @@
"explain": "Объяснить",
"rephrase": "Перефразировать",
"translate": "Перевести"
}
},
"citations": "Цитаты"
}

View File

@ -94,5 +94,6 @@
"explain": "解释",
"rephrase": "重述",
"translate": "翻译"
}
},
"citations": "引用"
}

View File

@ -1,6 +1,6 @@
import Markdown from "../../Common/Markdown"
import React from "react"
import { Tag, Image, Tooltip } from "antd"
import { Tag, Image, Tooltip, Collapse } from "antd"
import { WebSearch } from "./WebSearch"
import {
CheckIcon,
@ -127,15 +127,31 @@ export const PlaygroundMessage = (props: Props) => {
)}
{props.isBot && props?.sources && props?.sources.length > 0 && (
<div className="mb-3 flex flex-wrap gap-2">
{props?.sources?.map((source, index) => (
<MessageSource
onSourceClick={props.onSourceClick}
key={index}
source={source}
/>
))}
</div>
<Collapse
className="mt-6"
ghost
items={[
{
key: "1",
label: (
<div className="italic text-gray-500 dark:text-gray-400">
{t('citations')}
</div>
),
children: (
<div className="mb-3 flex flex-wrap gap-2">
{props?.sources?.map((source, index) => (
<MessageSource
onSourceClick={props.onSourceClick}
key={index}
source={source}
/>
))}
</div>
)
}
]}
/>
)}
{!props.isProcessing && !editMode && (
<div

View File

@ -9,6 +9,7 @@ import { getPageShareUrl } from "~/services/ollama"
import { cleanUrl } from "~/libs/clean-url"
import { getUserId, saveWebshare } from "@/db"
import { useTranslation } from "react-i18next"
import fetcher from "@/libs/fetcher"
type Props = {
messages: Message[]
@ -94,7 +95,7 @@ export const ShareBtn: React.FC<Props> = ({ messages }) => {
const chat = reformatMessages(messages, values.name)
const title = values.title
const url = await getPageShareUrl()
const res = await fetch(`${cleanUrl(url)}/api/v1/share/create`, {
const res = await fetcher(`${cleanUrl(url)}/api/v1/share/create`, {
method: "POST",
headers: {
"Content-Type": "application/json"

View File

@ -14,6 +14,7 @@ import { useTranslation } from "react-i18next"
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobe } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down"
type Props = {
dropedFile: File | undefined
@ -144,13 +145,16 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
})
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Process" || e.key === "229") return
if (import.meta.env.BROWSER !== "firefox") {
if (e.key === "Process" || e.key === "229") return
}
if (
!typing &&
e.key === "Enter" &&
!e.shiftKey &&
!isSending &&
sendWhenEnter
handleChatInputKeyDown({
e,
sendWhenEnter,
typing,
isSending
})
) {
e.preventDefault()
stopListening()
@ -244,8 +248,16 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/>
<div className="w-full border-x border-t flex flex-col dark:border-gray-600 rounded-t-xl p-2">
<textarea
onCompositionStart={() => setTyping(true)}
onCompositionEnd={() => setTyping(false)}
onCompositionStart={() => {
if (import.meta.env.BROWSER !== "firefox") {
setTyping(true)
}
}}
onCompositionEnd={() => {
if (import.meta.env.BROWSER !== "firefox") {
setTyping(false)
}
}}
onKeyDown={(e) => handleKeyDown(e)}
ref={textareaRef}
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"

View File

@ -4,6 +4,7 @@ import { useQuery } from "@tanstack/react-query"
import { Skeleton } from "antd"
import { cleanUrl } from "@/libs/clean-url"
import { Descriptions } from "antd"
import fetcher from "@/libs/fetcher"
export const AboutApp = () => {
const { t } = useTranslation("settings")
@ -14,7 +15,7 @@ export const AboutApp = () => {
const chromeVersion = browser.runtime.getManifest().version
try {
const url = await getOllamaURL()
const req = await fetch(`${cleanUrl(url)}/api/version`)
const req = await fetcher(`${cleanUrl(url)}/api/version`)
if (!req.ok) {
return {
@ -75,11 +76,11 @@ export const AboutApp = () => {
label: "X (formerly Twitter)",
children: (
<a
href="https://twitter.com/n4ze3m"
href="https://twitter.com/page_assist"
target="_blank"
rel="noreferrer"
className="text-blue-500 dark:text-blue-400">
@n4ze3m
@page_assist
</a>
)
}

View File

@ -16,9 +16,13 @@ import {
import { useStorage } from "@plasmohq/storage/hook"
export const GeneralSettings = () => {
const { clearChat, speechToTextLanguage, setSpeechToTextLanguage } =
const { clearChat } =
useMessageOption()
const [ speechToTextLanguage, setSpeechToTextLanguage ] = useStorage(
"speechToTextLanguage",
"en-US"
)
const [copilotResumeLastChat, setCopilotResumeLastChat] = useStorage(
"copilotResumeLastChat",
false

View File

@ -7,6 +7,7 @@ import { deleteWebshare, getAllWebshares, getUserId } from "@/db"
import { getPageShareUrl, setPageShareUrl } from "~/services/ollama"
import { verifyPageShareURL } from "~/utils/verify-page-share"
import { useStorage } from "@plasmohq/storage/hook"
import fetcher from "@/libs/fetcher"
export const OptionShareBody = () => {
const queryClient = useQueryClient()
@ -48,7 +49,7 @@ export const OptionShareBody = () => {
api_url: string
}) => {
const owner_id = await getUserId()
const res = await fetch(`${api_url}/api/v1/share/delete`, {
const res = await fetcher(`${api_url}/api/v1/share/delete`, {
method: "POST",
headers: {
"Content-Type": "application/json"

View File

@ -12,6 +12,7 @@ import { useTranslation } from "react-i18next"
import { ModelSelect } from "@/components/Common/ModelSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobeX, PiGlobe } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down"
type Props = {
dropedFile: File | undefined
@ -66,11 +67,12 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Process" || e.key === "229") return
if (
e.key === "Enter" &&
!e.shiftKey &&
!isSending &&
sendWhenEnter &&
!typing
handleChatInputKeyDown({
e,
sendWhenEnter,
typing,
isSending
})
) {
e.preventDefault()
form.onSubmit(async (value) => {
@ -245,8 +247,16 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
rows={1}
style={{ minHeight: "60px" }}
tabIndex={0}
onCompositionStart={() => setTyping(true)}
onCompositionEnd={() => setTyping(false)}
onCompositionStart={() => {
if (import.meta.env.BROWSER !== "firefox") {
setTyping(true)
}
}}
onCompositionEnd={() => {
if (import.meta.env.BROWSER !== "firefox") {
setTyping(false)
}
}}
placeholder={t("form.textarea.placeholder")}
{...form.getInputProps("message")}
/>

View File

@ -53,7 +53,10 @@ export const SettingsBody = () => {
const [hideCurrentChatModelSettings, setHideCurrentChatModelSettings] =
useStorage("hideCurrentChatModelSettings", false)
const { speechToTextLanguage, setSpeechToTextLanguage } = useMessage()
const [ speechToTextLanguage, setSpeechToTextLanguage ] = useStorage(
"speechToTextLanguage",
"en-US"
)
const { mode, toggleDarkMode } = useDarkMode()
const { changeLocale, locale, supportLanguage } = useI18n()

View File

@ -74,8 +74,6 @@ export const useMessage = () => {
setChatMode,
setIsEmbedding,
isEmbedding,
speechToTextLanguage,
setSpeechToTextLanguage,
currentURL,
setCurrentURL
} = useStoreMessage()
@ -1230,8 +1228,6 @@ export const useMessage = () => {
chatMode,
setChatMode,
isEmbedding,
speechToTextLanguage,
setSpeechToTextLanguage,
regenerateLastMessage,
webSearch,
setWebSearch,

View File

@ -10,6 +10,7 @@ import { ja } from "./lang/ja";
import { it } from "./lang/it";
import { es } from "./lang/es";
import { fa } from "./lang/fa";
import { de } from "./lang/de";
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
@ -30,7 +31,8 @@ i18n
ja: ja,
"ja-JP": ja,
fa: fa,
"fa-IR": fa
"fa-IR": fa,
de: de
},
fallbackLng: "en",
lng: localStorage.getItem("i18nextLng") || "en",

17
src/i18n/lang/de.ts Normal file
View File

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

View File

@ -39,5 +39,9 @@ export const supportLanguage = [
{
label: "فارسی",
value: "fa"
},
{
label: "Deutsch",
value: "de"
}
]

14
src/libs/fetcher.ts Normal file
View File

@ -0,0 +1,14 @@
import { getCustomOllamaHeaders } from "@/services/app"
const fetcher = async (input: string | URL | globalThis.Request, init?: RequestInit) : Promise<Response> => {
const update = {...init} || {}
const customHeaders = await getCustomOllamaHeaders()
update.headers = {
...customHeaders,
...update?.headers
}
return fetch(input, update)
}
export default fetcher

View File

@ -10,19 +10,8 @@ import {
import { BaseMessage, AIMessageChunk } from "@langchain/core/messages"
import { ChatGenerationChunk } from "@langchain/core/outputs"
import { IterableReadableStream } from "@langchain/core/utils/stream"
import { AITextSession, checkChromeAIAvailability, createAITextSession } from "./utils/chrome"
export interface AI {
canCreateTextSession(): Promise<AIModelAvailability>
createTextSession(options?: AITextSessionOptions): Promise<AITextSession>
defaultTextSessionOptions(): Promise<AITextSessionOptions>
}
export interface AITextSession {
prompt(input: string): Promise<string>
promptStreaming(input: string): ReadableStream
destroy(): void
clone(): AITextSession
}
export interface AITextSessionOptions {
topK: number
@ -44,16 +33,12 @@ export interface ChromeAIInputs extends BaseChatModelParams {
promptFormatter?: (messages: BaseMessage[]) => string
}
export interface ChromeAICallOptions extends BaseLanguageModelCallOptions {}
export interface ChromeAICallOptions extends BaseLanguageModelCallOptions { }
function formatPrompt(messages: BaseMessage[]): string {
return messages
.map((message) => {
if (typeof message.content !== "string") {
// console.log(message.content)
// throw new Error(
// "ChatChromeAI does not support non-string message content."
// )
if (message.content.length > 0) {
//@ts-ignore
return message.content[0]?.text || ""
@ -66,31 +51,12 @@ function formatPrompt(messages: BaseMessage[]): string {
.join("\n")
}
/**
* To use this model you need to have the `Built-in AI Early Preview Program`
* for Chrome. You can find more information about the program here:
* @link https://developer.chrome.com/docs/ai/built-in
*
* @example
* ```typescript
* // Initialize the ChatChromeAI model.
* const model = new ChatChromeAI({
* temperature: 0.5, // Optional. Default is 0.5.
* topK: 40, // Optional. Default is 40.
* });
*
* // Call the model with a message and await the response.
* const response = await model.invoke([
* new HumanMessage({ content: "My name is John." }),
* ]);
* ```
*/
export class ChatChromeAI extends SimpleChatModel<ChromeAICallOptions> {
session?: AITextSession
temperature = 0.5
temperature = 0.8
topK = 40
topK = 120
promptFormatter: (messages: BaseMessage[]) => string
@ -120,15 +86,14 @@ export class ChatChromeAI extends SimpleChatModel<ChromeAICallOptions> {
throw new Error("ChatChromeAI can only be used in the browser.")
}
const { ai } = window as any
const canCreateTextSession = await ai.canCreateTextSession()
const canCreateTextSession = await checkChromeAIAvailability()
if (canCreateTextSession === AIModelAvailability.No) {
throw new Error("The AI model is not available.")
} else if (canCreateTextSession === AIModelAvailability.AfterDownload) {
throw new Error("The AI model is not yet downloaded.")
}
this.session = await ai.createTextSession({
this.session = await createAITextSession({
topK: this.topK,
temperature: this.temperature
})

View File

@ -0,0 +1,54 @@
export const checkChromeAIAvailability = async (): Promise<"readily" | "no" | "after-download"> => {
try {
const ai = (window as any).ai;
// upcoming version change
if (ai?.assistant?.capabilities) {
const capabilities = await ai.assistant.capabilities();
return capabilities?.available ?? "no";
}
// old version
if (ai?.canCreateTextSession) {
const available = await ai.canCreateTextSession();
return available ?? "no";
}
return "no";
} catch (e) {
console.error("Error checking Chrome AI availability:", e);
return "no";
}
}
export interface AITextSession {
prompt(input: string): Promise<string>
promptStreaming(input: string): ReadableStream
destroy(): void
clone(): AITextSession
}
export const createAITextSession = async (data: any): Promise<AITextSession> => {
const ai = (window as any).ai;
// upcoming version change
if (ai?.assistant?.create) {
const session = await ai.assistant.create({
...data
})
return session
}
// old version
if (ai.createTextSession) {
const session = await ai.createTextSession({
...data
})
return session
}
throw new Error("Chrome AI is not available.")
}

View File

@ -0,0 +1,29 @@
{
"extName": {
"message": "Page Assist - Eine Web-Benutzeroberfläche für lokale KI-Modelle"
},
"extDescription": {
"message": "Nutzen Sie Ihre lokal laufenden KI-Modelle, um Sie beim Surfen im Web zu unterstützen."
},
"openSidePanelToChat": {
"message": "Copilot zum Chatten öffnen"
},
"openOptionToChat": {
"message": "Web-Benutzeroberfläche zum Chatten öffnen"
},
"contextSummarize": {
"message": "Zusammenfassen"
},
"contextExplain": {
"message": "Erklären"
},
"contextRephrase": {
"message": "Umformulieren"
},
"contextTranslate" :{
"message": "Übersetzen"
},
"contextCustom": {
"message": "Benutzerdefiniert"
}
}

View File

@ -3,6 +3,8 @@ import { cleanUrl } from "../libs/clean-url"
import { urlRewriteRuntime } from "../libs/runtime"
import { getChromeAIModel } from "./chrome"
import { setNoOfRetrievedDocs, setTotalFilePerKB } from "./app"
import fetcher from "@/libs/fetcher"
const storage = new Storage()
@ -15,9 +17,9 @@ const DEFAULT_RAG_QUESTION_PROMPT =
const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer:`
const DEFAULT_WEBSEARCH_PROMP = `You are an AI model who is expert at searching the web and answering user's queries.
const DEFAULT_WEBSEARCH_PROMP = `You are an AI model who is expert at searching the web and answering user's queries.
Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}.
Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}.
\`search-results\` block provides knowledge from the web search results. You can use this information to generate a meaningful response.
@ -43,7 +45,7 @@ Follow-up question: Taylor Swift's latest album?
Rephrased question: Name of Taylor Swift's latest album.
Previous Conversation:
Previous Conversation:
{chat_history}
@ -82,7 +84,7 @@ export const defaultModel = async () => {
export const isOllamaRunning = async () => {
try {
const baseUrl = await getOllamaURL()
const response = await fetch(`${cleanUrl(baseUrl)}`)
const response = await fetcher(`${cleanUrl(baseUrl)}`)
if (!response.ok) {
throw new Error(response.statusText)
}
@ -100,7 +102,7 @@ export const getAllModels = async ({
}) => {
try {
const baseUrl = await getOllamaURL()
const response = await fetch(`${cleanUrl(baseUrl)}/api/tags`)
const response = await fetcher(`${cleanUrl(baseUrl)}/api/tags`)
if (!response.ok) {
if (returnEmpty) {
return []
@ -132,7 +134,7 @@ export const getAllModels = async ({
export const deleteModel = async (model: string) => {
const baseUrl = await getOllamaURL()
const response = await fetch(`${cleanUrl(baseUrl)}/api/delete`, {
const response = await fetcher(`${cleanUrl(baseUrl)}/api/delete`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
@ -154,7 +156,7 @@ export const fetchChatModels = async ({
}) => {
try {
const baseUrl = await getOllamaURL()
const response = await fetch(`${cleanUrl(baseUrl)}/api/tags`)
const response = await fetcher(`${cleanUrl(baseUrl)}/api/tags`)
if (!response.ok) {
if (returnEmpty) {
return []

View File

@ -49,8 +49,6 @@ type State = {
setChatMode: (chatMode: "normal" | "rag") => void
isEmbedding: boolean
setIsEmbedding: (isEmbedding: boolean) => void
speechToTextLanguage: string
setSpeechToTextLanguage: (language: string) => void
webSearch: boolean
setWebSearch: (webSearch: boolean) => void
isSearchingInternet: boolean

View File

@ -1,3 +1,5 @@
import { checkChromeAIAvailability } from "@/models/utils/chrome"
export const getChromeAISupported = async () => {
try {
let browserInfo = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)
@ -11,9 +13,8 @@ export const getChromeAISupported = async () => {
return "ai_not_supported"
}
//@ts-ignore
const createSession = await ai?.canCreateGenericSession()
if (createSession !== "readily") {
const capabilities = await checkChromeAIAvailability()
if (capabilities !== "readily") {
return "ai_not_ready"
}

19
src/utils/key-down.tsx Normal file
View File

@ -0,0 +1,19 @@
export const handleChatInputKeyDown = ({
e,
sendWhenEnter,
typing,
isSending
}: {
e: React.KeyboardEvent
typing: boolean
sendWhenEnter: boolean
isSending: boolean
}) => {
return import.meta.env.BROWSER === "firefox"
? e.key === "Enter" &&
!e.shiftKey &&
!e.nativeEvent.isComposing &&
!isSending &&
sendWhenEnter
: !typing && e.key === "Enter" && !e.shiftKey && !isSending && sendWhenEnter
}

View File

@ -1,5 +1,5 @@
import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action"
import fetcher from "@/libs/fetcher"
export const progressHuman = (completed: number, total: number) => {
return ((completed / total) * 100).toFixed(0) + "%"
@ -11,7 +11,7 @@ export const clearBadge = () => {
}
export const streamDownload = async (url: string, model: string) => {
url += "/api/pull"
const response = await fetch(url, {
const response = await fetcher(url, {
method: "POST",
headers: {
"Content-Type": "application/json"

View File

@ -1,7 +1,8 @@
import { cleanUrl } from "~/libs/clean-url"
import fetcher from "@/libs/fetcher"
export const verifyPageShareURL = async (url: string) => {
const res = await fetch(`${cleanUrl(url)}/api/v1/ping`)
const res = await fetcher(`${cleanUrl(url)}/api/v1/ping`)
if (!res.ok) {
throw new Error("Unable to verify page share")
}

View File

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