diff --git a/.gitignore b/.gitignore
index c76b5c4..cf3d5c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,4 +42,7 @@ keys.json
# typescript
.tsbuildinfo
-.wxt
\ No newline at end of file
+# WXT
+.wxt
+# WebStorm
+.idea
diff --git a/README.md b/README.md
index db7ef0b..c1fdc5a 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/bun.lockb b/bun.lockb
index a4deaa6..c6cbef9 100644
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/docs/connection-issue.md b/docs/connection-issue.md
new file mode 100644
index 0000000..a5681f4
--- /dev/null
+++ b/docs/connection-issue.md
@@ -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.
\ No newline at end of file
diff --git a/package.json b/package.json
index 98f5370..ec33b1b 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/page-share.md b/page-share.md
index f0646f2..a0b43d4 100644
--- a/page-share.md
+++ b/page-share.md
@@ -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
diff --git a/src/assets/locale/en/playground.json b/src/assets/locale/en/playground.json
index 1d0794d..5df01bb 100644
--- a/src/assets/locale/en/playground.json
+++ b/src/assets/locale/en/playground.json
@@ -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 documentation for troubleshooting."
},
"formError": {
"noModel": "Please select a model",
diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json
index 589f557..acfcfa5 100644
--- a/src/assets/locale/en/settings.json
+++ b/src/assets/locale/en/settings.json
@@ -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, click here."
}
}
},
diff --git a/src/assets/locale/ja-JP/playground.json b/src/assets/locale/ja-JP/playground.json
index a559d18..b5ebedf 100644
--- a/src/assets/locale/ja-JP/playground.json
+++ b/src/assets/locale/ja-JP/playground.json
@@ -2,7 +2,8 @@
"ollamaState": {
"searching": "Ollamaを検索中 🦙",
"running": "Ollamaが実行中 🦙",
- "notRunning": "Ollamaに接続できません 🦙"
+ "notRunning": "Ollamaに接続できません 🦙",
+ "connectionError": "接続エラーが発生しているようです。トラブルシューティングについてはドキュメントをご覧ください。"
},
"formError": {
"noModel": "モデルを選択してください",
diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json
index 1fae4f5..8fd2860 100644
--- a/src/assets/locale/ja-JP/settings.json
+++ b/src/assets/locale/ja-JP/settings.json
@@ -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を設定できます。設定の詳細については、ここをクリックしてください。"
}
}
},
diff --git a/src/assets/locale/ml/playground.json b/src/assets/locale/ml/playground.json
index 0c6b7ee..708782c 100644
--- a/src/assets/locale/ml/playground.json
+++ b/src/assets/locale/ml/playground.json
@@ -2,7 +2,8 @@
"ollamaState": {
"searching": "നിങ്ങളുടെ ഒല്ലാമയ്ക്കായി തിരയുന്നു 🦙",
"running": "ഒല്ലാമ പ്രവര്ത്തിക്കുന്നു 🦙",
- "notRunning": "ഒല്ലാമയുമായി ബന്ധിപ്പിക്കാന് കഴിയുന്നില്ല 🦙"
+ "notRunning": "ഒല്ലാമയുമായി ബന്ധിപ്പിക്കാന് കഴിയുന്നില്ല 🦙",
+ "connectionError": "നിങ്ങൾക്ക് കണക്ഷൻ പ്രശ്നം ഉണ്ടെന്നു കാണുന്നു. ഈ ഡോക്യുമെന്റേഷൻ പരിശോധിക്കാൻ കൂടുതൽ സഹായത്തിനായി."
},
"formError": {
"noModel": "ദയവായി ഒരു മോഡല് തിരഞ്ഞെടുക്കുക",
diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json
index 97152cd..b3384c7 100644
--- a/src/assets/locale/ml/settings.json
+++ b/src/assets/locale/ml/settings.json
@@ -47,13 +47,13 @@
"label": "ചാറ്റ് ചരിത്രം, അറിവ് അടിസ്ഥാനം, പ്രോംപ്റ്റുകൾ എക്സ്പോർട്ട് ചെയ്യുക",
"button": "ഡാറ്റ എക്സ്പോർട്ട് ചെയ്യുക",
"success": "എക്സ്പോർട്ട് വിജയകരമായി"
- },
- "import": {
+ },
+ "import": {
"label": "ചാറ്റ് ചരിത്രം, അറിവ് അടിസ്ഥാനം, പ്രോംപ്റ്റുകൾ ഇമ്പോർട്ട് ചെയ്യുക",
"button": "ഡാറ്റ ഇമ്പോർട്ട് ചെയ്യുക",
"success": "ഇമ്പോർട്ട് വിജയകരമായി",
"error": "ഇമ്പോർട്ട് പിശക്"
- }
+ }
},
"tts": {
"heading": "ടെക്സ്റ്റ്-ടു-സ്പീച്ച് ക്രമീകരണങ്ങൾ",
@@ -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 കോൺഫിഗർ ചെയ്യാം. കോൺഫിഗറേഷനെക്കുറിച്ച് കൂടുതലറിയാൻ, ഇവിടെ ക്ലിക്കുചെയ്യുക."
}
}
},
diff --git a/src/assets/locale/ru/playground.json b/src/assets/locale/ru/playground.json
index 498f2c8..433c67d 100644
--- a/src/assets/locale/ru/playground.json
+++ b/src/assets/locale/ru/playground.json
@@ -2,7 +2,8 @@
"ollamaState": {
"searching": "Поиск вашего Ollama 🦙",
"running": "Ollama работает 🦙",
- "notRunning": "Не удалось подключиться к Ollama 🦙"
+ "notRunning": "Не удалось подключиться к Ollama 🦙",
+ "connectionError": "Похоже, у вас возникла ошибка соединения. Пожалуйста, обратитесь к этой документации для устранения неисправностей."
},
"formError": {
"noModel": "Пожалуйста, выберите модель",
diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json
index c48c357..51fa44e 100644
--- a/src/assets/locale/ru/settings.json
+++ b/src/assets/locale/ru/settings.json
@@ -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. Чтобы узнать больше о конфигурации, нажмите здесь."
}
}
},
diff --git a/src/assets/locale/zh/playground.json b/src/assets/locale/zh/playground.json
index 84a591c..bb74e7a 100644
--- a/src/assets/locale/zh/playground.json
+++ b/src/assets/locale/zh/playground.json
@@ -2,7 +2,8 @@
"ollamaState": {
"searching": "正在搜索您的Ollama 🦙",
"running": "Ollama正在运行 🦙",
- "notRunning": "无法连接到Ollama 🦙"
+ "notRunning": "无法连接到Ollama 🦙",
+ "connectionError": "看起来你正在遇到连接错误。请参阅这文档进行故障排除。"
},
"formError": {
"noModel": "请选择一个模型",
diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json
index 70b8d3c..883ae22 100644
--- a/src/assets/locale/zh/settings.json
+++ b/src/assets/locale/zh/settings.json
@@ -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。要了解更多关于配置的信息,点击此处。"
}
}
},
diff --git a/src/components/Common/AdvanceOllamaSettings.tsx b/src/components/Common/AdvanceOllamaSettings.tsx
new file mode 100644
index 0000000..0027fb2
--- /dev/null
+++ b/src/components/Common/AdvanceOllamaSettings.tsx
@@ -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 (
+
+
+
+ {t("ollamaSettings.settings.advanced.urlRewriteEnabled.label")}
+
+
+ setUrlRewriteEnabled(checked)}
+ />
+
+
+
+
+ {t("ollamaSettings.settings.advanced.rewriteUrl.label")}
+
+
+ setRewriteUrl(e.target.value)}
+ />
+
+
+
+ )
+}
diff --git a/src/components/Common/PageAssistLoader.tsx b/src/components/Common/PageAssistLoader.tsx
new file mode 100644
index 0000000..f98e014
--- /dev/null
+++ b/src/components/Common/PageAssistLoader.tsx
@@ -0,0 +1,10 @@
+
+export const PageAssistLoader = () => {
+ return (
+
+ )
+}
diff --git a/src/components/Option/Models/index.tsx b/src/components/Option/Models/index.tsx
index 7728484..e342edc 100644
--- a/src/components/Option/Models/index.tsx
+++ b/src/components/Option/Models/index.tsx
@@ -60,7 +60,7 @@ export const ModelsBody = () => {
form.reset()
- chrome.runtime.sendMessage({
+ browser.runtime.sendMessage({
type: "pull_model",
modelName
})
diff --git a/src/components/Option/Playground/PlaygroundEmpty.tsx b/src/components/Option/Playground/PlaygroundEmpty.tsx
index 6636605..7fb94ab 100644
--- a/src/components/Option/Playground/PlaygroundEmpty.tsx
+++ b/src/components/Option/Playground/PlaygroundEmpty.tsx
@@ -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 = () => {
{t("common:retry")}
+
+ {ollamaURL &&
+ cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
+
+
+ )
+ }}
+ />
+
+ )}
)
) : null}
diff --git a/src/components/Option/Settings/about.tsx b/src/components/Option/Settings/about.tsx
index 024bf82..6250ab1 100644
--- a/src/components/Option/Settings/about.tsx
+++ b/src/components/Option/Settings/about.tsx
@@ -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`)
diff --git a/src/components/Option/Settings/ollama.tsx b/src/components/Option/Settings/ollama.tsx
index e71045b..969a845 100644
--- a/src/components/Option/Settings/ollama.tsx
+++ b/src/components/Option/Settings/ollama.tsx
@@ -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("")
+
const { t } = useTranslation("settings")
const { data: ollamaInfo, status } = useQuery({
@@ -61,7 +64,7 @@ export const SettingsOllama = () => {
-
+
+
+
+ {t("ollamaSettings.settings.advanced.label")}
+
+
+
+ )
+ }}
+ />
+
+
+ ),
+ children:
+ }
+ ]}
+ />
+
{
@@ -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"
+ )
+ }
]}>
diff --git a/src/components/Sidepanel/Chat/empty.tsx b/src/components/Sidepanel/Chat/empty.tsx
index 499e320..000d4e3 100644
--- a/src/components/Sidepanel/Chat/empty.tsx
+++ b/src/components/Sidepanel/Chat/empty.tsx
@@ -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 = () => {
{t("common:retry")}
+ {ollamaURL &&
+ cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
+
+
+ )
+ }}
+ />
+
+ )}
)
) : null}
diff --git a/src/db/index.ts b/src/db/index.ts
index 6ca341f..f709905 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -455,4 +455,4 @@ export const importPrompts = async (prompts: Prompts) => {
for (const prompt of prompts) {
await db.addPrompt(prompt)
}
-}
+}
\ No newline at end of file
diff --git a/src/db/knowledge.ts b/src/db/knowledge.ts
index 9579536..3f90880 100644
--- a/src/db/knowledge.ts
+++ b/src/db/knowledge.ts
@@ -202,4 +202,4 @@ export const importKnowledge = async (data: Knowledge[]) => {
for (const d of data) {
await db.create(d)
}
-}
+}
\ No newline at end of file
diff --git a/src/db/vector.ts b/src/db/vector.ts
index 09624fc..f5dd141 100644
--- a/src/db/vector.ts
+++ b/src/db/vector.ts
@@ -165,4 +165,4 @@ export const exportVectors = async () => {
export const importVectors = async (data: VectorData[]) => {
const db = new PageAssistVectorDb()
return db.saveImportedData(data)
-}
+}
\ No newline at end of file
diff --git a/src/entries/background.ts b/src/entries/background.ts
index f671467..91c71a7 100644
--- a/src/entries/background.ts
+++ b/src/entries/background.ts
@@ -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,47 +98,73 @@ export default defineBackground({
}
})
- chrome.action.onClicked.addListener((tab) => {
- chrome.tabs.create({ url: chrome.runtime.getURL("options.html") })
- })
+ if (import.meta.env.BROWSER === "chrome") {
+ chrome.action.onClicked.addListener((tab) => {
+ browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
+ })
+ } else {
+ browser.browserAction.onClicked.addListener((tab) => {
+ console.log("browser.browserAction.onClicked.addListener")
+ browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
+ })
+ }
- chrome.commands.onCommand.addListener((command) => {
- switch (command) {
- case "execute_side_panel":
+ browser.contextMenus.create({
+ id: "open-side-panel-pa",
+ title: browser.i18n.getMessage("openSidePanelToChat"),
+ contexts: ["all"]
+ })
+ 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 },
async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
- windowId: tab.windowId!
+ tabId: tab.id!
})
}
)
- break
- default:
- break
- }
- })
-
- chrome.contextMenus.create({
- id: "open-side-panel-pa",
- title: browser.i18n.getMessage("openSidePanelToChat"),
- contexts: ["all"]
- })
+ }
+ })
+
+ 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
+ }
+ })
+ }
- chrome.contextMenus.onClicked.addListener((info, tab) => {
- if (info.menuItemId === "open-side-panel-pa") {
- chrome.tabs.query(
- { active: true, currentWindow: true },
- async (tabs) => {
- const tab = tabs[0]
- chrome.sidePanel.open({
- tabId: tab.id!
- })
- }
- )
- }
- })
+ 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
})
diff --git a/src/entries/ollama-pull.content.ts b/src/entries/ollama-pull.content.ts
index 2740f44..6acbccd 100644
--- a/src/entries/ollama-pull.content.ts
+++ b/src/entries/ollama-pull.content.ts
@@ -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
})
diff --git a/src/entries/sidepanel/index.html b/src/entries/sidepanel/index.html
index 2abb927..f18de84 100644
--- a/src/entries/sidepanel/index.html
+++ b/src/entries/sidepanel/index.html
@@ -4,6 +4,7 @@
Page Assist - A Web UI for Local AI Models
+
diff --git a/src/hooks/useTTS.tsx b/src/hooks/useTTS.tsx
index 075f005..5274956 100644
--- a/src/hooks/useTTS.tsx
+++ b/src/hooks/useTTS.tsx
@@ -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,16 +16,28 @@ export const useTTS = () => {
if (isSSML) {
utterance = markdownToSSML(utterance)
}
- chrome.tts.speak(utterance, {
- voiceName: voice,
- onEvent(event) {
- if (event.type === "start") {
- setIsSpeaking(true)
- } else if (event.type === "end") {
- setIsSpeaking(false)
+ if (import.meta.env.BROWSER === "chrome") {
+ chrome.tts.speak(utterance, {
+ voiceName: voice,
+ onEvent(event) {
+ if (event.type === "start") {
+ setIsSpeaking(true)
+ } else if (event.type === "end") {
+ setIsSpeaking(false)
+ }
}
+ })
+ } 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 = () => {
- chrome.tts.stop()
+ if (import.meta.env.BROWSER === "chrome") {
+ chrome.tts.stop()
+ } else {
+ window.speechSynthesis.cancel()
+ }
setIsSpeaking(false)
}
diff --git a/src/libs/get-html.ts b/src/libs/get-html.ts
index 40499d4..ddb2ad7 100644
--- a/src/libs/get-html.ts
+++ b/src/libs/get-html.ts
@@ -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,18 +24,35 @@ const _getHtml = () => {
export const getDataFromCurrentTab = async () => {
const result = new Promise((resolve) => {
- chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
- const tab = tabs[0]
+ if (import.meta.env.BROWSER === "chrome") {
+ chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
+ const tab = tabs[0]
- const data = await chrome.scripting.executeScript({
- target: { tabId: tab.id },
- func: _getHtml
+ const data = await chrome.scripting.executeScript({
+ target: { tabId: tab.id },
+ func: _getHtml
+ })
+
+ if (data.length > 0) {
+ resolve(data[0].result)
+ }
})
+ } else {
+ browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then(async (tabs) => {
+ const tab = tabs[0]
- if (data.length > 0) {
- resolve(data[0].result)
- }
- })
+ 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
diff --git a/src/libs/runtime.ts b/src/libs/runtime.ts
index 8f55cc0..dc54c4a 100644
--- a/src/libs/runtime.ts
+++ b/src/libs/runtime.ts
@@ -1,31 +1,63 @@
-export const chromeRunTime = async function (domain: string) {
- if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.id) {
- const url = new URL(domain)
- const domains = [url.hostname]
- const rules = [
- {
- id: 1,
- priority: 1,
- condition: {
- requestDomains: domains
- },
- action: {
- type: "modifyHeaders",
- requestHeaders: [
- {
- header: "Origin",
- operation: "set",
- value: `${url.protocol}//${url.hostname}`
- }
- ]
- }
+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,
+ priority: 1,
+ condition: {
+ requestDomains: domains
+ },
+ action: {
+ type: "modifyHeaders",
+ requestHeaders: [
+ {
+ header: "Origin",
+ operation: "set",
+ value: origin
+ }
+ ]
+ }
+ }
+ ]
+ await browser.declarativeNetRequest.updateDynamicRules({
+ removeRuleIds: rules.map((r) => r.id),
+ // @ts-ignore
+ addRules: rules
+ })
+ }
- await chrome.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"]
+ )
+ }
}
}
diff --git a/src/loader/html.ts b/src/loader/html.ts
index 94eaed0..786c60e 100644
--- a/src/loader/html.ts
+++ b/src/loader/html.ts
@@ -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: [
diff --git a/src/parser/google-docs.ts b/src/parser/google-docs.ts
index 23e7316..0781396 100644
--- a/src/parser/google-docs.ts
+++ b/src/parser/google-docs.ts
@@ -1,7 +1,6 @@
-
export const isGoogleDocs = (url: string) => {
- const GOOGLE_DOCS_REGEX = /docs\.google\.com\/document/g
- return GOOGLE_DOCS_REGEX.test(url)
+ const GOOGLE_DOCS_REGEX = /docs\.google\.com\/document/g
+ return GOOGLE_DOCS_REGEX.test(url)
}
const getGoogleDocs = () => {
@@ -96,24 +95,41 @@ const getGoogleDocs = () => {
export const parseGoogleDocs = async () => {
const result = new Promise((resolve) => {
- chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
- const tab = tabs[0]
+ if (import.meta.env.BROWSER === "chrome") {
+ chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
+ const tab = tabs[0]
+
+ const data = await chrome.scripting.executeScript({
+ target: { tabId: tab.id },
+ world: "MAIN",
+ func: getGoogleDocs
+ })
- const data = await chrome.scripting.executeScript({
- target: { tabId: tab.id },
- world: "MAIN",
- func: getGoogleDocs
+ if (data.length > 0) {
+ resolve(data[0].result)
+ }
})
+ } else {
+ browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then(async (tabs) => {
+ const tab = tabs[0]
- if (data.length > 0) {
- resolve(data[0].result)
- }
- })
+ const data = await browser.scripting.executeScript({
+ target: { tabId: tab.id },
+ func: getGoogleDocs
+ })
+
+ if (data.length > 0) {
+ resolve(data[0].result)
+ }
+ })
+ }
}) as Promise<{
content?: string
}>
const { content } = await result
-
+
return content
}
diff --git a/src/routes/chrome.tsx b/src/routes/chrome.tsx
new file mode 100644
index 0000000..a7ccb7c
--- /dev/null
+++ b/src/routes/chrome.tsx
@@ -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 (
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ )
+}
+
+export const SidepanelRoutingChrome = () => {
+ return (
+
+ } />
+ } />
+
+ )
+}
diff --git a/src/routes/firefox.tsx b/src/routes/firefox.tsx
new file mode 100644
index 0000000..1281a72
--- /dev/null
+++ b/src/routes/firefox.tsx
@@ -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 (
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ )
+}
+
+export const SidepanelRoutingFirefox = () => {
+ return (
+
+ } />
+ } />
+
+ )
+}
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 87581f8..86af217 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -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"
}`}>
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
+ }>
+ {import.meta.env.BROWSER === "chrome" ? (
+
+ ) : (
+
+ )}
+
)
}
@@ -44,10 +34,13 @@ export const SidepanelRouting = () => {
className={`${mode === "dark" ? "dark" : "light"} ${
i18n.language === "ru" ? "onest" : "inter"
}`}>
-
- } />
- } />
-
+ }>
+ {import.meta.env.BROWSER === "chrome" ? (
+
+ ) : (
+
+ )}
+
)
}
diff --git a/src/routes/option-index.tsx b/src/routes/option-index.tsx
index 6178d0a..776bcce 100644
--- a/src/routes/option-index.tsx
+++ b/src/routes/option-index.tsx
@@ -1,10 +1,12 @@
import OptionLayout from "~/components/Layouts/Layout"
import { Playground } from "~/components/Option/Playground/Playground"
-export const OptionIndex = () => {
+ const OptionIndex = () => {
return (
)
}
+
+export default OptionIndex
\ No newline at end of file
diff --git a/src/routes/option-settings-about.tsx b/src/routes/option-settings-about.tsx
index 982f1ea..9d00233 100644
--- a/src/routes/option-settings-about.tsx
+++ b/src/routes/option-settings-about.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionAbout = () => {
)
}
+
+export default OptionAbout
diff --git a/src/routes/option-settings-knowledge.tsx b/src/routes/option-settings-knowledge.tsx
index aedddd3..504035c 100644
--- a/src/routes/option-settings-knowledge.tsx
+++ b/src/routes/option-settings-knowledge.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionKnowledgeBase = () => {
)
}
+
+export default OptionKnowledgeBase
\ No newline at end of file
diff --git a/src/routes/option-settings-model.tsx b/src/routes/option-settings-model.tsx
index 99264d9..456e1dd 100644
--- a/src/routes/option-settings-model.tsx
+++ b/src/routes/option-settings-model.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionModal = () => {
)
}
+
+export default OptionModal
diff --git a/src/routes/option-settings-prompt.tsx b/src/routes/option-settings-prompt.tsx
index f7a35cf..f796403 100644
--- a/src/routes/option-settings-prompt.tsx
+++ b/src/routes/option-settings-prompt.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionPrompt = () => {
)
}
+
+export default OptionPrompt
\ No newline at end of file
diff --git a/src/routes/option-settings-share.tsx b/src/routes/option-settings-share.tsx
index dcd0da0..3ca7a30 100644
--- a/src/routes/option-settings-share.tsx
+++ b/src/routes/option-settings-share.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionShare = () => {
)
}
+
+export default OptionShare
\ No newline at end of file
diff --git a/src/routes/option-settings.tsx b/src/routes/option-settings.tsx
index 6849f7f..3bb60c0 100644
--- a/src/routes/option-settings.tsx
+++ b/src/routes/option-settings.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionSettings = () => {
)
}
+
+export default OptionSettings
\ No newline at end of file
diff --git a/src/routes/options-settings-ollama.tsx b/src/routes/options-settings-ollama.tsx
index cdbbad1..dad6498 100644
--- a/src/routes/options-settings-ollama.tsx
+++ b/src/routes/options-settings-ollama.tsx
@@ -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 (
@@ -11,3 +11,5 @@ export const OptionOllamaSettings = () => {
)
}
+
+export default OptionOllamaSettings
\ No newline at end of file
diff --git a/src/routes/sidepanel-chat.tsx b/src/routes/sidepanel-chat.tsx
index 0e87879..5921a2b 100644
--- a/src/routes/sidepanel-chat.tsx
+++ b/src/routes/sidepanel-chat.tsx
@@ -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(null)
const [dropedFile, setDropedFile] = React.useState()
const [dropState, setDropState] = React.useState<
@@ -90,3 +90,5 @@ export const SidepanelChat = () => {
)
}
+
+export default SidepanelChat
diff --git a/src/routes/sidepanel-settings.tsx b/src/routes/sidepanel-settings.tsx
index ad51649..eae0658 100644
--- a/src/routes/sidepanel-settings.tsx
+++ b/src/routes/sidepanel-settings.tsx
@@ -1,7 +1,7 @@
import { SettingsBody } from "~/components/Sidepanel/Settings/body"
import { SidepanelSettingsHeader } from "~/components/Sidepanel/Settings/header"
-export const SidepanelSettings = () => {
+const SidepanelSettings = () => {
return (
@@ -11,3 +11,5 @@ export const SidepanelSettings = () => {
)
}
+
+export default SidepanelSettings
diff --git a/src/services/app.ts b/src/services/app.ts
new file mode 100644
index 0000000..8f0cc66
--- /dev/null
+++ b/src/services/app.ts
@@ -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
("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
+ }
+}
diff --git a/src/services/ollama.ts b/src/services/ollama.ts
index ccca697..e1ce53b 100644
--- a/src/services/ollama.ts
+++ b/src/services/ollama.ts
@@ -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))
}
diff --git a/src/services/tts.ts b/src/services/tts.ts
index 2d38d3e..847efb4 100644
--- a/src/services/tts.ts
+++ b/src/services/tts.ts
@@ -21,8 +21,16 @@ export const setTTSProvider = async (ttsProvider: string) => {
}
export const getBrowserTTSVoices = async () => {
- const tts = await chrome.tts.getVoices()
- return tts
+ 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 () => {
diff --git a/src/utils/action.ts b/src/utils/action.ts
new file mode 100644
index 0000000..5881aa4
--- /dev/null
+++ b/src/utils/action.ts
@@ -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 })
+ }
+}
diff --git a/src/web/search-engines/duckduckgo.ts b/src/web/search-engines/duckduckgo.ts
index 089e571..374ce60 100644
--- a/src/web/search-engines/duckduckgo.ts
+++ b/src/web/search-engines/duckduckgo.ts
@@ -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)
diff --git a/src/web/search-engines/google.ts b/src/web/search-engines/google.ts
index 98fa2a8..8f0a16a 100644
--- a/src/web/search-engines/google.ts
+++ b/src/web/search-engines/google.ts
@@ -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)
diff --git a/src/web/search-engines/sogou.ts b/src/web/search-engines/sogou.ts
index ffa7e6f..274d03f 100644
--- a/src/web/search-engines/sogou.ts
+++ b/src/web/search-engines/sogou.ts
@@ -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()
diff --git a/tailwind.config.js b/tailwind.config.js
index a30235c..f4b105c 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -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"),]
}
diff --git a/wxt.config.ts b/wxt.config.ts
index d80478d..0f5aeba 100644
--- a/wxt.config.ts
+++ b/wxt.config.ts
@@ -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
}
})