diff --git a/src/assets/locale/da/settings.json b/src/assets/locale/da/settings.json index 1d088d1..37168c4 100644 --- a/src/assets/locale/da/settings.json +++ b/src/assets/locale/da/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Genoptag den sidste chat, når du åbner SidePanel (copilot)" }, + "webUIResumeLastChat": { + "label": "Genoptag den sidste chat, når du åbner Web UI'en" + }, "hideCurrentChatModelSettings": { "label": "Skjul nuværende chat model indstillinger" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Besøg websitet nævnt i samtalen" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/de/settings.json b/src/assets/locale/de/settings.json index c6b134b..6315f85 100644 --- a/src/assets/locale/de/settings.json +++ b/src/assets/locale/de/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Letzten Chat beim Öffnen des Seitenpanels fortsetzen (Copilot)" }, + "webUIResumeLastChat": { + "label": "Letzten Chat beim Öffnen der Web-UI fortsetzen" + }, "hideCurrentChatModelSettings": { "label": "Aktuelle Chat-Modell-Einstellungen ausblenden" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Die in der Nachricht erwähnte Website besuchen" + }, + "searxng": { + "url": { + "label": "SearXNG-URL" + } } }, "system": { diff --git a/src/assets/locale/en/common.json b/src/assets/locale/en/common.json index d033144..88c1be5 100644 --- a/src/assets/locale/en/common.json +++ b/src/assets/locale/en/common.json @@ -87,6 +87,9 @@ "label": "Top P", "placeholder": "Enter Top P value (e.g. 0.9, 0.95)" }, + "useMMap": { + "label": "useMmap" + }, "numGpu": { "label": "Num GPU", "placeholder": "Enter number of layers to send to GPU(s)" diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json index eb98bf4..f0498df 100644 --- a/src/assets/locale/en/settings.json +++ b/src/assets/locale/en/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Resume the last chat when opening the SidePanel (Copilot)" }, + "webUIResumeLastChat": { + "label": "Resume the last chat when opening the Web UI" + }, "hideCurrentChatModelSettings": { "label": "Hide the current Chat Model Settings" }, @@ -33,8 +36,8 @@ "generateTitle": { "label": "Generate Title using AI" }, - "ollamaStatus" :{ - "label":"Enable or disable Ollama connection status check" + "ollamaStatus": { + "label": "Enable or disable Ollama connection status check" } }, "sidepanelRag": { @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Visit the website mentioned in the message" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/es/settings.json b/src/assets/locale/es/settings.json index 4ed17d8..eb98d80 100644 --- a/src/assets/locale/es/settings.json +++ b/src/assets/locale/es/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Retomar el último chat al abrir el Panel Lateral (Copilot)" }, + "webUIResumeLastChat": { + "label": "Retomar el último chat al abrir la Interfaz Web" + }, "hideCurrentChatModelSettings": { "label": "Ocultar Configuraciones del Modelo de Chat Actual" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Visita el sitio web mencionado en el mensaje" + }, + "searxng": { + "url": { + "label": "URL de SearXNG" + } } }, "system": { diff --git a/src/assets/locale/fa/settings.json b/src/assets/locale/fa/settings.json index 0a0cde8..985639a 100644 --- a/src/assets/locale/fa/settings.json +++ b/src/assets/locale/fa/settings.json @@ -62,6 +62,11 @@ }, "visitSpecificWebsite": { "label": "مراجعه به وب سایت ذکر شده در پیام" + }, + "searxng": { + "url": { + "label": "آدرس SearXNG" + } } }, "system": { diff --git a/src/assets/locale/fr/settings.json b/src/assets/locale/fr/settings.json index 7d3b191..3ef9b49 100644 --- a/src/assets/locale/fr/settings.json +++ b/src/assets/locale/fr/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Reprendre la dernière conversation lors de l'ouverture du sidepanel (Copilot)" }, + "webUIResumeLastChat": { + "label": "Reprendre la dernière conversation lors de l'ouverture de l'interface Web" + }, "hideCurrentChatModelSettings": { "label": "Masquer les paramètres actuels du modèle de chat" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Visitez le site web mentionné dans le message" + }, + "searxng": { + "url": { + "label": "URL SearXNG" + } } }, "system": { diff --git a/src/assets/locale/it/settings.json b/src/assets/locale/it/settings.json index e227d22..4e07754 100644 --- a/src/assets/locale/it/settings.json +++ b/src/assets/locale/it/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Riprendi l'ultima chat quando apri il Pannello Laterale (Copilot)" }, + "webUIResumeLastChat": { + "label": "Riprendi l'ultima chat quando apri l'interfaccia Web" + }, "hideCurrentChatModelSettings": { "label": "Nascondi le impostazioni correnti del modello Chat" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Visita il sito web menzionato nel messaggio" + }, + "searxng": { + "url": { + "label": "URL SearXNG" + } } }, "system": { diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json index 0058062..d34b63b 100644 --- a/src/assets/locale/ja-JP/settings.json +++ b/src/assets/locale/ja-JP/settings.json @@ -24,6 +24,9 @@ "copilotResumeLastChat": { "label": "サイドパネルを開いたときに最後のチャットを再開 (Copilot)" }, + "webUIResumeLastChat": { + "label": "Web UIを開いたときに最後のチャットを再開" + }, "hideCurrentChatModelSettings": { "label": "現在のチャットモデル設定を非表示" }, @@ -65,6 +68,11 @@ }, "visitSpecificWebsite": { "label": "メッセージに記載されたウェブサイトを訪問してください" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/ko/settings.json b/src/assets/locale/ko/settings.json index f3c7109..73ac857 100644 --- a/src/assets/locale/ko/settings.json +++ b/src/assets/locale/ko/settings.json @@ -24,6 +24,9 @@ "copilotResumeLastChat": { "label": "사이드 패널을 열 때 마지막 채팅 재개 (Copilot)" }, + "webUIResumeLastChat": { + "label": "웹 UI를 열 때 마지막 채팅 재개" + }, "hideCurrentChatModelSettings": { "label": "현재 채팅 모델 설정 숨기기" }, @@ -65,6 +68,11 @@ }, "visitSpecificWebsite": { "label": "메시지에 언급된 웹사이트 방문" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json index 03d67f1..b9ccb0d 100644 --- a/src/assets/locale/ml/settings.json +++ b/src/assets/locale/ml/settings.json @@ -24,6 +24,9 @@ "copilotResumeLastChat": { "label": "സൈഡ്പാനൽ തുറക്കുമ്പോൾ അവസാനത്തെ ചാറ്റ് പുനരാരംഭിക്കുക (Copilot)" }, + "webUIResumeLastChat": { + "label": "വെബ് UI തുറക്കുമ്പോൾ അവസാനത്തെ ചാറ്റ് പുനരാരംഭിക്കുക" + }, "hideCurrentChatModelSettings": { "label": "നിലവിലുള്ള ചാറ്റ് മോഡൽ ക്രമീകരണങ്ങൾ മറയ്ക്കുക" }, @@ -65,6 +68,11 @@ }, "visitSpecificWebsite": { "label": "സന്ദേശത്തിൽ പറയുന്ന വെബ്സൈറ്റ് സന്ദർശിക്കുക." + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/no/settings.json b/src/assets/locale/no/settings.json index 8085e3e..c73cbec 100644 --- a/src/assets/locale/no/settings.json +++ b/src/assets/locale/no/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Gjenoppta siste chat ved åpning av SidePanel (copilot)" }, + "webUIResumeLastChat": { + "label": "Gjenoppta siste chat når Web UI åpnes" + }, "hideCurrentChatModelSettings": { "label": "Skjul gjeldende chat modell innstillinger" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Besøk nettstedet nevnt i samtalen" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { @@ -241,25 +249,104 @@ }, "ollamaSettings": { "title": "Ollama Innstillinger", - "heading": "Konfigurer Ollama Plugin", - "enabled": { - "label": "Aktiver Ollama Plugin" - }, - "provider": { - "label": "Ollama Tilbyder", - "placeholder": "Velg en Ollama tilbyder" - }, - "apiKey": { - "label": "Ollama API Nøkkel", - "placeholder": "Skriv inn Ollama API Nøkkel", - "required": "API Nøkkel er påkrevd" - }, - "saveBtn": { - "save": "Lagre" - }, - "notification": { - "saveSuccess": "Ollama Plugin lagret vellykket", - "someError": "Noe gikk galt. Vennligst prøv igjen senere" + "heading": "Konfigurer Ollama", + "settings": { + "ollamaUrl": { + "label": "Ollama URL", + "placeholder": "Skriv inn Ollama URL" + }, + "advanced": { + "label": "Avansert Ollama URL-konfigurasjon", + "urlRewriteEnabled": { + "label": "Aktiver eller deaktiver tilpasset opprinnelses-URL" + }, + "rewriteUrl": { + "label": "Tilpasset opprinnelses-URL", + "placeholder": "Skriv inn tilpasset opprinnelses-URL" + }, + "headers": { + "label": "Tilpass Headers", + "LeggTil": "Legg til Header", + "key": { + "label": "Header Nøkkel", + "placeholder": "Autorisasjon" + }, + "value": { + "label": "Header Verdi", + "placeholder": "Bearer token" + } + }, + "help": "Hvis du har forbindelsesproblemer med Ollama på Page Assist, kan du konfigurere en brukerdefinert opprinnelses-URL. For mer informasjon om konfigurasjonen, klikk her." + } } + }, + "manageSearch": { + "title": "Administrer Web Søk", + "heading": "Konfigurer Web Søk" + }, + "about": { + "title": "Om", + "heading": "Om", + "chromeVersion": "Page Assist Versjon", + "ollamaVersion": "Ollama Versjon", + "support": "Du kan støtte Page Assist-prosjektet ved å donere eller sponse via følgende plattformer:", + "koFi": "Støtt på Ko-fi", + "githubSponsor": "Spons på GitHub", + "githubRepo": "GitHub Repository" + }, + "manageKnowledge": { + "title": "Administrer Kunnskap", + "heading": "Konfigurer Kunnskapsbase" + }, + "rag": { + "title": "RAG Innstillinger", + "ragSettings": { + "label": "RAG Innstillinger", + "model": { + "label": "Embedding Modell", + "required": "Vennligst velg en modell", + "help": "Det anbefales sterkt å bruke embeddingsmodeller som `nomic-embed-text`.", + "placeholder": "Velg en modell" + }, + "chunkSize": { + "label": "Delstørrelse", + "placeholder": "Skriv inn delstørrelse", + "required": "Vennligst skriv inn en delstørrelse" + }, + "chunkOverlap": { + "label": "Deloverlapp", + "placeholder": "Skriv inn deloverlapp", + "required": "Vennligst skriv inn deloverlapp" + }, + "totalFilePerKB": { + "label": "Kunnskapsbase standard filopplastingsgrense", + "placeholder": "Skriv inn standard filopplastingsgrense (f.eks. 10)", + "required": "Vennligst skriv inn standard filopplastingsgrense" + }, + "noOfRetrievedDocs": { + "label": "Antall hentede dokumenter", + "placeholder": "Skriv inn antall hentede dokumenter", + "required": "Vennligst skriv inn antall hentede dokumenter" + } + }, + "prompt": { + "label": "Konfigurer RAG Prompt", + "option1": "Normal", + "option2": "Web", + "alert": "Konfigurering av systemprompt her er foreldet. Vennligst bruk Administrer Prompts-seksjonen for å legge til eller endre prompts. Denne seksjonen vil bli fjernet i fremtidige versjoner.", + "systemPrompt": "Systemprompt", + "systemPromptPlaceholder": "Skriv inn systemprompt", + "webSearchPrompt": "Websøke-prompt", + "webSearchPromptHelp": "Ikke fjern `{search_results}` fra prompten.", + "webSearchPromptError": "Vennligst skriv inn en websøke-prompt", + "webSearchPromptPlaceholder": "Skriv inn websøke-prompt", + "webSearchFollowUpPrompt": "Oppfølgingsprompt for websøking", + "webSearchFollowUpPromptHelp": "Ikke fjern `{chat_history}` og `{question}` fra prompten.", + "webSearchFollowUpPromptError": "Vennligst skriv inn din oppfølgingsprompt for websøking!", + "webSearchFollowUpPromptPlaceholder": "Din oppfølgingsprompt for websøking" + } + }, + "chromeAiSettings": { + "title": "Chrome AI Innstillinger" } } diff --git a/src/assets/locale/pt-BR/settings.json b/src/assets/locale/pt-BR/settings.json index fad3b11..019ad68 100644 --- a/src/assets/locale/pt-BR/settings.json +++ b/src/assets/locale/pt-BR/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Retomar o último chat ao abrir o Painel Lateral (Copilot)" }, + "webUIResumeLastChat": { + "label": "Retomar o último chat ao abrir a Interface Web" + }, "hideCurrentChatModelSettings": { "label": "Ocultar as Configurações Atuais do Modelo de Chat" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Visitar o site mencionado na mensagem" + }, + "searxng": { + "url": { + "label": "URL do SearXNG" + } } }, "system": { diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json index adf0008..7e6b75b 100644 --- a/src/assets/locale/ru/settings.json +++ b/src/assets/locale/ru/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Возобновить последний чат при открытии боковой панели (Copilot)" }, + "webUIResumeLastChat": { + "label": "Возобновить последний чат при открытии веб-интерфейса" + }, "hideCurrentChatModelSettings": { "label": "Скрыть текущие настройки модели чата" }, @@ -63,6 +66,11 @@ }, "visitSpecificWebsite": { "label": "Посетите веб-сайт, указанный в сообщении." + }, + "searxng": { + "url": { + "label": "URL-адрес SearXNG" + } } }, "system": { diff --git a/src/assets/locale/sv/settings.json b/src/assets/locale/sv/settings.json index d1e11d0..3edaa52 100644 --- a/src/assets/locale/sv/settings.json +++ b/src/assets/locale/sv/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Återuppta den senaste chatten när du öppnar sidopanelen (Copilot)" }, + "webUIResumeLastChat": { + "label": "Återuppta den senaste chatten när du öppnar webbgränssnittet" + }, "hideCurrentChatModelSettings": { "label": "Göm de nuvarande chattmodellinställningarna" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Besök webbplatsen som nämns i meddelandet" + }, + "searxng": { + "url": { + "label": "SearXNG URL" + } } }, "system": { diff --git a/src/assets/locale/uk/settings.json b/src/assets/locale/uk/settings.json index db0695b..91fe5de 100644 --- a/src/assets/locale/uk/settings.json +++ b/src/assets/locale/uk/settings.json @@ -21,6 +21,9 @@ "copilotResumeLastChat": { "label": "Поновити останню розмову при відкритті бічної панелі (Copilot)" }, + "webUIResumeLastChat": { + "label": "Поновити останню розмову при відкритті веб-інтерфейсу" + }, "hideCurrentChatModelSettings": { "label": "Приховати налаштування поточної моделі чату" }, @@ -62,6 +65,11 @@ }, "visitSpecificWebsite": { "label": "Відвідати веб-сайт, згаданий у повідомленні" + }, + "searxng": { + "url": { + "label": "SearXNG URL-адреса" + } } }, "system": { diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json index 2f834e9..83c576d 100644 --- a/src/assets/locale/zh/settings.json +++ b/src/assets/locale/zh/settings.json @@ -24,6 +24,9 @@ "copilotResumeLastChat": { "label": "打开侧边栏时恢复上次聊天(Copilot)" }, + "webUIResumeLastChat": { + "label": "打开Web UI时恢复上次聊天" + }, "hideCurrentChatModelSettings": { "label": "隐藏当前聊天模型设置" }, @@ -65,6 +68,11 @@ }, "visitSpecificWebsite": { "label": "访问消息中提到的网站。" + }, + "searxng": { + "url": { + "label": "SearXNG 网址" + } } }, "system": { diff --git a/src/components/Common/Settings/CurrentChatModelSettings.tsx b/src/components/Common/Settings/CurrentChatModelSettings.tsx index e950bd9..b1218b4 100644 --- a/src/components/Common/Settings/CurrentChatModelSettings.tsx +++ b/src/components/Common/Settings/CurrentChatModelSettings.tsx @@ -5,15 +5,19 @@ import { useStoreChatModelSettings } from "@/store/model" import { useQuery } from "@tanstack/react-query" import { Collapse, + Divider, Drawer, Form, Input, InputNumber, Modal, - Skeleton + Skeleton, + Switch, + Button } from "antd" -import React from "react" +import React, { useState, useCallback } from "react" import { useTranslation } from "react-i18next" +import { SaveButton } from "../SaveButton" type Props = { open: boolean @@ -30,12 +34,31 @@ export const CurrentChatModelSettings = ({ const [form] = Form.useForm() const cUserSettings = useStoreChatModelSettings() const { selectedSystemPrompt } = useMessageOption() + + const savePrompt = useCallback( + (value: string) => { + cUserSettings.setX("systemPrompt", value) + }, + [cUserSettings] + ) + + const saveSettings = useCallback( + (values: any) => { + Object.entries(values).forEach(([key, value]) => { + if (key !== "systemPrompt") { + cUserSettings.setX(key, value) + } + }) + }, + [cUserSettings] + ) + const { isPending: isLoading } = useQuery({ queryKey: ["fetchModelConfig2", open], queryFn: async () => { const data = await getAllModelSettings() - let tempSystemPrompt = ""; + let tempSystemPrompt = "" // i hate this method but i need this feature so badly that i need to do this if (selectedSystemPrompt) { @@ -52,7 +75,8 @@ export const CurrentChatModelSettings = ({ seed: cUserSettings.seed, numGpu: cUserSettings.numGpu ?? data.numGpu, numPredict: cUserSettings.numPredict ?? data.numPredict, - systemPrompt: cUserSettings.systemPrompt ?? tempSystemPrompt + systemPrompt: cUserSettings.systemPrompt ?? tempSystemPrompt, + useMMap: cUserSettings.useMMap ?? data.useMMap }) return data }, @@ -61,25 +85,17 @@ export const CurrentChatModelSettings = ({ refetchOnWindowFocus: false }) - const renderBody = () => { return ( <> {!isLoading ? (
{ - Object.entries(values).forEach(([key, value]) => { - cUserSettings.setX(key, value) - setOpen(false) - }) - }} form={form} - layout="vertical"> + layout="vertical" + onFinish={(values) => { + saveSettings(values) + setOpen(false) + }}> {useDrawer && ( <> savePrompt(e.target.value)} /> + )} + + @@ -139,6 +159,8 @@ export const CurrentChatModelSettings = ({ /> + + + + + + ) } ]} /> - - + ) : ( diff --git a/src/components/Option/Playground/Playground.tsx b/src/components/Option/Playground/Playground.tsx index bc52b59..b97dae9 100644 --- a/src/components/Option/Playground/Playground.tsx +++ b/src/components/Option/Playground/Playground.tsx @@ -2,11 +2,18 @@ import React from "react" import { PlaygroundForm } from "./PlaygroundForm" import { PlaygroundChat } from "./PlaygroundChat" import { useMessageOption } from "@/hooks/useMessageOption" +import { webUIResumeLastChat } from "@/services/app" +import { + formatToChatHistory, + formatToMessage, + getRecentChatFromWebUI +} from "@/db" export const Playground = () => { const drop = React.useRef(null) const [dropedFile, setDropedFile] = React.useState() - const { selectedKnowledge } = useMessageOption() + const { selectedKnowledge, messages, setHistoryId, setHistory, setMessages } = + useMessageOption() const [dropState, setDropState] = React.useState< "idle" | "dragging" | "error" @@ -71,6 +78,26 @@ export const Playground = () => { } } }, [selectedKnowledge]) + + const setRecentMessagesOnLoad = async () => { + const isEnabled = await webUIResumeLastChat() + if (!isEnabled) { + return + } + if (messages.length === 0) { + const recentChat = await getRecentChatFromWebUI() + if (recentChat) { + setHistoryId(recentChat.history.id) + setHistory(formatToChatHistory(recentChat.messages)) + setMessages(formatToMessage(recentChat.messages)) + } + } + } + + React.useEffect(() => { + setRecentMessagesOnLoad() + }, []) + return (
{ dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800 z-10" : "" } bg-white dark:bg-[#171717]`}> - +
diff --git a/src/components/Option/Playground/PlaygroundEmpty.tsx b/src/components/Option/Playground/PlaygroundEmpty.tsx index 4e23d7d..0b772f6 100644 --- a/src/components/Option/Playground/PlaygroundEmpty.tsx +++ b/src/components/Option/Playground/PlaygroundEmpty.tsx @@ -39,6 +39,13 @@ export const PlaygroundEmpty = () => { enabled: checkOllamaStatus }) + useEffect(() => { + if (ollamaInfo?.ollamaURL) { + setOllamaURL(ollamaInfo.ollamaURL) + } + }, [ollamaInfo]) + + if (!checkOllamaStatus) { return (
diff --git a/src/components/Option/Settings/general-settings.tsx b/src/components/Option/Settings/general-settings.tsx index f615236..c471fd4 100644 --- a/src/components/Option/Settings/general-settings.tsx +++ b/src/components/Option/Settings/general-settings.tsx @@ -27,6 +27,11 @@ export const GeneralSettings = () => { false ) + const [webUIResumeLastChat, setWebUIResumeLastChat] = useStorage( + "webUIResumeLastChat", + false + ) + const [restoreLastChatModel, setRestoreLastChatModel] = useStorage( "restoreLastChatModel", false @@ -113,6 +118,17 @@ export const GeneralSettings = () => { onChange={(checked) => setCopilotResumeLastChat(checked)} />
+
+
+ + {t("generalSettings.settings.webUIResumeLastChat.label")} + +
+ setWebUIResumeLastChat(checked)} + /> +
diff --git a/src/components/Option/Settings/model-settings.tsx b/src/components/Option/Settings/model-settings.tsx index 40523f7..18015ba 100644 --- a/src/components/Option/Settings/model-settings.tsx +++ b/src/components/Option/Settings/model-settings.tsx @@ -2,7 +2,7 @@ import { BetaTag } from "@/components/Common/Beta" import { SaveButton } from "@/components/Common/SaveButton" import { getAllModelSettings, setModelSetting } from "@/services/model-settings" import { useQuery, useQueryClient } from "@tanstack/react-query" -import { Form, Skeleton, Input, InputNumber, Collapse } from "antd" +import { Form, Skeleton, Input, InputNumber, Collapse, Switch } from "antd" import React from "react" import { useTranslation } from "react-i18next" @@ -78,13 +78,13 @@ export const ModelSettings = () => { /> - - + name="numPredict" + label={t("modelSettings.form.numPredict.label")}> + + { + + + ) } diff --git a/src/components/Option/Settings/search-mode.tsx b/src/components/Option/Settings/search-mode.tsx index 53f391b..69ae112 100644 --- a/src/components/Option/Settings/search-mode.tsx +++ b/src/components/Option/Settings/search-mode.tsx @@ -3,7 +3,7 @@ import { getSearchSettings, setSearchSettings } from "@/services/search" import { SUPPORTED_SERACH_PROVIDERS } from "@/utils/search-provider" import { useForm } from "@mantine/form" import { useQuery, useQueryClient } from "@tanstack/react-query" -import { Select, Skeleton, Switch, InputNumber } from "antd" +import { Select, Skeleton, Switch, InputNumber, Input } from "antd" import { useTranslation } from "react-i18next" export const SearchModeSettings = () => { @@ -14,7 +14,9 @@ export const SearchModeSettings = () => { isSimpleInternetSearch: false, searchProvider: "", totalSearchResults: 0, - visitSpecificWebsite: false + visitSpecificWebsite: false, + searxngURL: "", + searxngJSONMode: false } }) @@ -62,6 +64,23 @@ export const SearchModeSettings = () => { />
+ {form.values.searchProvider === "searxng" && ( + <> +
+ + {t("generalSettings.webSearch.searxng.url.label")} + +
+ +
+
+ + )}
{t("generalSettings.webSearch.searchMode.label")} diff --git a/src/db/index.ts b/src/db/index.ts index da837b2..6cab3ef 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -356,11 +356,11 @@ export const updateMessageByIndex = async ( message: string ) => { try { - const db = new PageAssitDatabase() - const chatHistory = (await db.getChatHistory(history_id)).reverse() - chatHistory[index].content = message - await db.db.set({ [history_id]: chatHistory.reverse() }) - } catch(e) { + const db = new PageAssitDatabase() + const chatHistory = (await db.getChatHistory(history_id)).reverse() + chatHistory[index].content = message + await db.db.set({ [history_id]: chatHistory.reverse() }) + } catch (e) { // temp chat will break } } @@ -515,6 +515,20 @@ export const getRecentChatFromCopilot = async () => { return { history, messages } } +export const getRecentChatFromWebUI = async () => { + const db = new PageAssitDatabase() + const chatHistories = await db.getChatHistories() + if (chatHistories.length === 0) return null + const history = chatHistories.find( + (history) => history.message_source === "web-ui" + ) + if (!history) return null + + const messages = await db.getChatHistory(history.id) + + return { history, messages } +} + export const getTitleById = async (id: string) => { const db = new PageAssitDatabase() const title = await db.getChatHistoryTitleById(id) diff --git a/src/hooks/useMessage.tsx b/src/hooks/useMessage.tsx index eb17f3e..c95554d 100644 --- a/src/hooks/useMessage.tsx +++ b/src/hooks/useMessage.tsx @@ -139,7 +139,9 @@ export const useMessage = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -271,7 +273,10 @@ export const useMessage = () => { userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? + userDefaultModelSettings?.useMMap }) const response = await questionOllama.invoke(promptForQuestion) query = response.content.toString() @@ -482,7 +487,9 @@ export const useMessage = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -526,7 +533,7 @@ export const useMessage = () => { const prompt = await systemPromptForNonRag() const selectedPrompt = await getPromptById(selectedSystemPrompt) - const applicationChatHistory = generateHistory(history, selectedModel) + const applicationChatHistory = [] const data = await getScreenshotFromCurrentTab() console.log( @@ -716,7 +723,9 @@ export const useMessage = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -944,7 +953,9 @@ export const useMessage = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -1023,7 +1034,10 @@ export const useMessage = () => { userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? + userDefaultModelSettings?.useMMap }) const response = await questionOllama.invoke(promptForQuestion) query = response.content.toString() @@ -1211,7 +1225,9 @@ export const useMessage = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] diff --git a/src/hooks/useMessageOption.tsx b/src/hooks/useMessageOption.tsx index 322287c..d687344 100644 --- a/src/hooks/useMessageOption.tsx +++ b/src/hooks/useMessageOption.tsx @@ -130,7 +130,9 @@ export const useMessageOption = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -209,7 +211,10 @@ export const useMessageOption = () => { userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? + userDefaultModelSettings?.useMMap }) const response = await questionOllama.invoke(promptForQuestion) query = response.content.toString() @@ -429,7 +434,9 @@ export const useMessageOption = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -667,7 +674,9 @@ export const useMessageOption = () => { currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap }) let newMessage: Message[] = [] @@ -762,7 +771,10 @@ export const useMessageOption = () => { userDefaultModelSettings?.numGpu, numPredict: currentChatModelSettings?.numPredict ?? - userDefaultModelSettings?.numPredict + userDefaultModelSettings?.numPredict, + useMMap: + currentChatModelSettings?.useMMap ?? + userDefaultModelSettings?.useMMap }) const response = await questionOllama.invoke(promptForQuestion) query = response.content.toString() diff --git a/src/models/index.ts b/src/models/index.ts index 135025f..3481d52 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -16,6 +16,7 @@ export const pageAssistModel = async ({ seed, numGpu, numPredict, + useMMap }: { model: string baseUrl: string @@ -27,6 +28,7 @@ export const pageAssistModel = async ({ seed?: number numGpu?: number numPredict?: number + useMMap?: boolean }) => { if (model === "chrome::gemini-nano::page-assist") { @@ -73,7 +75,8 @@ export const pageAssistModel = async ({ seed, model, numGpu, - numPredict + numPredict, + useMMap, }) diff --git a/src/services/app.ts b/src/services/app.ts index bbae388..647abab 100644 --- a/src/services/app.ts +++ b/src/services/app.ts @@ -39,6 +39,10 @@ export const copilotResumeLastChat = async () => { return await storage.get("copilotResumeLastChat") } +export const webUIResumeLastChat = async () => { + return await storage.get("webUIResumeLastChat") +} + export const defaultSidebarOpen = async () => { const sidebarOpen = await storage.get("sidebarOpen") if (!sidebarOpen || sidebarOpen === "") { diff --git a/src/services/search.ts b/src/services/search.ts index f548cfe..66fb5cf 100644 --- a/src/services/search.ts +++ b/src/services/search.ts @@ -62,20 +62,45 @@ export const setTotalSearchResults = async (totalSearchResults: number) => { await storage.set("totalSearchResults", totalSearchResults.toString()) } +export const getSearxngURL = async () => { + const searxngURL = await storage.get("searxngURL") + return searxngURL || "" +} + +export const isSearxngJSONMode = async () => { + const searxngJSONMode = await storage.get("searxngJSONMode") + return searxngJSONMode ?? false +} + +export const setSearxngJSONMode = async (searxngJSONMode: boolean) => { + await storage.set("searxngJSONMode", searxngJSONMode) +} + +export const setSearxngURL = async (searxngURL: string) => { + await storage.set("searxngURL", searxngURL) +} + export const getSearchSettings = async () => { - const [isSimpleInternetSearch, searchProvider, totalSearchResult, visitSpecificWebsite] = + const [isSimpleInternetSearch, searchProvider, totalSearchResult, visitSpecificWebsite, + searxngURL, + searxngJSONMode + ] = await Promise.all([ getIsSimpleInternetSearch(), getSearchProvider(), totalSearchResults(), - getIsVisitSpecificWebsite() + getIsVisitSpecificWebsite(), + getSearxngURL(), + isSearxngJSONMode() ]) return { isSimpleInternetSearch, searchProvider, totalSearchResults: totalSearchResult, - visitSpecificWebsite + visitSpecificWebsite, + searxngURL, + searxngJSONMode } } @@ -83,17 +108,23 @@ export const setSearchSettings = async ({ isSimpleInternetSearch, searchProvider, totalSearchResults, - visitSpecificWebsite + visitSpecificWebsite, + searxngJSONMode, + searxngURL }: { isSimpleInternetSearch: boolean searchProvider: string totalSearchResults: number visitSpecificWebsite: boolean + searxngURL: string + searxngJSONMode: boolean }) => { await Promise.all([ setIsSimpleInternetSearch(isSimpleInternetSearch), setSearchProvider(searchProvider), setTotalSearchResults(totalSearchResults), - setIsVisitSpecificWebsite(visitSpecificWebsite) + setIsVisitSpecificWebsite(visitSpecificWebsite), + setSearxngJSONMode(searxngJSONMode), + setSearxngURL(searxngURL) ]) } diff --git a/src/utils/search-provider.ts b/src/utils/search-provider.ts index d623df2..404c13f 100644 --- a/src/utils/search-provider.ts +++ b/src/utils/search-provider.ts @@ -14,5 +14,9 @@ export const SUPPORTED_SERACH_PROVIDERS = [ { label: "Brave", value: "brave" + }, + { + label: "Searxng", + value: "searxng" } ] \ No newline at end of file diff --git a/src/web/search-engines/searxng.ts b/src/web/search-engines/searxng.ts new file mode 100644 index 0000000..0dc2e64 --- /dev/null +++ b/src/web/search-engines/searxng.ts @@ -0,0 +1,158 @@ +import { urlRewriteRuntime } from "~/libs/runtime" +import { cleanUrl } from "~/libs/clean-url" +import { getSearxngURL, isSearxngJSONMode, getIsSimpleInternetSearch, totalSearchResults } from "@/services/search" +import { pageAssistEmbeddingModel } from "@/models/embedding" +import type { Document } from "@langchain/core/documents" +import { RecursiveCharacterTextSplitter } from "langchain/text_splitter" +import { MemoryVectorStore } from "langchain/vectorstores/memory" +import { PageAssistHtmlLoader } from "~/loader/html" +import { + defaultEmbeddingChunkOverlap, + defaultEmbeddingChunkSize, + defaultEmbeddingModelForRag, + getOllamaURL +} from "~/services/ollama" + +interface SearxNGJSONResult { + title: string + url: string + content: string +} + +interface SearxNGJSONResponse { + results: SearxNGJSONResult[] +} + +export const searxngSearch = async (query: string) => { + const searxngURL = await getSearxngURL() + if (!searxngURL) { + throw new Error("SearXNG URL not configured") + } + + const isJSONMode = await isSearxngJSONMode() + const results = isJSONMode + ? await searxngJSONSearch(searxngURL, query) + : await searxngWebSearch(searxngURL, query) + + const TOTAL_SEARCH_RESULTS = await totalSearchResults() + const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS) + + const isSimpleMode = await getIsSimpleInternetSearch() + + if (isSimpleMode) { + await getOllamaURL() + return searchResults.map((result) => { + return { + url: result.link, + content: result.content + } + }) + } + + const docs: Document>[] = [] + try { + for (const result of searchResults) { + const loader = new PageAssistHtmlLoader({ + html: "", + url: result.link + }) + + const documents = await loader.loadByURL() + documents.forEach((doc) => { + docs.push(doc) + }) + } + } catch (error) { + console.error(error) + } + + const ollamaUrl = await getOllamaURL() + const embeddingModel = await defaultEmbeddingModelForRag() + const ollamaEmbedding = await pageAssistEmbeddingModel({ + model: embeddingModel || "", + 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, 3) + + const searchResult = resultsWithEmbeddings.map((result) => { + return { + url: result.metadata.url, + content: result.pageContent + } + }) + + return searchResult +} + +const searxngJSONSearch = async (baseURL: string, query: string) => { + const searchURL = `${cleanUrl(baseURL)}?q=${encodeURIComponent(query)}&format=json` + + const abortController = new AbortController() + setTimeout(() => abortController.abort(), 20000) + + try { + const response = await fetch(searchURL, { + signal: abortController.signal, + headers: { + 'Accept': 'application/json' + } + }) + + if (!response.ok) { + throw new Error(`SearXNG search failed: ${response.statusText}`) + } + + const data = await response.json() as SearxNGJSONResponse + + return data.results.map(result => ({ + title: result.title, + link: result.url, + content: result.content + })) + } catch (error) { + console.error('SearXNG JSON search failed:', error) + return [] + } +} + +const searxngWebSearch = async (baseURL: string, query: string) => { + const searchURL = `${cleanUrl(baseURL)}?q=${encodeURIComponent(query)}` + + await urlRewriteRuntime(cleanUrl(searchURL), "searxng") + + const abortController = new AbortController() + setTimeout(() => abortController.abort(), 10000) + + try { + const htmlString = await fetch(searchURL, { + signal: abortController.signal + }).then(response => response.text()) + + const parser = new DOMParser() + const doc = parser.parseFromString(htmlString, "text/html") + + const searchResults = Array.from(doc.querySelectorAll("article.result")).map(result => { + const title = result.querySelector("h3")?.textContent?.trim() + const link = result.querySelector("a.url_header")?.getAttribute("href") + const content = result.querySelector("p.content")?.textContent?.trim() + return { title, link, content } + }).filter(result => result.title && result.link && result.content) + + return searchResults + } catch (error) { + console.error('SearXNG web search failed:', error) + return [] + } +} diff --git a/src/web/web.ts b/src/web/web.ts index 88aabc4..6c48f1b 100644 --- a/src/web/web.ts +++ b/src/web/web.ts @@ -5,6 +5,7 @@ import { getIsVisitSpecificWebsite, getSearchProvider } from "@/services/search" import { webSogouSearch } from "./search-engines/sogou" import { webBraveSearch } from "./search-engines/brave" import { getWebsiteFromQuery, processSingleWebsite } from "./website" +import { searxngSearch } from "./search-engines/searxng" const getHostName = (url: string) => { try { @@ -23,6 +24,8 @@ const searchWeb = (provider: string, query: string) => { return webSogouSearch(query) case "brave": return webBraveSearch(query) + case "searxng": + return searxngSearch(query) default: return webGoogleSearch(query) } @@ -51,7 +54,6 @@ export const getSystemPromptForWeb = async (query: string) => { } - const search_results = search .map( (result, idx) => diff --git a/wxt.config.ts b/wxt.config.ts index 18b2a44..abf8cda 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -50,7 +50,7 @@ export default defineConfig({ outDir: "build", manifest: { - version: "1.3.5", + version: "1.3.6", name: process.env.TARGET === "firefox" ? "Page Assist - A Web UI for Local AI Models"