Merge pull request #64 from n4ze3m/next

v1.1.7
This commit is contained in:
Muhammed Nazeem 2024-05-13 17:34:34 +05:30 committed by GitHub
commit a4a684f118
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 790 additions and 250 deletions

3
.gitignore vendored
View File

@ -42,4 +42,7 @@ keys.json
# typescript
.tsbuildinfo
# WXT
.wxt
# WebStorm
.idea

View File

@ -30,6 +30,13 @@ Note: You can install the extension on any Chromium-based browser. It is not lim
### Manual Installation
#### Pre-requisites
- Node.js (v18 or higher) - [Installation Guide](https://nodejs.org)
- npm
- Ollama (Local AI Provider) - [Installation Guide](https://ollama.com)
1. Clone the repository
```bash
@ -43,13 +50,19 @@ cd page-assist
npm install
```
3. Build the extension
3. Build the extension (by default it will build for Chrome)
```bash
npm run build
```
4. Load the extension
or you can build for Firefox
```bash
npm run build:firefox
```
4. Load the extension (chrome)
- Open the Extension Management page by navigating to `chrome://extensions`.
@ -57,6 +70,13 @@ npm run build
- Click the `Load unpacked` button and select the `build` directory.
5. Load the extension (firefox)
- Open the Add-ons page by navigating to `about:addons`.
- Click the `Extensions` tab.
- Click the `Manage Your Extensions` button.
- Click the `Load Temporary Add-on` button and select the `manifest.json` file from the `build` directory.
## Usage
### Sidebar
@ -89,10 +109,10 @@ This will start a development server and watch for changes in the source files.
| -------- | ------- | ----------------- | ------ |
| Chrome | ✅ | ✅ | ✅ |
| Brave | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ |
| Edge | ✅ | ❌ | ✅ |
| Opera GX | ❌ | ❌ | ✅ |
| Arc | ❌ | ❌ | ✅ |
| Firefox | ❌ | ❌ | ❌ |
## Local AI Provider
@ -100,7 +120,7 @@ This will start a development server and watch for changes in the source files.
## Roadmap
- [ ] Firefox Support
- [X] Firefox Support
- [ ] More Local AI Providers
- [ ] More Features
- [ ] More Customization Options

BIN
bun.lockb

Binary file not shown.

33
docs/connection-issue.md Normal file
View File

@ -0,0 +1,33 @@
# Ollama Connection Issues
Connection issues can be caused by a number of reasons. Here are some common issues and how to resolve them on Page Assist. You will see the following error message if there is a connection issue:
### 1. Direct Connection Error
![Direct connection error](https://image.pageassist.xyz/Screenshot%202024-05-13%20001742.png)
### 2. `403` Error When Sending a Message
![403 error when sending a message](https://image.pageassist.xyz/Screenshot%202024-05-13%20001940.png)
This issue usually occurs when Ollama is not running on [http://127.0.0.1:11434/](http://127.0.0.1:11434/), and the connection is from the private network or a different network.
### Solutions
Since Ollama has connection issues when directly accessed from the browser extension, Page Assist rewrites the request headers to make it work. However, automatic rewriting of headers only works on `http://127.0.0.1:*` and `http://localhost:*` URLs. To resolve the connection issue, you can try the following solutions:
1. Go to Page Assist and click on the `Settings` icon.
2. Click on the `Ollama Settings` tab.
3. There you will see the `Advance Ollama URL Configuration` option. You need to expand it.
![Advance Ollama URL Configuration](https://image.pageassist.xyz/Screenshot%202024-05-13%20003123.png)
4. Enable the `Enable or Disable Custom Origin URL` option.
![Enable or Disable Custom Origin URL](https://image.pageassist.xyz/Screenshot%202024-05-13%20003225.png)
5. (Optional) If Ollama is running on a different port or host, then change the URL in the `Custom Origin URL` field; otherwise, leave it as it is.
This will resolve the connection issue, and you will be able to use Ollama without any issues on Page Assist ❤
If you still face any issues, feel free to contact us [here](https://github.com/n4ze3m/page-assist/issues/new), and we will be happy to help you out.

View File

@ -5,12 +5,12 @@
"description": "Use your locally running AI models to assist you in your web browsing.",
"author": "n4ze3m",
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox",
"dev": "cross-env TARGET=chrome wxt",
"dev:firefox": "cross-env TARGET=firefox wxt -b firefox",
"build": "cross-env TARGET=chrome wxt build",
"build:firefox": "cross-env TARGET=chrome cross-env TARGET=firefox wxt build -b firefox",
"zip": "cross-env TARGET=chrome wxt zip",
"zip:firefox": "cross-env TARGET=firefox wxt zip -b firefox",
"compile": "tsc --noEmit",
"postinstall": "wxt prepare"
},
@ -66,6 +66,7 @@
"@types/react-syntax-highlighter": "^15.5.11",
"@types/turndown": "^5.0.4",
"autoprefixer": "^10.4.17",
"cross-env": "^7.0.3",
"postcss": "^8.4.33",
"prettier": "3.2.4",
"tailwindcss": "^3.4.1",

View File

@ -23,7 +23,7 @@ Click the button below to deploy the code to Railway.
```bash
git clone https://github.com/n4ze3m/page-share-app.git
cd page-assist-app
cd page-share-app
```
2. Run the server

View File

@ -2,7 +2,8 @@
"ollamaState": {
"searching": "Searching for Your Ollama 🦙",
"running": "Ollama is running 🦙",
"notRunning": "Unable to connect to Ollama 🦙"
"notRunning": "Unable to connect to Ollama 🦙",
"connectionError": "It seems like you are having a connection error. Please refer to this <anchor>documentation</anchor> for troubleshooting."
},
"formError": {
"noModel": "Please select a model",

View File

@ -245,6 +245,17 @@
"webSearchFollowUpPromptHelp": "Do not remove `{chat_history}` and `{question}` from the prompt.",
"webSearchFollowUpPromptError": "Please input your Web Search Follow Up Prompt!",
"webSearchFollowUpPromptPlaceholder": "Your Web Search Follow Up Prompt"
},
"advanced": {
"label": "Advance Ollama URL Configuration",
"urlRewriteEnabled": {
"label": "Enable or Disable Custom Origin URL"
},
"rewriteUrl": {
"label": "Custom Origin URL",
"placeholder": "Enter Custom Origin URL"
},
"help": "If you have connection issues with Ollama on Page Assist, you can configure a custom origin URL. To learn more about the configuration, <anchor>click here</anchor>."
}
}
},

View File

@ -2,7 +2,8 @@
"ollamaState": {
"searching": "Ollamaを検索中 🦙",
"running": "Ollamaが実行中 🦙",
"notRunning": "Ollamaに接続できません 🦙"
"notRunning": "Ollamaに接続できません 🦙",
"connectionError": "接続エラーが発生しているようです。トラブルシューティングについては<anchor>ドキュメント</anchor>をご覧ください。"
},
"formError": {
"noModel": "モデルを選択してください",

View File

@ -248,6 +248,17 @@
"webSearchFollowUpPromptHelp": "プロンプトから`{chat_history}`と`{question}`を削除しないでください。",
"webSearchFollowUpPromptError": "Web検索フォローアッププロンプトを入力してください",
"webSearchFollowUpPromptPlaceholder": "Web検索フォローアッププロンプト"
},
"advanced": {
"label": "Ollama URL の高度な設定",
"urlRewriteEnabled": {
"label": "カスタムOriginのURLを有効化または無効化する"
},
"rewriteUrl": {
"label": "カスタムOriginのURL",
"placeholder": "カスタムOriginのURLを入力"
},
"help": "PageAssistでOllamaに接続の問題がある場合は、カスタムOriginのURLを設定できます。設定の詳細については、<anchor>ここをクリック</anchor>してください。"
}
}
},

View File

@ -2,7 +2,8 @@
"ollamaState": {
"searching": "നിങ്ങളുടെ ഒല്ലാമയ്ക്കായി തിരയുന്നു 🦙",
"running": "ഒല്ലാമ പ്രവര്‍ത്തിക്കുന്നു 🦙",
"notRunning": "ഒല്ലാമയുമായി ബന്ധിപ്പിക്കാന്‍ കഴിയുന്നില്ല 🦙"
"notRunning": "ഒല്ലാമയുമായി ബന്ധിപ്പിക്കാന്‍ കഴിയുന്നില്ല 🦙",
"connectionError": "നിങ്ങൾക്ക് കണക്ഷൻ പ്രശ്നം ഉണ്ടെന്നു കാണുന്നു. ഈ <anchor>ഡോക്യുമെന്റേഷൻ</anchor> പരിശോധിക്കാൻ കൂടുതൽ സഹായത്തിനായി."
},
"formError": {
"noModel": "ദയവായി ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക",

View File

@ -248,6 +248,17 @@
"webSearchFollowUpPromptHelp": "പ്രോംപ്റ്റില്‍ നിന്ന് `{chat_history}` യും `{question}` യും നീക്കം ചെയ്യരുത്.",
"webSearchFollowUpPromptError": "ദയവായി നിങ്ങളുടെ വെബ് തിരയല്‍ തുടര്‍പ്രോംപ്റ്റ് നല്കുക!",
"webSearchFollowUpPromptPlaceholder": "നിങ്ങളുടെ വെബ് തിരയല്‍ തുടര്‍പ്രോംപ്റ്റ്"
},
"advanced": {
"label": "Advance Ollama URL Configuration",
"urlRewriteEnabled": {
"label": "Enable or Disable Custom Origin URL"
},
"rewriteUrl": {
"label": "Custom Origin URL",
"placeholder": "Enter Custom Origin URL"
},
"help": "ഏജ് അസിസ്റ്റന്റിൽ Ollama-യുമായി ബന്ധപ്പെടുമ്പോൾ ബന്ധതടസ്സം ഉണ്ടെങ്കിൽ, നിങ്ങൾക്ക് ഒരു വ്യക്തിഗത അസ്ഥിരത്വം URL കോൺഫിഗർ ചെയ്യാം. കോൺഫിഗറേഷനെക്കുറിച്ച് കൂടുതലറിയാൻ, <anchor>ഇവിടെ ക്ലിക്കുചെയ്യുക</anchor>."
}
}
},

View File

@ -2,7 +2,8 @@
"ollamaState": {
"searching": "Поиск вашего Ollama 🦙",
"running": "Ollama работает 🦙",
"notRunning": "Не удалось подключиться к Ollama 🦙"
"notRunning": "Не удалось подключиться к Ollama 🦙",
"connectionError": "Похоже, у вас возникла ошибка соединения. Пожалуйста, обратитесь к этой <anchor>документации</anchor> для устранения неисправностей."
},
"formError": {
"noModel": "Пожалуйста, выберите модель",

View File

@ -245,6 +245,17 @@
"webSearchFollowUpPromptHelp": "Не удаляйте `{chat_history}` и `{question}` из подсказки.",
"webSearchFollowUpPromptError": "Введите подсказку для последующего веб-поиска!",
"webSearchFollowUpPromptPlaceholder": "Ваша подсказка для последующего веб-поиска"
},
"advanced": {
"label": "Расширенная конфигурация URL Ollama",
"urlRewriteEnabled": {
"label": "Включить или отключить пользовательский исходный URL"
},
"rewriteUrl": {
"label": "Пользовательский исходный URL",
"placeholder": "Введите пользовательский исходный URL"
},
"help": "Если у вас возникают проблемы с подключением к Ollama на странице помощника, вы можете настроить пользовательский исходный URL. Чтобы узнать больше о конфигурации, <anchor>нажмите здесь</anchor>."
}
}
},

