Merge pull request #153 from n4ze3m/next

v1.1.16
This commit is contained in:
Muhammed Nazeem 2024-07-23 22:49:38 +05:30 committed by GitHub
commit 325913cc9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 424 additions and 165 deletions

View File

@ -31,6 +31,16 @@
"label": "Send Notification After Finishing Processing the Knowledge Base" "label": "Send Notification After Finishing Processing the Knowledge Base"
} }
}, },
"sidepanelRag": {
"heading": "Copilot Chat With Website Settings",
"ragEnabled": {
"label": "Chat with website using vector embeddings"
},
"maxWebsiteContext": {
"label": "Normal mode website content size",
"placeholder": "Content size (default 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Manage Web Search", "heading": "Manage Web Search",
"searchMode": { "searchMode": {
@ -288,6 +298,11 @@
"label": "Chunk Overlap", "label": "Chunk Overlap",
"placeholder": "Enter Chunk Overlap", "placeholder": "Enter Chunk Overlap",
"required": "Please enter a chunk overlap" "required": "Please enter a chunk overlap"
},
"totalFilePerKB": {
"label": "Knowledge Base Default File Limit",
"placeholder": "Enter default file limit (e.g., 10)",
"required": "Please enter the default file limit"
} }
}, },
"prompt": { "prompt": {

View File

@ -31,6 +31,16 @@
"label": "Enviar notificación después de terminar el procesamiento de la base de conocimientos" "label": "Enviar notificación después de terminar el procesamiento de la base de conocimientos"
} }
}, },
"sidepanelRag": {
"heading": "Configuración del Chat Copilot con Sitio Web",
"ragEnabled": {
"label": "Chatear con el sitio web usando incrustaciones vectoriales"
},
"maxWebsiteContext": {
"label": "Tamaño del contenido del sitio web en modo normal",
"placeholder": "Tamaño del contenido (predeterminado 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Manejo de la busqueda Web", "heading": "Manejo de la busqueda Web",
"searchMode": { "searchMode": {
@ -288,6 +298,11 @@
"label": "Solapamiento del Chunk", "label": "Solapamiento del Chunk",
"placeholder": "Ingrese el solapamiento del chunk", "placeholder": "Ingrese el solapamiento del chunk",
"required": "Por favor, ingresar el solapamiento del chunk" "required": "Por favor, ingresar el solapamiento del chunk"
},
"totalFilePerKB": {
"label": "Límite predeterminado de archivos de la base de conocimientos",
"placeholder": "Ingrese el límite predeterminado de archivos (ej. 10)",
"required": "Por favor, ingrese el límite predeterminado de archivos"
} }
}, },
"prompt": { "prompt": {

View File

@ -31,6 +31,16 @@
"label": "Envoyer une notification après avoir terminé le traitement de la base de connaissances" "label": "Envoyer une notification après avoir terminé le traitement de la base de connaissances"
} }
}, },
"sidepanelRag": {
"heading": "Paramètres du Chat Copilot avec le Site Web",
"ragEnabled": {
"label": "Discuter avec le site web en utilisant des embeddings vectoriels"
},
"maxWebsiteContext": {
"label": "Taille du contenu du site web en mode normal",
"placeholder": "Taille du contenu (par défaut 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Gérer la recherche Web", "heading": "Gérer la recherche Web",
"searchMode": { "searchMode": {
@ -288,6 +298,11 @@
"label": "Chevauchement", "label": "Chevauchement",
"placeholder": "Entrez le chevauchement des morceaux", "placeholder": "Entrez le chevauchement des morceaux",
"required": "Veuillez saisir un chevauchement" "required": "Veuillez saisir un chevauchement"
},
"totalFilePerKB": {
"label": "Limite par défaut de fichiers de la base de connaissances",
"placeholder": "Entrez la limite par défaut de fichiers (ex. 10)",
"required": "Veuillez entrer la limite par défaut de fichiers"
} }
}, },
"prompt": { "prompt": {

View File

@ -31,6 +31,16 @@
"label": "Inviare notifica dopo aver terminato l'elaborazione della base di conoscenza" "label": "Inviare notifica dopo aver terminato l'elaborazione della base di conoscenza"
} }
}, },
"sidepanelRag": {
"heading": "Impostazioni Chat Copilot con Sito Web",
"ragEnabled": {
"label": "Chatta con il sito web utilizzando embedding vettoriali"
},
"maxWebsiteContext": {
"label": "Dimensione del contenuto del sito web in modalità normale",
"placeholder": "Dimensione del contenuto (predefinito 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Gestione ricerca Web", "heading": "Gestione ricerca Web",
"searchMode": { "searchMode": {
@ -288,6 +298,11 @@
"label": "Sovrapposizione del Blocco (Chunk Overlap)", "label": "Sovrapposizione del Blocco (Chunk Overlap)",
"placeholder": "Inserisci la Sovrapposizione del Blocco (Chunk Overlap)", "placeholder": "Inserisci la Sovrapposizione del Blocco (Chunk Overlap)",
"required": "Inserisci la Sovrapposizione del Blocco" "required": "Inserisci la Sovrapposizione del Blocco"
},
"totalFilePerKB": {
"label": "Limite predefinito di file della base di conoscenza",
"placeholder": "Inserisci il limite predefinito di file (es. 10)",
"required": "Inserisci il limite predefinito di file"
} }
}, },
"prompt": { "prompt": {

View File

@ -34,6 +34,16 @@
"label": "ナレッジベースの処理完了後に通知を送信" "label": "ナレッジベースの処理完了後に通知を送信"
} }
}, },
"sidepanelRag": {
"heading": "ウェブサイトとのCopilotチャット設定",
"ragEnabled": {
"label": "ベクトル埋め込みを使用してウェブサイトとチャットする"
},
"maxWebsiteContext": {
"label": "通常モードのウェブサイトコンテンツサイズ",
"placeholder": "コンテンツサイズデフォルト4028"
}
},
"webSearch": { "webSearch": {
"heading": "ウェブ検索を管理する", "heading": "ウェブ検索を管理する",
"searchMode": { "searchMode": {
@ -291,6 +301,11 @@
"label": "チャンクオーバーラップ", "label": "チャンクオーバーラップ",
"placeholder": "チャンクオーバーラップを入力", "placeholder": "チャンクオーバーラップを入力",
"required": "チャンクオーバーラップを入力してください" "required": "チャンクオーバーラップを入力してください"
},
"totalFilePerKB": {
"label": "ナレッジベースのデフォルトファイル制限",
"placeholder": "デフォルトのファイル制限を入力してください10",
"required": "デフォルトのファイル制限を入力してください"
} }
}, },
"prompt": { "prompt": {

View File

@ -34,6 +34,16 @@
"label": "അറിവ് ശേഖരം പ്രോസസ്സ് ചെയ്ത് കഴിഞ്ഞതിന് ശേഷം അറിയിപ്പ് അയയ്ക്കുക" "label": "അറിവ് ശേഖരം പ്രോസസ്സ് ചെയ്ത് കഴിഞ്ഞതിന് ശേഷം അറിയിപ്പ് അയയ്ക്കുക"
} }
}, },
"sidepanelRag": {
"heading": "വെബ്‌സൈറ്റുമായുള്ള കോപൈലറ്റ് ചാറ്റ് ക്രമീകരണങ്ങൾ",
"ragEnabled": {
"label": "വെക്ടർ എംബെഡിംഗുകൾ ഉപയോഗിച്ച് വെബ്‌സൈറ്റുമായി ചാറ്റ് ചെയ്യുക"
},
"maxWebsiteContext": {
"label": "സാധാരണ മോഡിലെ വെബ്‌സൈറ്റ് ഉള്ളടക്ക വലുപ്പം",
"placeholder": "ഉള്ളടക്ക വലുപ്പം (സ്ഥിരസ്ഥിതി 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "വെബ്ബ് തിരച്ചിൽ നിയന്ത്രിക്കുക", "heading": "വെബ്ബ് തിരച്ചിൽ നിയന്ത്രിക്കുക",
"searchMode": { "searchMode": {
@ -291,6 +301,11 @@
"label": "ചങ്ക് ഓവര്‍ലാപ്പ്", "label": "ചങ്ക് ഓവര്‍ലാപ്പ്",
"placeholder": "ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക", "placeholder": "ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക",
"required": "ദയവായി ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക" "required": "ദയവായി ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക"
},
"totalFilePerKB": {
"label": "Limite padrão de arquivos da base de conhecimento",
"placeholder": "Digite o limite padrão de arquivos (ex. 10)",
"required": "Por favor, digite o limite padrão de arquivos"
} }
}, },
"prompt": { "prompt": {

View File

@ -31,6 +31,16 @@
"label": "Enviar notificação após concluir o processamento da base de conhecimento" "label": "Enviar notificação após concluir o processamento da base de conhecimento"
} }
}, },
"sidepanelRag": {
"heading": "Configurações de Chat Copilot com o Site",
"ragEnabled": {
"label": "Conversar com o site usando embeddings vetoriais"
},
"maxWebsiteContext": {
"label": "Tamanho do conteúdo do site no modo normal",
"placeholder": "Tamanho do conteúdo (padrão 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Gerenciar Pesquisa na Web", "heading": "Gerenciar Pesquisa na Web",
"searchMode": { "searchMode": {
@ -288,6 +298,11 @@
"label": "Sobreposição do Pedaço", "label": "Sobreposição do Pedaço",
"placeholder": "Digite a Sobreposição do Pedaço", "placeholder": "Digite a Sobreposição do Pedaço",
"required": "Por favor, insira uma sobreposição de pedaço" "required": "Por favor, insira uma sobreposição de pedaço"
},
"totalFilePerKB": {
"label": "Limite padrão de arquivos da base de conhecimento",
"placeholder": "Digite o limite padrão de arquivos (ex. 10)",
"required": "Por favor, digite o limite padrão de arquivos"
} }
}, },
"prompt": { "prompt": {

View File

@ -31,6 +31,17 @@
"label": "Отправить уведомление после завершения обработки базы знаний" "label": "Отправить уведомление после завершения обработки базы знаний"
} }
}, },
"sidepanelRag": {
"heading": "Настройки чата Copilot с веб-сайтом",
"ragEnabled": {
"label": "Общаться с веб-сайтом, используя векторные вложения"
},
"maxWebsiteContext": {
"label": "Размер содержимого веб-сайта в обычном режиме",
"placeholder": "Размер содержимого (по умолчанию 4028)"
}
},
"webSearch": { "webSearch": {
"heading": "Управление веб-поиском", "heading": "Управление веб-поиском",
"searchMode": { "searchMode": {
@ -289,6 +300,11 @@
"label": "Перекрытие фрагментов", "label": "Перекрытие фрагментов",
"placeholder": "Введите перекрытие фрагментов", "placeholder": "Введите перекрытие фрагментов",
"required": "Пожалуйста, введите перекрытие фрагментов" "required": "Пожалуйста, введите перекрытие фрагментов"
},
"totalFilePerKB": {
"label": "Стандартный лимит файлов базы знаний",
"placeholder": "Введите стандартный лимит файлов (напр. 10)",
"required": "Пожалуйста, введите стандартный лимит файлов"
} }
}, },
"prompt": { "prompt": {

View File

@ -34,6 +34,16 @@
"label": "完成知识库处理后发送通知" "label": "完成知识库处理后发送通知"
} }
}, },
"sidepanelRag": {
"heading": "与网站对话的Copilot聊天设置",
"ragEnabled": {
"label": "使用向量嵌入与网站聊天"
},
"maxWebsiteContext": {
"label": "普通模式下的网站内容大小",
"placeholder": "内容大小默认4028"
}
},
"webSearch": { "webSearch": {
"heading": "管理网络搜索", "heading": "管理网络搜索",
"searchMode": { "searchMode": {
@ -293,6 +303,11 @@
"label": "嵌入重叠", "label": "嵌入重叠",
"placeholder": "256-∞", "placeholder": "256-∞",
"required": "请输入嵌入重叠" "required": "请输入嵌入重叠"
},
"totalFilePerKB": {
"label": "知识库默认文件限制",
"placeholder": "输入默认文件限制例如10",
"required": "请输入默认文件限制"
} }
}, },
"prompt": { "prompt": {

View File

@ -7,6 +7,7 @@ import { InboxIcon } from "lucide-react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import PubSub from "pubsub-js" import PubSub from "pubsub-js"
import { KNOWLEDGE_QUEUE } from "@/queue" import { KNOWLEDGE_QUEUE } from "@/queue"
import { useStorage } from "@plasmohq/storage/hook"
type Props = { type Props = {
open: boolean open: boolean
@ -16,6 +17,7 @@ type Props = {
export const AddKnowledge = ({ open, setOpen }: Props) => { export const AddKnowledge = ({ open, setOpen }: Props) => {
const { t } = useTranslation(["knowledge", "common"]) const { t } = useTranslation(["knowledge", "common"])
const [form] = Form.useForm() const [form] = Form.useForm()
const [totalFilePerKB] = useStorage("totalFilePerKB", 10)
const onUploadHandler = async (data: { const onUploadHandler = async (data: {
title: string title: string
@ -92,7 +94,7 @@ export const AddKnowledge = ({ open, setOpen }: Props) => {
<Upload.Dragger <Upload.Dragger
accept={".pdf, .csv, .txt, .md, .docx"} accept={".pdf, .csv, .txt, .md, .docx"}
multiple={true} multiple={true}
maxCount={10} maxCount={totalFilePerKB}
beforeUpload={(file) => { beforeUpload={(file) => {
const allowedTypes = [ const allowedTypes = [
"application/pdf", "application/pdf",

View File

@ -17,9 +17,9 @@ export const SelectedKnowledge = () => {
<Tooltip <Tooltip
title={knowledge.title} title={knowledge.title}
> >
<div className="inline-flex truncate items-center gap-2"> <div className="inline-flex items-center gap-2 max-w-[150px]">
<Blocks className="h-5 w-5 text-gray-400" /> <Blocks className="h-5 w-5 text-gray-400 flex-shrink-0" />
<span className="text-xs hidden lg:inline-block font-semibold dark:text-gray-100"> <span className="text-xs hidden lg:inline-block font-semibold dark:text-gray-100 truncate">
{knowledge.title} {knowledge.title}
</span> </span>
</div> </div>

View File

@ -126,30 +126,31 @@ export const PromptBody = () => {
{ {
title: t("managePrompts.columns.title"), title: t("managePrompts.columns.title"),
dataIndex: "title", dataIndex: "title",
key: "title" key: "title",
render: (content) => (<span className="line-clamp-1">{content}</span>)
}, },
{ {
title: t("managePrompts.columns.prompt"), title: t("managePrompts.columns.prompt"),
dataIndex: "content", dataIndex: "content",
key: "content" key: "content",
render: (content) => (<span className="line-clamp-1">{content}</span>)
}, },
{ {
title: t("managePrompts.columns.type"), title: t("managePrompts.columns.type"),
dataIndex: "is_system", dataIndex: "is_system",
key: "is_system", key: "is_system",
render: (is_system) => render: (is_system) =>
is_system ? ( <span className="flex items-center gap-2 text-xs w-32">
<span className="flex items-center gap-2"> {is_system ? (
<Computer className="w-5 h-5 " /> <>
{t("managePrompts.systemPrompt")} <Computer className="size-4" /> {t("managePrompts.systemPrompt")}
</span> </>
) : ( ) : (
<span className="flex items-center gap-2"> <>
<Zap className="w-5 h-5" /> <Zap className="size-4" /> {t("managePrompts.quickPrompt")}
{t("managePrompts.quickPrompt")} </>
</span> )}
) </span> },
},
{ {
title: t("managePrompts.columns.actions"), title: t("managePrompts.columns.actions"),
render: (_, record) => ( render: (_, record) => (
@ -164,7 +165,7 @@ export const PromptBody = () => {
} }
}} }}
className="text-red-500 dark:text-red-400"> className="text-red-500 dark:text-red-400">
<Trash2 className="w-5 h-5" /> <Trash2 className="size-4" />
</button> </button>
</Tooltip> </Tooltip>
<Tooltip title={t("managePrompts.tooltip.edit")}> <Tooltip title={t("managePrompts.tooltip.edit")}>
@ -175,7 +176,7 @@ export const PromptBody = () => {
setOpenEdit(true) setOpenEdit(true)
}} }}
className="text-gray-500 dark:text-gray-400"> className="text-gray-500 dark:text-gray-400">
<Pen className="w-5 h-5" /> <Pen className="size-4" />
</button> </button>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -1,39 +0,0 @@
import { Tabs } from "antd"
import { SettingsOllama } from "./Settings/ollama"
import { SettingPrompt } from "./Settings/prompt"
import { SettingOther } from "./Settings/general-settings"
type Props = {
setClose: (close: boolean) => void
}
export const Settings = ({ setClose }: Props) => {
return (
<div className="my-6 max-h-[80vh] overflow-y-auto">
<Tabs
tabPosition="left"
defaultActiveKey="1"
items={[
{
id: "1",
key: "1",
label: "Prompt",
children: <SettingPrompt />
},
{
id: "2",
key: "2",
label: "Web UI Settings",
children: <SettingOther />
},
{
id: "3",
key: "3",
label: "Ollama Settings",
children: <SettingsOllama />
}
]}
/>
</div>
)
}

View File

@ -10,6 +10,8 @@ import {
} from "~/services/ollama" } from "~/services/ollama"
import { SettingPrompt } from "./prompt" import { SettingPrompt } from "./prompt"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { getTotalFilePerKB } from "@/services/app"
import { SidepanelRag } from "./sidepanel-rag"
export const RagSettings = () => { export const RagSettings = () => {
const { t } = useTranslation("settings") const { t } = useTranslation("settings")
@ -19,19 +21,20 @@ export const RagSettings = () => {
const { data: ollamaInfo, status } = useQuery({ const { data: ollamaInfo, status } = useQuery({
queryKey: ["fetchRAGSettings"], queryKey: ["fetchRAGSettings"],
queryFn: async () => { queryFn: async () => {
const [allModels, chunkOverlap, chunkSize, defaultEM] = await Promise.all( const [allModels, chunkOverlap, chunkSize, defaultEM, totalFilePerKB] =
[ await Promise.all([
getAllModels({ returnEmpty: true }), getAllModels({ returnEmpty: true }),
defaultEmbeddingChunkOverlap(), defaultEmbeddingChunkOverlap(),
defaultEmbeddingChunkSize(), defaultEmbeddingChunkSize(),
defaultEmbeddingModelForRag() defaultEmbeddingModelForRag(),
] getTotalFilePerKB()
) ])
return { return {
models: allModels, models: allModels,
chunkOverlap, chunkOverlap,
chunkSize, chunkSize,
defaultEM defaultEM,
totalFilePerKB
} }
} }
}) })
@ -41,8 +44,9 @@ export const RagSettings = () => {
model: string model: string
chunkSize: number chunkSize: number
overlap: number overlap: number
totalFilePerKB: number
}) => { }) => {
await saveForRag(data.model, data.chunkSize, data.overlap) await saveForRag(data.model, data.chunkSize, data.overlap, data.totalFilePerKB)
return true return true
}, },
onSuccess: () => { onSuccess: () => {
@ -70,13 +74,15 @@ export const RagSettings = () => {
saveRAG({ saveRAG({
model: data.defaultEM, model: data.defaultEM,
chunkSize: data.chunkSize, chunkSize: data.chunkSize,
overlap: data.chunkOverlap overlap: data.chunkOverlap,
totalFilePerKB: data.totalFilePerKB
}) })
}} }}
initialValues={{ initialValues={{
chunkSize: ollamaInfo?.chunkSize, chunkSize: ollamaInfo?.chunkSize,
chunkOverlap: ollamaInfo?.chunkOverlap, chunkOverlap: ollamaInfo?.chunkOverlap,
defaultEM: ollamaInfo?.defaultEM defaultEM: ollamaInfo?.defaultEM,
totalFilePerKB: ollamaInfo?.totalFilePerKB
}}> }}>
<Form.Item <Form.Item
name="defaultEM" name="defaultEM"
@ -85,9 +91,7 @@ export const RagSettings = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: t( message: t("rag.ragSettings.model.required")
"rag.ragSettings.model.required"
)
} }
]}> ]}>
<Select <Select
@ -99,9 +103,7 @@ export const RagSettings = () => {
0 0
} }
showSearch showSearch
placeholder={t( placeholder={t("rag.ragSettings.model.placeholder")}
"rag.ragSettings.model.placeholder"
)}
style={{ width: "100%" }} style={{ width: "100%" }}
className="mt-4" className="mt-4"
options={ollamaInfo.models?.map((model) => ({ options={ollamaInfo.models?.map((model) => ({
@ -117,45 +119,53 @@ export const RagSettings = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: t( message: t("rag.ragSettings.chunkSize.required")
"rag.ragSettings.chunkSize.required"
)
} }
]}> ]}>
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
placeholder={t( placeholder={t("rag.ragSettings.chunkSize.placeholder")}
"rag.ragSettings.chunkSize.placeholder"
)}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="chunkOverlap" name="chunkOverlap"
label={t( label={t("rag.ragSettings.chunkOverlap.label")}
"rag.ragSettings.chunkOverlap.label"
)}
rules={[ rules={[
{ {
required: true, required: true,
message: t( message: t("rag.ragSettings.chunkOverlap.required")
"rag.ragSettings.chunkOverlap.required"
)
} }
]}> ]}>
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
placeholder={t( placeholder={t("rag.ragSettings.chunkOverlap.placeholder")}
"rag.ragSettings.chunkOverlap.placeholder"
)}
/> />
</Form.Item> </Form.Item>
<Form.Item
name="totalFilePerKB"
label={t("rag.ragSettings.totalFilePerKB.label")}
rules={[
{
required: true,
message: t("rag.ragSettings.totalFilePerKB.required")
}
]}>
<InputNumber
style={{ width: "100%" }}
placeholder={t("rag.ragSettings.totalFilePerKB.placeholder")}
/>
</Form.Item>
<div className="flex justify-end"> <div className="flex justify-end">
<SaveButton disabled={isSaveRAGPending} btnType="submit" /> <SaveButton disabled={isSaveRAGPending} btnType="submit" />
</div> </div>
</Form> </Form>
</div> </div>
<SidepanelRag />
<div> <div>
<div> <div>
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white"> <h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">

View File

@ -0,0 +1,63 @@
import { useStorage } from "@plasmohq/storage/hook"
import { InputNumber, Switch } from "antd"
import { useTranslation } from "react-i18next"
export const SidepanelRag = ({ hideBorder }: { hideBorder?: boolean }) => {
const { t } = useTranslation("settings")
const [chatWithWebsiteEmbedding, setChatWithWebsiteEmbedding] = useStorage(
"chatWithWebsiteEmbedding",
true
)
const [maxWebsiteContext, setMaxWebsiteContext] = useStorage(
"maxWebsiteContext",
4028
)
return (
<div>
<div className="mb-5">
<h2
className={`${
!hideBorder ? "text-base font-semibold leading-7" : "text-md"
} text-gray-900 dark:text-white`}>
{t("generalSettings.sidepanelRag.heading")}
</h2>
{!hideBorder && (
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
)}
</div>
<div className={`${
!hideBorder ? "text-sm" : ""
} space-y-4`}>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-4 sm:space-y-0">
<span className="text-gray-700 truncate dark:text-neutral-50">
{t("generalSettings.sidepanelRag.ragEnabled.label")}
</span>
<div>
<Switch
className="mt-4 sm:mt-0"
checked={chatWithWebsiteEmbedding}
onChange={(checked) => setChatWithWebsiteEmbedding(checked)}
/>
</div>
</div>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-4 sm:space-y-0">
<span className="text-gray-700 truncate dark:text-neutral-50">
{t("generalSettings.sidepanelRag.maxWebsiteContext.label")}
</span>
<div>
<InputNumber
disabled={chatWithWebsiteEmbedding}
className="mt-4 sm:mt-0"
value={maxWebsiteContext}
onChange={(value) => setMaxWebsiteContext(value)}
placeholder={t(
"generalSettings.sidepanelRag.maxWebsiteContext.placeholder"
)}
/>
</div>
</div>
</div>
</div>
)
}

View File

@ -35,7 +35,10 @@ export const TTSModeSettings = ({ hideBorder }: { hideBorder?: boolean }) => {
return ( return (
<div> <div>
<div className="mb-5"> <div className="mb-5">
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white"> <h2
className={`${
!hideBorder ? "text-base font-semibold leading-7" : "text-md"
} text-gray-900 dark:text-white`}>
{t("generalSettings.tts.heading")} {t("generalSettings.tts.heading")}
</h2> </h2>
{!hideBorder && ( {!hideBorder && (

View File

@ -33,6 +33,8 @@ import { useI18n } from "@/hooks/useI18n"
import { TTSModeSettings } from "@/components/Option/Settings/tts-mode" import { TTSModeSettings } from "@/components/Option/Settings/tts-mode"
import { AdvanceOllamaSettings } from "@/components/Common/Settings/AdvanceOllamaSettings" import { AdvanceOllamaSettings } from "@/components/Common/Settings/AdvanceOllamaSettings"
import { useStorage } from "@plasmohq/storage/hook" import { useStorage } from "@plasmohq/storage/hook"
import { getTotalFilePerKB } from "@/services/app"
import { SidepanelRag } from "@/components/Option/Settings/sidepanel-rag"
export const SettingsBody = () => { export const SettingsBody = () => {
const { t } = useTranslation("settings") const { t } = useTranslation("settings")
@ -66,7 +68,8 @@ export const SettingsBody = () => {
allModels, allModels,
chunkOverlap, chunkOverlap,
chunkSize, chunkSize,
defaultEM defaultEM,
totalFilePerKB
] = await Promise.all([ ] = await Promise.all([
getOllamaURL(), getOllamaURL(),
systemPromptForNonRag(), systemPromptForNonRag(),
@ -74,7 +77,8 @@ export const SettingsBody = () => {
getAllModels({ returnEmpty: true }), getAllModels({ returnEmpty: true }),
defaultEmbeddingChunkOverlap(), defaultEmbeddingChunkOverlap(),
defaultEmbeddingChunkSize(), defaultEmbeddingChunkSize(),
defaultEmbeddingModelForRag() defaultEmbeddingModelForRag(),
getTotalFilePerKB()
]) ])
return { return {
@ -85,18 +89,19 @@ export const SettingsBody = () => {
models: allModels, models: allModels,
chunkOverlap, chunkOverlap,
chunkSize, chunkSize,
defaultEM defaultEM,
totalFilePerKB
} }
} }
}) })
const { mutate: saveRAG, isPending: isSaveRAGPending } = useMutation({ const { mutate: saveRAG, isPending: isSaveRAGPending } = useMutation({
mutationFn: async (data: { mutationFn: async (f: {
model: string model: string
chunkSize: number chunkSize: number
overlap: number overlap: number
}) => { }) => {
await saveForRag(data.model, data.chunkSize, data.overlap) await saveForRag(f.model, f.chunkSize, f.overlap, data.totalFilePerKB)
} }
}) })
@ -196,7 +201,9 @@ export const SettingsBody = () => {
</div> </div>
)} )}
</div> </div>
<div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
<SidepanelRag hideBorder />
</div>
<div className="border flex flex-col gap-4 border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]"> <div className="border flex flex-col gap-4 border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
<h2 className="text-md font-semibold dark:text-white"> <h2 className="text-md font-semibold dark:text-white">
{t("ollamaSettings.heading")} {t("ollamaSettings.heading")}
@ -247,7 +254,6 @@ export const SettingsBody = () => {
/> />
</div> </div>
</div> </div>
<div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]"> <div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
<h2 className="text-md mb-4 font-semibold dark:text-white"> <h2 className="text-md mb-4 font-semibold dark:text-white">
{t("rag.ragSettings.label")} {t("rag.ragSettings.label")}
@ -298,16 +304,12 @@ export const SettingsBody = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: t( message: t("rag.ragSettings.chunkSize.required")
"rag.ragSettings.chunkSize.required"
)
} }
]}> ]}>
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
placeholder={t( placeholder={t("rag.ragSettings.chunkSize.placeholder")}
"rag.ragSettings.chunkSize.placeholder"
)}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -316,16 +318,12 @@ export const SettingsBody = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: t( message: t("rag.ragSettings.chunkOverlap.required")
"rag.ragSettings.chunkOverlap.required"
)
} }
]}> ]}>
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
placeholder={t( placeholder={t("rag.ragSettings.chunkOverlap.placeholder")}
"rag.ragSettings.chunkOverlap.placeholder"
)}
/> />
</Form.Item> </Form.Item>