View File

@ -2,7 +2,8 @@
"ollamaState": {
"searching": "正在搜索您的Ollama 🦙",
"running": "Ollama正在运行 🦙",
"notRunning": "无法连接到Ollama 🦙"
"notRunning": "无法连接到Ollama 🦙",
"connectionError": "看起来你正在遇到连接错误。请参阅这<anchor>文档</anchor>进行故障排除。"
},
"formError": {
"noModel": "请选择一个模型",

View File

@ -249,6 +249,17 @@
"webSearchFollowUpPromptHelp": "请勿从提示词中删除 `{chat_history}` 和 `{question}`。",
"webSearchFollowUpPromptError": "请输入您的网页搜索追问提示词!",
"webSearchFollowUpPromptPlaceholder": "您的网页搜索追问提示词"
},
"advanced": {
"label": "Ollama URL 高级配置",
"urlRewriteEnabled": {
"label": "启用或禁用自定义来源 URL"
},
"rewriteUrl": {
"label": "自定义来源 URL",
"placeholder": "输入自定义来源 URL"
},
"help": "如果您在 Page Assist 上与 Ollama 有连接问题,您可以配置自定义来源 URL。要了解更多关于配置的信息,<anchor>点击此处</anchor>。"
}
}
},

View File

@ -0,0 +1,49 @@
import { useStorage } from "@plasmohq/storage/hook"
import { Input, Switch } from "antd"
import { useTranslation } from "react-i18next"
export const AdvanceOllamaSettings = () => {
const [urlRewriteEnabled, setUrlRewriteEnabled] = useStorage(
"urlRewriteEnabled",
false
)
const [rewriteUrl, setRewriteUrl] = useStorage(
"rewriteUrl",
"http://127.0.0.1:11434"
)
const { t } = useTranslation("settings")
return (
<div className="space-y-4">
<div className="flex sm:flex-row flex-col space-y-4 sm:space-y-0 sm:justify-between">
<span className="text-gray-500 dark:text-neutral-50 ">
{t("ollamaSettings.settings.advanced.urlRewriteEnabled.label")}
</span>
<div>
<Switch
className="mt-4 sm:mt-0"
checked={urlRewriteEnabled}
onChange={(checked) => setUrlRewriteEnabled(checked)}
/>
</div>
</div>
<div className="flex flex-col space-y-4 sm:space-y-0 sm:justify-between">
<span className="text-gray-500 dark:text-neutral-50 mb-3">
{t("ollamaSettings.settings.advanced.rewriteUrl.label")}
</span>
<div>
<Input
className="w-full"
value={rewriteUrl}
disabled={!urlRewriteEnabled}
placeholder={t(
"ollamaSettings.settings.advanced.rewriteUrl.placeholder"
)}
onChange={(e) => setRewriteUrl(e.target.value)}
/>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,10 @@
export const PageAssistLoader = () => {
return (
<div className="fixed bg-[#171717] top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden opacity-75 flex flex-col items-center justify-center">
<p className="text-center text-white text-lg mt-4">
Loading...
</p>
</div>
)
}

View File

@ -60,7 +60,7 @@ export const ModelsBody = () => {
form.reset()
chrome.runtime.sendMessage({
browser.runtime.sendMessage({
type: "pull_model",
modelName
})

View File

@ -1,7 +1,8 @@
import { cleanUrl } from "@/libs/clean-url"
import { useQuery } from "@tanstack/react-query"
import { RotateCcw } from "lucide-react"
import { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { Trans, useTranslation } from "react-i18next"
import {
getOllamaURL,
isOllamaRunning,
@ -79,6 +80,23 @@ export const PlaygroundEmpty = () => {
<RotateCcw className="h-4 w-4 mr-3" />
{t("common:retry")}
</button>
{ollamaURL &&
cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4 text-center">
<Trans
i18nKey="playground:ollamaState.connectionError"
components={{
anchor: (
<a
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md"
target="__blank"
className="text-blue-600 dark:text-blue-400"></a>
)
}}
/>
</p>
)}
</div>
)
) : null}

View File

@ -11,7 +11,7 @@ export const AboutApp = () => {
const { data, status } = useQuery({
queryKey: ["fetchOllamURL"],
queryFn: async () => {
const chromeVersion = chrome.runtime.getManifest().version
const chromeVersion = browser.runtime.getManifest().version
try {
const url = await getOllamaURL()
const req = await fetch(`${cleanUrl(url)}/api/version`)

View File

@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@tanstack/react-query"
import { Form, InputNumber, Select, Skeleton } from "antd"
import { Collapse, Form, InputNumber, Select, Skeleton } from "antd"
import { useState } from "react"
import { SaveButton } from "~/components/Common/SaveButton"
import {
@ -12,10 +12,13 @@ import {
setOllamaURL as saveOllamaURL
} from "~/services/ollama"
import { SettingPrompt } from "./prompt"
import { useTranslation } from "react-i18next"
import { Trans, useTranslation } from "react-i18next"
import { useStorage } from "@plasmohq/storage/hook"
import { AdvanceOllamaSettings } from "@/components/Common/AdvanceOllamaSettings"
export const SettingsOllama = () => {
const [ollamaURL, setOllamaURL] = useState<string>("")
const { t } = useTranslation("settings")
const { data: ollamaInfo, status } = useQuery({
@ -61,7 +64,7 @@ export const SettingsOllama = () => {
</h2>
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
</div>
<div>
<div className="mb-3">
<label
htmlFor="ollamaURL"
className="text-sm font-medium dark:text-gray-200">
@ -78,6 +81,36 @@ export const SettingsOllama = () => {
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
/>
</div>
<Collapse
size="small"
items={[
{
key: "1",
label: (
<div>
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
{t("ollamaSettings.settings.advanced.label")}
</h2>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
<Trans
i18nKey="settings:ollamaSettings.settings.advanced.help"
components={{
anchor: (
<a
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md#solutions"
target="__blank"
className="text-blue-600 dark:text-blue-400"></a>
)
}}
/>
</p>
</div>
),
children: <AdvanceOllamaSettings />
}
]}
/>
<div className="flex justify-end">
<SaveButton
onClick={() => {
@ -130,7 +163,9 @@ export const SettingsOllama = () => {
0
}
showSearch
placeholder={t("ollamaSettings.settings.ragSettings.model.placeholder")}
placeholder={t(
"ollamaSettings.settings.ragSettings.model.placeholder"
)}
style={{ width: "100%" }}
className="mt-4"
options={ollamaInfo.models?.map((model) => ({
@ -144,26 +179,38 @@ export const SettingsOllama = () => {
name="chunkSize"
label={t("ollamaSettings.settings.ragSettings.chunkSize.label")}
rules={[
{ required: true, message: t("ollamaSettings.settings.ragSettings.chunkSize.required")
{
required: true,
message: t(
"ollamaSettings.settings.ragSettings.chunkSize.required"
)
}
]}>
<InputNumber
style={{ width: "100%" }}
placeholder={t("ollamaSettings.settings.ragSettings.chunkSize.placeholder")}
placeholder={t(
"ollamaSettings.settings.ragSettings.chunkSize.placeholder"
)}
/>
</Form.Item>
<Form.Item
name="chunkOverlap"
label={t("ollamaSettings.settings.ragSettings.chunkOverlap.label")}
label={t(
"ollamaSettings.settings.ragSettings.chunkOverlap.label"
)}
rules={[
{
required: true,
message: t("ollamaSettings.settings.ragSettings.chunkOverlap.required")
message: t(
"ollamaSettings.settings.ragSettings.chunkOverlap.required"
)
}
]}>
<InputNumber
style={{ width: "100%" }}
placeholder={t("ollamaSettings.settings.ragSettings.chunkOverlap.placeholder")}
placeholder={t(
"ollamaSettings.settings.ragSettings.chunkOverlap.placeholder"
)}
/>
</Form.Item>

View File

@ -1,8 +1,9 @@
import { cleanUrl } from "@/libs/clean-url"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { Select } from "antd"
import { RotateCcw } from "lucide-react"
import { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { Trans, useTranslation } from "react-i18next"
import { useMessage } from "~/hooks/useMessage"
import {
getAllModels,
@ -91,6 +92,22 @@ export const EmptySidePanel = () => {
<RotateCcw className="h-4 w-4 mr-3" />
{t("common:retry")}
</button>
{ollamaURL &&
cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4 text-center">
<Trans
i18nKey="playground:ollamaState.connectionError"
components={{
anchor: (
<a
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md"
target="__blank"
className="text-blue-600 dark:text-blue-400"></a>
)
}}
/>
</p>
)}
</div>
)
) : null}

View File

@ -1,13 +1,13 @@
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
import { Storage } from "@plasmohq/storage"
import { browser } from "wxt/browser"
import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action"
const progressHuman = (completed: number, total: number) => {
return ((completed / total) * 100).toFixed(0) + "%"
}
const clearBadge = () => {
chrome.action.setBadgeText({ text: "" })
chrome.action.setTitle({ title: "" })
setBadgeText({ text: "" })
setTitle({ title: "" })
}
const streamDownload = async (url: string, model: string) => {
url += "/api/pull"
@ -42,16 +42,16 @@ const streamDownload = async (url: string, model: string) => {
completed?: number
}
if (json.total && json.completed) {
chrome.action.setBadgeText({
setBadgeText({
text: progressHuman(json.completed, json.total)
})
chrome.action.setBadgeBackgroundColor({ color: "#0000FF" })
setBadgeBackgroundColor({ color: "#0000FF" })
} else {
chrome.action.setBadgeText({ text: "🏋️‍♂️" })
chrome.action.setBadgeBackgroundColor({ color: "#FFFFFF" })
setBadgeText({ text: "🏋️‍♂️" })
setBadgeBackgroundColor({ color: "#FFFFFF" })
}
chrome.action.setTitle({ title: json.status })
setTitle({ title: json.status })
if (json.status === "success") {
isSuccess = true
@ -62,13 +62,13 @@ const streamDownload = async (url: string, model: string) => {
}
if (isSuccess) {
chrome.action.setBadgeText({ text: "✅" })
chrome.action.setBadgeBackgroundColor({ color: "#00FF00" })
chrome.action.setTitle({ title: "Model pulled successfully" })
setBadgeText({ text: "✅" })
setBadgeBackgroundColor({ color: "#00FF00" })
setTitle({ title: "Model pulled successfully" })
} else {
chrome.action.setBadgeText({ text: "❌" })
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
chrome.action.setTitle({ title: "Model pull failed" })
setBadgeText({ text: "❌" })
setBadgeBackgroundColor({ color: "#FF0000" })
setTitle({ title: "Model pull failed" })
}
setTimeout(() => {
@ -77,29 +77,18 @@ const streamDownload = async (url: string, model: string) => {
}
export default defineBackground({
main() {
const storage = new Storage()
chrome.runtime.onMessage.addListener(async (message) => {
browser.runtime.onMessage.addListener(async (message) => {
if (message.type === "sidepanel") {
chrome.tabs.query(
{ active: true, currentWindow: true },
async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
// tabId: tab.id!,
windowId: tab.windowId!
})
}
)
browser.sidebarAction.open()
} else if (message.type === "pull_model") {
const ollamaURL = await getOllamaURL()
const isRunning = await isOllamaRunning()
if (!isRunning) {
chrome.action.setBadgeText({ text: "E" })
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
chrome.action.setTitle({ title: "Ollama is not running" })
setBadgeText({ text: "E" })
setBadgeBackgroundColor({ color: "#FF0000" })
setTitle({ title: "Ollama is not running" })
setTimeout(() => {
clearBadge()
}, 5000)
@ -109,35 +98,24 @@ export default defineBackground({
}
})
if (import.meta.env.BROWSER === "chrome") {
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") })
browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
})
chrome.commands.onCommand.addListener((command) => {
switch (command) {
case "execute_side_panel":
chrome.tabs.query(
{ active: true, currentWindow: true },
async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
windowId: tab.windowId!
} else {
browser.browserAction.onClicked.addListener((tab) => {
console.log("browser.browserAction.onClicked.addListener")
browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
})
}
)
break
default:
break
}
})
chrome.contextMenus.create({
browser.contextMenus.create({
id: "open-side-panel-pa",
title: browser.i18n.getMessage("openSidePanelToChat"),
contexts: ["all"]
})
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (import.meta.env.BROWSER === "chrome") {
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "open-side-panel-pa") {
chrome.tabs.query(
{ active: true, currentWindow: true },
@ -150,6 +128,43 @@ export default defineBackground({
)
}
})
browser.commands.onCommand.addListener((command) => {
switch (command) {
case "execute_side_panel":
chrome.tabs.query(
{ active: true, currentWindow: true },
async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
tabId: tab.id!
})
}
)
break
default:
break
}
})
}
if (import.meta.env.BROWSER === "firefox") {
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "open-side-panel-pa") {
browser.sidebarAction.toggle()
}
})
browser.commands.onCommand.addListener((command) => {
switch (command) {
case "execute_side_panel":
browser.sidebarAction.toggle()
break
default:
break
}
})
}
},
persistent: true
})

View File

@ -9,7 +9,7 @@ export default defineContentScript({
`[Page Assist Extension] Pulling ${modelName} model. For more details, check the extension icon.`
)
await chrome.runtime.sendMessage({
await browser.runtime.sendMessage({
type: "pull_model",
modelName
})

View File

@ -4,6 +4,7 @@
<title>Page Assist - A Web UI for Local AI Models</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="manifest.type" content="browser_action" />
<meta name="manifest.open_at_install" content="false" />
<link href="~/assets/tailwind.css" rel="stylesheet" />
<meta charset="utf-8" />
</head>

View File

@ -2,7 +2,6 @@ import { useEffect, useState } from "react"
import { notification } from "antd"
import { getVoice, isSSMLEnabled } from "@/services/tts"
import { markdownToSSML } from "@/utils/markdown-to-ssml"
type VoiceOptions = {
utterance: string
}
@ -17,6 +16,7 @@ export const useTTS = () => {
if (isSSML) {
utterance = markdownToSSML(utterance)
}
if (import.meta.env.BROWSER === "chrome") {
chrome.tts.speak(utterance, {
voiceName: voice,
onEvent(event) {
@ -27,6 +27,17 @@ export const useTTS = () => {
}
}
})
} else {
// browser tts
window.speechSynthesis.speak(new SpeechSynthesisUtterance(utterance))
window.speechSynthesis.onvoiceschanged = () => {
const voices = window.speechSynthesis.getVoices()
const voice = voices.find((v) => v.name === voice)
const utter = new SpeechSynthesisUtterance(utterance)
utter.voice = voice
window.speechSynthesis.speak(utter)
}
}
} catch (error) {
notification.error({
message: "Error",
@ -36,7 +47,11 @@ export const useTTS = () => {
}
const cancel = () => {
if (import.meta.env.BROWSER === "chrome") {
chrome.tts.stop()
} else {
window.speechSynthesis.cancel()
}
setIsSpeaking(false)
}

View File

@ -4,7 +4,7 @@ import {
isTweet,
isTwitterTimeline,
parseTweet,
parseTwitterTimeline,
parseTwitterTimeline
} from "@/parser/twitter"
import { isGoogleDocs, parseGoogleDocs } from "@/parser/google-docs"
import { cleanUnwantedUnicode } from "@/utils/clean"
@ -24,6 +24,7 @@ const _getHtml = () => {
export const getDataFromCurrentTab = async () => {
const result = new Promise((resolve) => {
if (import.meta.env.BROWSER === "chrome") {
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
@ -36,6 +37,22 @@ export const getDataFromCurrentTab = async () => {
resolve(data[0].result)
}
})
} else {
browser.tabs
.query({ active: true, currentWindow: true })
.then(async (tabs) => {
const tab = tabs[0]
const data = await browser.scripting.executeScript({
target: { tabId: tab.id },
func: _getHtml
})
if (data.length > 0) {
resolve(data[0].result)
}
})
}
}) as Promise<{
url: string
content: string

View File

@ -1,7 +1,18 @@
export const chromeRunTime = async function (domain: string) {
if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.id) {
import { getAdvancedOllamaSettings } from "@/services/app"
export const urlRewriteRuntime = async function (
domain: string,
type = "ollama"
) {
if (browser.runtime && browser.runtime.id) {
const { isEnableRewriteUrl, rewriteUrl } = await getAdvancedOllamaSettings()
if (import.meta.env.BROWSER === "chrome") {
const url = new URL(domain)
const domains = [url.hostname]
let origin = `${url.protocol}//${url.hostname}`
if (isEnableRewriteUrl && rewriteUrl && type === "ollama") {
origin = rewriteUrl
}
const rules = [
{
id: 1,
@ -15,17 +26,38 @@ export const chromeRunTime = async function (domain: string) {
{
header: "Origin",
operation: "set",
value: `${url.protocol}//${url.hostname}`
value: origin
}
]
}
}
]
await chrome.declarativeNetRequest.updateDynamicRules({
await browser.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map((r) => r.id),
// @ts-ignore
addRules: rules
})
}
if (import.meta.env.BROWSER === "firefox") {
const url = new URL(domain)
const domains = [`*://${url.hostname}/*`]
browser.webRequest.onBeforeSendHeaders.addListener(
(details) => {
let origin = `${url.protocol}//${url.hostname}`
if (isEnableRewriteUrl && rewriteUrl && type === "ollama") {
origin = rewriteUrl
}
for (let i = 0; i < details.requestHeaders.length; i++) {
if (details.requestHeaders[i].name === "Origin") {
details.requestHeaders[i].value = origin
}
}
return { requestHeaders: details.requestHeaders }
},
{ urls: domains },
["blocking", "requestHeaders"]
)
}
}
}

View File

@ -1,7 +1,7 @@
import { BaseDocumentLoader } from "langchain/document_loaders/base"
import { Document } from "@langchain/core/documents"
import { compile } from "html-to-text"
import { chromeRunTime } from "~/libs/runtime"
import { urlRewriteRuntime } from "~/libs/runtime"
import { YtTranscript } from "yt-transcript"
import { isWikipedia, parseWikipedia } from "@/parser/wiki"
@ -102,7 +102,7 @@ export class PageAssistHtmlLoader
}
]
}
await chromeRunTime(this.url)
await urlRewriteRuntime(this.url, "web")
const fetchHTML = await fetch(this.url)
let html = await fetchHTML.text()
@ -111,11 +111,6 @@ export class PageAssistHtmlLoader
html = parseWikipedia(await fetchHTML.text())
}
// else if (isTwitter(this.url)) {
// console.log("Twitter URL detected")
// html = parseTweet(await fetchHTML.text(), this.url)
// }
const htmlCompiler = compile({
wordwrap: false,
selectors: [

View File

@ -1,4 +1,3 @@
export const isGoogleDocs = (url: string) => {
const GOOGLE_DOCS_REGEX = /docs\.google\.com\/document/g
return GOOGLE_DOCS_REGEX.test(url)
@ -96,6 +95,7 @@ const getGoogleDocs = () => {
export const parseGoogleDocs = async () => {
const result = new Promise((resolve) => {
if (import.meta.env.BROWSER === "chrome") {
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
@ -109,6 +109,22 @@ export const parseGoogleDocs = async () => {
resolve(data[0].result)
}
})
} else {
browser.tabs
.query({ active: true, currentWindow: true })
.then(async (tabs) => {
const tab = tabs[0]
const data = await browser.scripting.executeScript({
target: { tabId: tab.id },
func: getGoogleDocs
})
if (data.length > 0) {
resolve(data[0].result)
}
})
}
}) as Promise<{
content?: string
}>

35
src/routes/chrome.tsx Normal file
View File

@ -0,0 +1,35 @@
import { Route, Routes } from "react-router-dom"
import OptionIndex from "./option-index"
import OptionSettings from "./option-settings"
import OptionModal from "./option-settings-model"
import OptionPrompt from "./option-settings-prompt"
import OptionOllamaSettings from "./options-settings-ollama"
import OptionShare from "./option-settings-share"
import OptionKnowledgeBase from "./option-settings-knowledge"
import OptionAbout from "./option-settings-about"
import SidepanelChat from "./sidepanel-chat"
import SidepanelSettings from "./sidepanel-settings"
export const OptionRoutingChrome = () => {
return (
<Routes>
<Route path="/" element={<OptionIndex />} />
<Route path="/settings" element={<OptionSettings />} />
<Route path="/settings/model" element={<OptionModal />} />
<Route path="/settings/prompt" element={<OptionPrompt />} />
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
<Route path="/settings/share" element={<OptionShare />} />
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} />
</Routes>
)
}
export const SidepanelRoutingChrome = () => {
return (
<Routes>
<Route path="/" element={<SidepanelChat />} />
<Route path="/settings" element={<SidepanelSettings />} />
</Routes>
)
}

39
src/routes/firefox.tsx Normal file
View File

@ -0,0 +1,39 @@
// this is a temp fix for firefox
// because chunks getting 4mb+ and it's not working on firefox addon store
import { lazy } from "react"
import { Route , Routes} from "react-router-dom"
const SidepanelChat = lazy(() => import("./sidepanel-chat"))
const SidepanelSettings = lazy(() => import("./sidepanel-settings"))
const OptionIndex = lazy(() => import("./option-index"))
const OptionModal = lazy(() => import("./option-settings-model"))
const OptionPrompt = lazy(() => import("./option-settings-prompt"))
const OptionOllamaSettings = lazy(() => import("./options-settings-ollama"))
const OptionSettings = lazy(() => import("./option-settings"))
const OptionShare = lazy(() => import("./option-settings-share"))
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
const OptionAbout = lazy(() => import("./option-settings-about"))
export const OptionRoutingFirefox = () => {
return (
<Routes>
<Route path="/" element={<OptionIndex />} />
<Route path="/settings" element={<OptionSettings />} />
<Route path="/settings/model" element={<OptionModal />} />
<Route path="/settings/prompt" element={<OptionPrompt />} />
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
<Route path="/settings/share" element={<OptionShare />} />
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} />
</Routes>
)
}
export const SidepanelRoutingFirefox = () => {
return (
<Routes>
<Route path="/" element={<SidepanelChat />} />
<Route path="/settings" element={<SidepanelSettings />} />
</Routes>
)
}

View File

@ -1,16 +1,9 @@
import { Route, Routes } from "react-router-dom"
import { SidepanelChat } from "./sidepanel-chat"
import { useDarkMode } from "~/hooks/useDarkmode"
import { SidepanelSettings } from "./sidepanel-settings"
import { OptionIndex } from "./option-index"
import { OptionModal } from "./option-settings-model"
import { OptionPrompt } from "./option-settings-prompt"
import { OptionOllamaSettings } from "./options-settings-ollama"
import { OptionSettings } from "./option-settings"
import { OptionShare } from "./option-settings-share"
import { OptionKnowledgeBase } from "./option-settings-knowledge"
import { OptionAbout } from "./option-settings-about"
import { Suspense } from "react"
import { useTranslation } from "react-i18next"
import { useDarkMode } from "~/hooks/useDarkmode"
import { OptionRoutingChrome, SidepanelRoutingChrome } from "./chrome"
import { OptionRoutingFirefox, SidepanelRoutingFirefox } from "./firefox"
import { PageAssistLoader } from "@/components/Common/PageAssistLoader"
export const OptionRouting = () => {
const { mode } = useDarkMode()
@ -21,16 +14,13 @@ export const OptionRouting = () => {
className={`${mode === "dark" ? "dark" : "light"} ${
i18n.language === "ru" ? "onest" : "inter"
}`}>
<Routes>
<Route path="/" element={<OptionIndex />} />
<Route path="/settings" element={<OptionSettings />} />
<Route path="/settings/model" element={<OptionModal />} />
<Route path="/settings/prompt" element={<OptionPrompt />} />
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
<Route path="/settings/share" element={<OptionShare />} />
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} />
</Routes>
<Suspense fallback={<PageAssistLoader />}>
{import.meta.env.BROWSER === "chrome" ? (
<OptionRoutingChrome />
) : (
<OptionRoutingFirefox />
)}
</Suspense>
</div>
)
}
@ -44,10 +34,13 @@ export const SidepanelRouting = () => {
className={`${mode === "dark" ? "dark" : "light"} ${
i18n.language === "ru" ? "onest" : "inter"
}`}>
<Routes>
<Route path="/" element={<SidepanelChat />} />
<Route path="/settings" element={<SidepanelSettings />} />
</Routes>
<Suspense fallback={<PageAssistLoader />}>
{import.meta.env.BROWSER === "chrome" ? (
<SidepanelRoutingChrome />
) : (
<SidepanelRoutingFirefox />
)}
</Suspense>
</div>
)
}

View File

@ -1,10 +1,12 @@
import OptionLayout from "~/components/Layouts/Layout"
import { Playground } from "~/components/Option/Playground/Playground"
export const OptionIndex = () => {
const OptionIndex = () => {
return (
<OptionLayout>
<Playground />
</OptionLayout>
)
}
export default OptionIndex

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { AboutApp } from "@/components/Option/Settings/about"
export const OptionAbout = () => {
const OptionAbout = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionAbout = () => {
</OptionLayout>
)
}
export default OptionAbout

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { KnowledgeSettings } from "@/components/Option/Knowledge"
export const OptionKnowledgeBase = () => {
const OptionKnowledgeBase = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionKnowledgeBase = () => {
</OptionLayout>
)
}
export default OptionKnowledgeBase

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { ModelsBody } from "~/components/Option/Models"
export const OptionModal = () => {
const OptionModal = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionModal = () => {
</OptionLayout>
)
}
export default OptionModal

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { PromptBody } from "~/components/Option/Prompt"
export const OptionPrompt = () => {
const OptionPrompt = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionPrompt = () => {
</OptionLayout>
)
}
export default OptionPrompt

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { OptionShareBody } from "~/components/Option/Share"
export const OptionShare = () => {
const OptionShare = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionShare = () => {
</OptionLayout>
)
}
export default OptionShare

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { SettingOther } from "~/components/Option/Settings/other"
export const OptionSettings = () => {
const OptionSettings = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionSettings = () => {
</OptionLayout>
)
}
export default OptionSettings

View File

@ -2,7 +2,7 @@ import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { SettingsOllama } from "~/components/Option/Settings/ollama"
export const OptionOllamaSettings = () => {
const OptionOllamaSettings = () => {
return (
<OptionLayout>
<SettingsLayout>
@ -11,3 +11,5 @@ export const OptionOllamaSettings = () => {
</OptionLayout>
)
}
export default OptionOllamaSettings

View File

@ -4,7 +4,7 @@ import { SidepanelForm } from "~/components/Sidepanel/Chat/form"
import { SidepanelHeader } from "~/components/Sidepanel/Chat/header"
import { useMessage } from "~/hooks/useMessage"
export const SidepanelChat = () => {
const SidepanelChat = () => {
const drop = React.useRef<HTMLDivElement>(null)
const [dropedFile, setDropedFile] = React.useState<File | undefined>()
const [dropState, setDropState] = React.useState<
@ -90,3 +90,5 @@ export const SidepanelChat = () => {
</div>
)
}
export default SidepanelChat

View File

@ -1,7 +1,7 @@
import { SettingsBody } from "~/components/Sidepanel/Settings/body"
import { SidepanelSettingsHeader } from "~/components/Sidepanel/Settings/header"
export const SidepanelSettings = () => {
const SidepanelSettings = () => {
return (
<div className="flex bg-neutral-50 dark:bg-[#171717] flex-col min-h-screen mx-auto max-w-7xl">
<div className="sticky bg-white dark:bg-[#171717] top-0 z-10">
@ -11,3 +11,5 @@ export const SidepanelSettings = () => {
</div>
)
}
export default SidepanelSettings

36
src/services/app.ts Normal file
View File

@ -0,0 +1,36 @@
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
const DEFAULT_URL_REWRITE_URL = "http://127.0.0.1:11434"
export const isUrlRewriteEnabled = async () => {
const enabled = await storage.get<boolean | undefined>("urlRewriteEnabled")
return enabled
}
export const setUrlRewriteEnabled = async (enabled: boolean) => {
await storage.set("urlRewriteEnabled", enabled ? "true" : "false")
}
export const getRewriteUrl = async () => {
const rewriteUrl = await storage.get("rewriteUrl")
if (!rewriteUrl || rewriteUrl.trim() === "") {
return DEFAULT_URL_REWRITE_URL
}
return rewriteUrl
}
export const setRewriteUrl = async (url: string) => {
await storage.set("rewriteUrl", url)
}
export const getAdvancedOllamaSettings = async () => {
const [isEnableRewriteUrl, rewriteUrl] = await Promise.all([
isUrlRewriteEnabled(),
getRewriteUrl()
])
return {
isEnableRewriteUrl,
rewriteUrl
}
}

View File

@ -1,6 +1,6 @@
import { Storage } from "@plasmohq/storage"
import { cleanUrl } from "../libs/clean-url"
import { chromeRunTime } from "../libs/runtime"
import { urlRewriteRuntime } from "../libs/runtime"
const storage = new Storage()
@ -22,10 +22,10 @@ Search results:
export const getOllamaURL = async () => {
const ollamaURL = await storage.get("ollamaURL")
if (!ollamaURL || ollamaURL.length === 0) {
await chromeRunTime(DEFAULT_OLLAMA_URL)
await urlRewriteRuntime(DEFAULT_OLLAMA_URL)
return DEFAULT_OLLAMA_URL
}
await chromeRunTime(cleanUrl(ollamaURL))
await urlRewriteRuntime(cleanUrl(ollamaURL))
return ollamaURL
}
@ -163,7 +163,7 @@ export const setOllamaURL = async (ollamaURL: string) => {
"http://127.0.0.1:"
)
}
await chromeRunTime(cleanUrl(formattedUrl))
await urlRewriteRuntime(cleanUrl(formattedUrl))
await storage.set("ollamaURL", cleanUrl(formattedUrl))
}

View File

@ -21,8 +21,16 @@ export const setTTSProvider = async (ttsProvider: string) => {
}
export const getBrowserTTSVoices = async () => {
if (import.meta.env.BROWSER === "chrome") {
const tts = await chrome.tts.getVoices()
return tts
} else {
const tts = await speechSynthesis.getVoices()
return tts.map((voice) => ({
voiceName: voice.name,
lang: voice.lang
}))
}
}
export const getVoice = async () => {

25
src/utils/action.ts Normal file
View File

@ -0,0 +1,25 @@
import { browser } from "wxt/browser"
export const setTitle = ({ title }: { title: string }) => {
if (import.meta.env.BROWSER === "chrome") {
chrome.action.setTitle({ title })
} else {
browser.browserAction.setTitle({ title })
}
}
export const setBadgeBackgroundColor = ({ color }: { color: string }) => {
if (import.meta.env.BROWSER === "chrome") {
chrome.action.setBadgeBackgroundColor({ color })
} else {
browser.browserAction.setBadgeBackgroundColor({ color })
}
}
export const setBadgeText = ({ text }: { text: string }) => {
if (import.meta.env.BROWSER === "chrome") {
chrome.action.setBadgeText({ text })
} else {
browser.browserAction.setBadgeText({ text })
}
}

View File

@ -1,5 +1,5 @@
import { cleanUrl } from "@/libs/clean-url"
import { chromeRunTime } from "@/libs/runtime"
import { urlRewriteRuntime } from "@/libs/runtime"
import { PageAssistHtmlLoader } from "@/loader/html"
import {
defaultEmbeddingChunkOverlap,
@ -18,7 +18,7 @@ import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
export const localDuckDuckGoSearch = async (query: string) => {
await chromeRunTime(cleanUrl("https://html.duckduckgo.com/html/?q=" + query))
await urlRewriteRuntime(cleanUrl("https://html.duckduckgo.com/html/?q=" + query), "duckduckgo")
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)

View File

@ -7,7 +7,7 @@ import type { Document } from "@langchain/core/documents"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { cleanUrl } from "~/libs/clean-url"
import { chromeRunTime } from "~/libs/runtime"
import { urlRewriteRuntime } from "~/libs/runtime"
import { PageAssistHtmlLoader } from "~/loader/html"
import {
defaultEmbeddingChunkOverlap,
@ -18,8 +18,9 @@ import {
export const localGoogleSearch = async (query: string) => {
await chromeRunTime(
cleanUrl("https://www.google.com/search?hl=en&q=" + query)
await urlRewriteRuntime(
cleanUrl("https://www.google.com/search?hl=en&q=" + query),
"google"
)
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)

View File

@ -1,5 +1,5 @@
import { cleanUrl } from "@/libs/clean-url"
import { chromeRunTime } from "@/libs/runtime"
import { urlRewriteRuntime } from "@/libs/runtime"
import { PageAssistHtmlLoader } from "@/loader/html"
import {
defaultEmbeddingChunkOverlap,
@ -25,7 +25,10 @@ const getCorrectTargeUrl = async (url: string) => {
return matches?.[1] || ""
}
export const localSogouSearch = async (query: string) => {
await chromeRunTime(cleanUrl("https://www.sogou.com/web?query=" + query))
await urlRewriteRuntime(
cleanUrl("https://www.sogou.com/web?query=" + query),
"sogou"
)
const abortController = new AbortController()

View File

@ -3,5 +3,5 @@ module.exports = {
mode: "jit",
darkMode: "class",
content: ["./src/**/*.tsx"],
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")]
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography"),]
}

View File

@ -2,35 +2,73 @@ import { defineConfig } from "wxt"
import react from "@vitejs/plugin-react"
import topLevelAwait from "vite-plugin-top-level-await"
const chromeMV3Permissions = [
"storage",
"sidePanel",
"activeTab",
"scripting",
"declarativeNetRequest",
"action",
"unlimitedStorage",
"contextMenus",
"tts"
]
const firefoxMV2Permissions = [
"storage",
"activeTab",
"scripting",
"unlimitedStorage",
"contextMenus",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*",
"file://*/*"
]
// See https://wxt.dev/api/config.html
export default defineConfig({
vite: () => ({
plugins: [react(),
plugins: [
react(),
topLevelAwait({
promiseExportName: '__tla',
promiseImportName: i => `__tla_${i}`,
}),
promiseExportName: "__tla",
promiseImportName: (i) => `__tla_${i}`
})
],
build: {
rollupOptions: {
external: [
"langchain",
"@langchain/community",
]
external: ["langchain", "@langchain/community"]
}
}
}),
entrypointsDir: "entries",
srcDir: "src",
outDir: "build",
manifest: {
version: "1.1.6",
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
version: "1.1.7",
name:
process.env.TARGET === "firefox"
? "Page Assist - A Web UI for Local AI Models"
: "__MSG_extName__",
description: "__MSG_extDescription__",
default_locale: "en",
action: {},
author: "n4ze3m",
host_permissions: ["http://*/*", "https://*/*", "file://*/*"],
browser_specific_settings:
process.env.TARGET === "firefox"
? {
gecko: {
id: "page-assist@nazeem"
}
}
: undefined,
host_permissions:
process.env.TARGET !== "firefox"
? ["http://*/*", "https://*/*", "file://*/*"]
: undefined,
commands: {
_execute_action: {
suggested_key: {
@ -44,16 +82,9 @@ export default defineConfig({
}
}
},
permissions: [
"storage",
"sidePanel",
"activeTab",
"scripting",
"declarativeNetRequest",
"action",
"unlimitedStorage",
"contextMenus",
"tts"
]
permissions:
process.env.TARGET === "firefox"
? firefoxMV2Permissions
: chromeMV3Permissions
}
})