View File

@ -44,8 +44,16 @@ export const useMessage = () => {
const { t } = useTranslation("option") const { t } = useTranslation("option")
const [selectedModel, setSelectedModel] = useStorage("selectedModel") const [selectedModel, setSelectedModel] = useStorage("selectedModel")
const currentChatModelSettings = useStoreChatModelSettings() const currentChatModelSettings = useStoreChatModelSettings()
const { setIsSearchingInternet, webSearch, setWebSearch, isSearchingInternet } = const {
useStoreMessageOption() setIsSearchingInternet,
webSearch,
setWebSearch,
isSearchingInternet
} = useStoreMessageOption()
const [chatWithWebsiteEmbedding] = useStorage("chatWithWebsiteEmbedding", true)
const [maxWebsiteContext] = useStorage("maxWebsiteContext", 4028)
const { const {
history, history,
@ -150,35 +158,32 @@ export const useMessage = () => {
setMessages(newMessage) setMessages(newMessage)
let fullText = "" let fullText = ""
let contentToSave = "" let contentToSave = ""
let isAlreadyExistEmbedding: MemoryVectorStore
let embedURL: string, embedHTML: string, embedType: string let embedURL: string, embedHTML: string, embedType: string
let embedPDF: { content: string; page: number }[] = [] let embedPDF: { content: string; page: number }[] = []
if (messages.length === 0) { let isAlreadyExistEmbedding: MemoryVectorStore
const { content: html, url, type, pdf } = await getDataFromCurrentTab() const {
content: html,
url: websiteUrl,
type,
pdf
} = await getDataFromCurrentTab()
embedHTML = html embedHTML = html
embedURL = url embedURL = websiteUrl
embedType = type embedType = type
embedPDF = pdf embedPDF = pdf
setCurrentURL(url) if (messages.length === 0) {
setCurrentURL(websiteUrl)
isAlreadyExistEmbedding = keepTrackOfEmbedding[currentURL] isAlreadyExistEmbedding = keepTrackOfEmbedding[currentURL]
} else { } else {
const { content: html, url, type, pdf } = await getDataFromCurrentTab() if (currentURL !== websiteUrl) {
if (currentURL !== url) { setCurrentURL(websiteUrl)
embedHTML = html
embedURL = url
embedType = type
embedPDF = pdf
setCurrentURL(url)
} else { } else {
embedHTML = html
embedURL = currentURL embedURL = currentURL
embedType = type
embedPDF = pdf
} }
isAlreadyExistEmbedding = keepTrackOfEmbedding[url] isAlreadyExistEmbedding = keepTrackOfEmbedding[websiteUrl]
} }
setMessages(newMessage) setMessages(newMessage)
const ollamaUrl = await getOllamaURL() const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag() const embeddingModle = await defaultEmbeddingModelForRag()
@ -196,8 +201,8 @@ export const useMessage = () => {
try { try {
if (isAlreadyExistEmbedding) { if (isAlreadyExistEmbedding) {
vectorstore = isAlreadyExistEmbedding vectorstore = isAlreadyExistEmbedding
console.log("Embedding already exist")
} else { } else {
if (chatWithWebsiteEmbedding) {
vectorstore = await memoryEmbedding({ vectorstore = await memoryEmbedding({
html: embedHTML, html: embedHTML,
keepTrackOfEmbedding: keepTrackOfEmbedding, keepTrackOfEmbedding: keepTrackOfEmbedding,
@ -208,8 +213,7 @@ export const useMessage = () => {
type: embedType, type: embedType,
url: embedURL url: embedURL
}) })
}
console.log("Embedding created")
} }
let query = message let query = message
const { ragPrompt: systemPrompt, ragQuestionPrompt: questionPrompt } = const { ragPrompt: systemPrompt, ragQuestionPrompt: questionPrompt } =
@ -247,9 +251,20 @@ export const useMessage = () => {
query = response.content.toString() query = response.content.toString()
} }
let context: string = ""
let source: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4) const docs = await vectorstore.similaritySearch(query, 4)
const context = formatDocs(docs) context = formatDocs(docs)
const source = docs.map((doc) => { source = docs.map((doc) => {
return { return {
...doc, ...doc,
name: doc?.metadata?.source || "untitled", name: doc?.metadata?.source || "untitled",
@ -258,14 +273,37 @@ export const useMessage = () => {
url: "" url: ""
} }
}) })
// message = message.trim().replaceAll("\n", " ") } else {
if (type === "html") {
context = embedHTML.slice(0, maxWebsiteContext)
} else {
context = embedPDF
.map((pdf) => pdf.content)
.join(" ")
.slice(0, maxWebsiteContext)
}
source = [
{
name: embedURL,
type: type,
mode: "chat",
url: embedURL,
pageContent: context,
metadata: {
source: embedURL,
url: embedURL
}
}
]
}
let humanMessage = new HumanMessage({ let humanMessage = new HumanMessage({
content: [ content: [
{ {
text: systemPrompt text: systemPrompt
.replace("{context}", context) .replace("{context}", context)
.replace("{question}", message), .replace("{question}", query),
type: "text" type: "text"
} }
] ]
@ -299,7 +337,7 @@ export const useMessage = () => {
}) })
count++ count++
} }
// update the message with the full text
setMessages((prev) => { setMessages((prev) => {
return prev.map((message) => { return prev.map((message) => {
if (message.id === generateMessageId) { if (message.id === generateMessageId) {
@ -976,6 +1014,6 @@ export const useMessage = () => {
regenerateLastMessage, regenerateLastMessage,
webSearch, webSearch,
setWebSearch, setWebSearch,
isSearchingInternet, isSearchingInternet
} }
} }

View File

@ -1,10 +1,43 @@
import * as cheerio from "cheerio" import * as cheerio from "cheerio"
import TurndownService from "turndown" import TurndownService from "turndown"
let turndownService = new TurndownService() import { Readability, isProbablyReaderable } from "@mozilla/readability"
export const defaultExtractContent = (html: string) => { export const defaultExtractContent = (html: string) => {
const $ = cheerio.load(html)
const mainContent = $('[role="main"]').html() || $("main").html() || $.html()
const markdown = turndownService.turndown(mainContent) const doc = new DOMParser().parseFromString(html, "text/html")
return markdown if (isProbablyReaderable(doc)) {
const reader = new Readability(doc)
const article = reader.parse()
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced'
})
return turndownService.turndown(article.content).trim()
}
const $ = cheerio.load(html)
$('script, style, link, svg, [src^="data:image/"]').remove()
$('*').each((_, element) => {
if ('attribs' in element) {
const attributes = element.attribs
for (const attr in attributes) {
if (attr !== 'href' && attr !== 'src') {
$(element).removeAttr(attr)
}
}
}
})
const mainContent = $('[role="main"]').html() || $("main").html() || $("body").html() || ""
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced'
})
const markdown = turndownService.turndown(mainContent)
return markdown.trim()
} }

View File

@ -1,4 +1,4 @@
import { Readability } from "@mozilla/readability" import { Readability, } from "@mozilla/readability"
import { defaultExtractContent } from "./default" import { defaultExtractContent } from "./default"
export const extractReadabilityContent = async (url: string) => { export const extractReadabilityContent = async (url: string) => {
const response = await fetch(url) const response = await fetch(url)

View File

@ -98,3 +98,14 @@ export const getOpenOnRightClick = async (): Promise<string> => {
export const setOpenOnRightClick = async (option: "webUI" | "sidePanel"): Promise<void> => { export const setOpenOnRightClick = async (option: "webUI" | "sidePanel"): Promise<void> => {
await storage.set("openOnRightClick", option); await storage.set("openOnRightClick", option);
}; };
export const getTotalFilePerKB = async (): Promise<number> => {
const totalFilePerKB = await storage.get<number>("totalFilePerKB");
return totalFilePerKB || 10;
}
export const setTotalFilePerKB = async (totalFilePerKB: number): Promise<void> => {
await storage.set("totalFilePerKB", totalFilePerKB);
};

View File

@ -2,6 +2,7 @@ import { Storage } from "@plasmohq/storage"
import { cleanUrl } from "../libs/clean-url" import { cleanUrl } from "../libs/clean-url"
import { urlRewriteRuntime } from "../libs/runtime" import { urlRewriteRuntime } from "../libs/runtime"
import { getChromeAIModel } from "./chrome" import { getChromeAIModel } from "./chrome"
import { setTotalFilePerKB } from "./app"
const storage = new Storage() const storage = new Storage()
@ -142,7 +143,7 @@ export const deleteModel = async (model: string) => {
if (!response.ok) { if (!response.ok) {
throw new Error(response.statusText) throw new Error(response.statusText)
} }
return response.json() return "ok"
} }
@ -324,11 +325,13 @@ export const setDefaultEmbeddingChunkOverlap = async (overlap: number) => {
export const saveForRag = async ( export const saveForRag = async (
model: string, model: string,
chunkSize: number, chunkSize: number,
overlap: number overlap: number,
totalFilePerKB: number
) => { ) => {
await setDefaultEmbeddingModelForRag(model) await setDefaultEmbeddingModelForRag(model)
await setDefaultEmbeddingChunkSize(chunkSize) await setDefaultEmbeddingChunkSize(chunkSize)
await setDefaultEmbeddingChunkOverlap(overlap) await setDefaultEmbeddingChunkOverlap(overlap)
await setTotalFilePerKB(totalFilePerKB)
} }
export const getWebSearchPrompt = async () => { export const getWebSearchPrompt = async () => {

View File

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