diff --git a/src/assets/locale/en/common.json b/src/assets/locale/en/common.json
index ffa6e81..05ef169 100644
--- a/src/assets/locale/en/common.json
+++ b/src/assets/locale/en/common.json
@@ -84,5 +84,12 @@
}
},
"advanced": "More Model Settings"
+ },
+ "copilot": {
+ "summary": "Summarize",
+ "explain": "Explain",
+ "rephrase": "Rephrase",
+ "translate": "Translate",
+ "custom": "Custom"
}
}
\ No newline at end of file
diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json
index c4405fe..fa16a1a 100644
--- a/src/assets/locale/en/settings.json
+++ b/src/assets/locale/en/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Send Notification After Finishing Processing the Knowledge Base"
+ },
+ "generateTitle" :{
+ "label": "Generate Title using AI"
}
},
"sidepanelRag": {
@@ -141,6 +144,10 @@
"option1": "Normal",
"option2": "RAG",
"questionPrompt": "Question Prompt",
+ "segmented": {
+ "custom": "Custom Prompts",
+ "copilot": "Copilot Prompts"
+ },
"columns": {
"title": "Title",
"prompt": "Prompt",
@@ -170,7 +177,8 @@
"label": "Prompt",
"placeholder": "Enter Prompt",
"required": "Please enter a prompt",
- "help": "You can use {key} as variable in your prompt."
+ "help": "You can use {key} as variable in your prompt.",
+ "missingTextPlaceholder": "The {text} variable is missing in the prompt. Please add it."
},
"isSystem": {
"label": "Is System Prompt"
diff --git a/src/assets/locale/es/common.json b/src/assets/locale/es/common.json
index e316a0b..06d0a00 100644
--- a/src/assets/locale/es/common.json
+++ b/src/assets/locale/es/common.json
@@ -84,5 +84,11 @@
}
},
"advanced": "Más Configuraciones del Modelo"
+ },
+ "copilot": {
+ "summary": "Resumir",
+ "explain": "Explicar",
+ "rephrase": "Reformular",
+ "translate": "Traducir"
}
-}
+}
\ No newline at end of file
diff --git a/src/assets/locale/es/settings.json b/src/assets/locale/es/settings.json
index 7884f46..bd0d2ad 100644
--- a/src/assets/locale/es/settings.json
+++ b/src/assets/locale/es/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Enviar notificación después de terminar el procesamiento de la base de conocimientos"
+ },
+ "generateTitle" :{
+ "label": "Generar título usando IA"
}
},
"sidepanelRag": {
@@ -147,6 +150,10 @@
"type": "Tipo de Prompt",
"actions": "Acciones"
},
+ "segmented": {
+ "custom": "Invites personnalisées",
+ "copilot": "Invites Copilot"
+ },
"systemPrompt": "Prompt del Sistema",
"quickPrompt": "Prompt Rápido",
"tooltip": {
@@ -170,7 +177,8 @@
"label": "Prompt",
"placeholder": "Ingrese un prompt",
"required": "Por favor, ingrese un prompt",
- "help": "Puede usar {key} como variable en su prompt."
+ "help": "Puede usar {key} como variable en su prompt.",
+ "missingTextPlaceholder": "Falta la variable {text} en el mensaje. Por favor, añádela."
},
"isSystem": {
"label": "Es un Prompt del Sistema"
diff --git a/src/assets/locale/fa/chrome.json b/src/assets/locale/fa/chrome.json
new file mode 100644
index 0000000..307388b
--- /dev/null
+++ b/src/assets/locale/fa/chrome.json
@@ -0,0 +1,13 @@
+{
+ "heading": "تنظیمات AI کروم",
+ "status": {
+ "label": "فعال یا غیر فعال کردن پشتیبانی از AI کروم"
+ },
+ "error": {
+ "browser_not_supported": "این نسخه از کروم توسط مدل Gemini Nano پشتیبانی نمی شود. لطفا به نسخه ۱۲۷ یا بالاتر به روزرسانی کنید.",
+ "ai_not_supported": "تنظیم chrome://flags/#prompt-api-for-gemini-nano فعال نشده است. لطفا فعالش کنید..",
+ "ai_not_ready": "Gemini Nano هنوز آماده نیست; تنظیمات کروم را مجددا بررسی کنید.",
+ "internal_error": "یک خطای داخلی رخ داد. لطفا بعدا مجددا تلاش نمایید."
+ },
+ "errorDescription": "برای استفاده از AI کروم، به نسخه مرورگر بالاتر از ۱۲۷ احتیاج دارید که اکنون در کانال های Dev و Canary در دسترس است. بعد از دریافت نسخه پشتیبانی شده این گام ها را دنبال کنید: \n\n۱. به `chrome://flags/#prompt-api-for-gemini-nano` بروید و \"Enable\" را انتخاب کنید.\n۲. به `chrome://flags/#optimization-guide-on-device-model` بروید و \"EnabledBypassPrefRequirement\" را انتخاب کنید.\n۳. به `chrome://components` بروید و \"Optimization Guide On Device Model\" را جستجو کنید، بر روی \"Check for Update\" کلیک کنید. این کار مدل را دریافت خواهد کرد. اگر این تنظیمات را نمی بینید، گام های ۱ و ۲ را تکرار و مرورگر را مجددا اجرا کنید."
+}
diff --git a/src/assets/locale/fa/common.json b/src/assets/locale/fa/common.json
new file mode 100644
index 0000000..3c02c21
--- /dev/null
+++ b/src/assets/locale/fa/common.json
@@ -0,0 +1,88 @@
+{
+ "pageAssist": "دستیار صفحه",
+ "selectAModel": "یک مدل انتخاب کنید",
+ "save": "ذخیره",
+ "saved": "ذخیره شد",
+ "cancel": "لغو",
+ "retry": "تلاش مجدد",
+ "share": {
+ "tooltip": {
+ "share": "اشتراک گذاری"
+ },
+ "modal": {
+ "title": "اشتراک گذاری لینک به گپ"
+ },
+ "form": {
+ "defaultValue": {
+ "name": "ناشناس",
+ "title": "گپ بی نام"
+ },
+ "title": {
+ "label": "عنوان گپ",
+ "placeholder": "عنوان گپ را وارد کنید",
+ "required": "وارد کردن عنوان چت الزامی است"
+ },
+ "name": {
+ "label": "نام شما",
+ "placeholder": "نام خود را وارد کنید",
+ "required": "وارد کردن نام الزامی است"
+ },
+ "btn": {
+ "save": "ایجاد لینک",
+ "saving": "در حال ایجاد لینک..."
+ }
+ },
+ "notification": {
+ "successGenerate": "پیوند در کلیپ بورد کپی شد",
+ "failGenerate": "پیوند ایجاد نشد"
+ }
+ },
+ "copyToClipboard": "کپی به کلیپ بورد",
+ "webSearch": "جستجوی وب",
+ "regenerate": "ایجاد مجدد",
+ "edit": "ویرایش",
+ "saveAndSubmit": "ذخیره و ارسال",
+ "editMessage": {
+ "placeholder": "یک پیام وارد کنید..."
+ },
+ "submit": "ارسال",
+ "noData": "اطلاعاتی وجود ندارد",
+ "noHistory": "تاریخچه گپ وجود ندارد",
+ "chatWithCurrentPage": "گپ زدن با این صفحه",
+ "beta": "بتا",
+ "tts": "بلند بخوان",
+ "currentChatModelSettings": "تنظیمات مدل گپ فعلی",
+ "modelSettings": {
+ "label": "تنظیمات مدل",
+ "description": "گزینه های مدل را به صورت کلی برای همه گپ ها تنظیم کنید",
+ "form": {
+ "keepAlive": {
+ "label": "Keep Alive",
+ "help": "کنترل می کند که چه مدت مدل پس از درخواست در حافظه باقی می ماند (پیش فرض: 5 دقیقه)",
+ "placeholder": "مدت زمان Keep Alive را وارد کنید (مثلا 5m, 10m, 1h)"
+ },
+ "temperature": {
+ "label": "Temperature",
+ "placeholder": "مقدار Temperature را وارد کنید (مثلا 0.7, 1.0)"
+ },
+ "numCtx": {
+ "label": "Number of Contexts",
+ "placeholder": "مقدار Number of Contexts را وارد کنید (پیش فرض: 2048)"
+ },
+ "seed": {
+ "label": "Seed",
+ "placeholder": "مقدار Seed را وارد کنید (e.g. 1234)",
+ "help": "تکرارپذیری خروجی مدل"
+ },
+ "topK": {
+ "label": "Top K",
+ "placeholder": "مقدار Top K را وارد کنید (مثلا 40, 100)"
+ },
+ "topP": {
+ "label": "Top P",
+ "placeholder": "مقدار Top P را وارد کنید (مثلا 0.9, 0.95)"
+ }
+ },
+ "advanced": "تنظیمات بیشتر مدل"
+ }
+}
diff --git a/src/assets/locale/fa/knowledge.json b/src/assets/locale/fa/knowledge.json
new file mode 100644
index 0000000..c955226
--- /dev/null
+++ b/src/assets/locale/fa/knowledge.json
@@ -0,0 +1,43 @@
+{
+ "addBtn": "افزودن دانش جدید",
+ "columns": {
+ "title": "عنوان",
+ "status": "وضعیت",
+ "embeddings": "مدل جاسازی",
+ "createdAt": "ایجاد شده در",
+ "action": "اقدامات"
+ },
+ "expandedColumns": {
+ "name": "نام"
+ },
+ "tooltip": {
+ "delete": "حذف"
+ },
+ "confirm": {
+ "delete": "آیا مطمئن هستید که می خواهید این دانش را حذف کنید؟"
+ },
+ "deleteSuccess": "دانش با موفقیت حذف شد",
+ "status": {
+ "pending": "انتظار",
+ "finished": "تمام",
+ "processing": "در حال پردازش",
+ "failed": "ناموفق"
+ },
+ "addKnowledge": "دانش را اضافه کنید",
+ "form": {
+ "title": {
+ "label": "عنوان دانش",
+ "placeholder": "عنوان دانش را وارد کنید",
+ "required": "وارد کردن عنوان دانش الزامی است"
+ },
+ "uploadFile": {
+ "label": "آپلود فایل",
+ "uploadText": "یک فایل را در اینجا بکشید و رها کنید یا برای آپلود کلیک کنید",
+ "uploadHint": "انواع فایل های پشتیبانی شده: .pdf, .csv, .txt, .md, .docx",
+ "required": "وارد کردن فایل مورد نیاز است"
+ },
+ "submit": "ارسال",
+ "success": "دانش با موفقیت اضافه شد"
+ },
+ "noEmbeddingModel": "لطفا ابتدا یک مدل جاسازی را از صفحه تنظیمات RAG اضافه کنید"
+}
diff --git a/src/assets/locale/fa/option.json b/src/assets/locale/fa/option.json
new file mode 100644
index 0000000..9188710
--- /dev/null
+++ b/src/assets/locale/fa/option.json
@@ -0,0 +1,12 @@
+{
+ "newChat": "گپ جدید",
+ "selectAPrompt": "یک پرامپت را انتخاب کنید",
+ "githubRepository": "مخزن GitHub",
+ "settings": "تنظیمات",
+ "sidebarTitle": "تاریخچه گپ",
+ "error": "خطا",
+ "somethingWentWrong": "مشکلی پیش آمد",
+ "validationSelectModel": "لطفا یک مدل را برای ادامه انتخاب کنید",
+ "deleteHistoryConfirmation": "آیا مطمئن هستید که می خواهید این تاریخچه را حذف کنید؟",
+ "editHistoryTitle": "یک عنوان جدید وارد کنید"
+}
diff --git a/src/assets/locale/fa/playground.json b/src/assets/locale/fa/playground.json
new file mode 100644
index 0000000..e9161a7
--- /dev/null
+++ b/src/assets/locale/fa/playground.json
@@ -0,0 +1,29 @@
+{
+ "ollamaState": {
+ "searching": "در حال جستجوی Ollama شما 🦙",
+ "running": "Ollama در حال اجرا است 🦙",
+ "notRunning": "امکان اتصال به Ollama وجود ندارد 🦙",
+ "connectionError": "به نظر می رسد که خطای اتصال دارید. لطفا برای عیب یابی به این مستندات مراجعه کنید."
+ },
+ "formError": {
+ "noModel": "لطفا یک مدل را انتخاب کنید",
+ "noEmbeddingModel": "لطفا یک مدل جاسازی در صفحه تنظیمات > RAG تنظیم کنید"
+ },
+ "form": {
+ "textarea": {
+ "placeholder": "یک پیام تایپ کنید..."
+ },
+ "webSearch": {
+ "on": "روشن",
+ "off": "خاموش"
+ }
+ },
+ "tooltip": {
+ "searchInternet": "جستجوی اینترنت",
+ "speechToText": "گفتار به متن",
+ "uploadImage": "آپلود تصویر",
+ "stopStreaming": "توقف Streaming",
+ "knowledge": "دانش"
+ },
+ "sendWhenEnter": "با فشار دادن Enter ارسال شود"
+}
diff --git a/src/assets/locale/fa/settings.json b/src/assets/locale/fa/settings.json
new file mode 100644
index 0000000..f1d2d13
--- /dev/null
+++ b/src/assets/locale/fa/settings.json
@@ -0,0 +1,335 @@
+{
+ "generalSettings": {
+ "title": "تنظیمات عمومی",
+ "settings": {
+ "heading": "تنظیمات رابط کاربری وب",
+ "speechRecognitionLang": {
+ "label": "زبان تشخیص گفتار",
+ "placeholder": "یک زبان را انتخاب کنید"
+ },
+ "language": {
+ "label": "زبان",
+ "placeholder": "یک زبان را انتخاب کنید"
+ },
+ "darkMode": {
+ "label": "تغییر تم",
+ "options": {
+ "light": "روشن",
+ "dark": "تیره"
+ }
+ },
+ "copilotResumeLastChat": {
+ "label": "آخرین گفتگو را هنگام باز کردن SidePanel (Copilot) از سر بگیر"
+ },
+ "hideCurrentChatModelSettings": {
+ "label": "مخفی کردن تنظیمات مدل گپ فعلی را"
+ },
+ "restoreLastChatModel": {
+ "label": "بازیابی آخرین مدل استفاده شده برای گپهای قبلی"
+ },
+ "sendNotificationAfterIndexing": {
+ "label": "ارسال نوتیفیکیشن پس از اتمام پردازش پایگاه دانش"
+ },
+ "generateTitle" :{
+ "label": "تولید عنوان با استفاده از هوش مصنوعی"
+ }
+ },
+ "sidepanelRag": {
+ "heading": "تنظیمات گپ Copilot با وب سایت",
+ "ragEnabled": {
+ "label": "چت با وب سایت با استفاده از بردارهای جاسازی"
+ },
+ "maxWebsiteContext": {
+ "label": "اندازه محتوای وب سایت حالت عادی",
+ "placeholder": "اندازه محتوا (پیش فرض 4028)"
+ }
+ },
+ "webSearch": {
+ "heading": "مدیریت جستجوی وب",
+ "searchMode": {
+ "label": "انجام جستجوی ساده اینترنتی"
+ },
+ "provider": {
+ "label": "موتور جستجو",
+ "placeholder": "یک موتور جستجو را انتخاب کنید"
+ },
+ "totalSearchResults": {
+ "label": "مجموع نتایج جستجو",
+ "placeholder": "کل نتایج جستجو را وارد کنید"
+ },
+ "visitSpecificWebsite": {
+ "label": "مراجعه به وب سایت ذکر شده در پیام"
+ }
+ },
+ "system": {
+ "heading": "تنظیمات سیستم",
+ "deleteChatHistory": {
+ "label": "حذف تاریخچه گفتگو",
+ "button": "حذف",
+ "confirm": "آیا مطمئن هستید که می خواهید تاریخچه گفتگوهای خود را حذف کنید؟ این عمل قابل برگشت نیست."
+ },
+ "export": {
+ "label": "تاریخچه گپ، پایگاه دانش و پرامپتها را اکسپورت کنید",
+ "button": "اکسپورت دادهها",
+ "success": "موفقیت در اکسپورت"
+ },
+ "import": {
+ "label": "تاریخچه گپ، پایگاه دانش و پرامپتها را ایمپورت کنید",
+ "button": "ایمپورت دادهها",
+ "success": "موفقیت در ایمپورت",
+ "error": "خطا در ایمپورت"
+ }
+ },
+ "tts": {
+ "heading": "تنظیمات تبدیل متن به گفتار",
+ "ttsEnabled": {
+ "label": "فعال کردن تبدیل متن به گفتار"
+ },
+ "ttsProvider": {
+ "label": "ارائه دهنده تبدیل متن به گفتار",
+ "placeholder": "یک ارائه دهنده را انتخاب کنید"
+ },
+ "ttsVoice": {
+ "label": "صدای تبدیل متن به گفتار",
+ "placeholder": "صدا را انتخاب کنید"
+ },
+ "ssmlEnabled": {
+ "label": "فعال کردن SSML (Speech Synthesis Markup Language)"
+ }
+ }
+ },
+ "manageModels": {
+ "title": "مدیریت مدلها",
+ "addBtn": "افزودن مدل جدید",
+ "columns": {
+ "name": "نام",
+ "digest": "هضم",
+ "modifiedAt": "اصلاح شده در",
+ "size": "اندازه",
+ "actions": "اقدامات"
+ },
+ "expandedColumns": {
+ "parentModel": "مدل والد",
+ "format": "فرمت",
+ "family": "خانواده",
+ "parameterSize": "اندازه پارامترها",
+ "quantizationLevel": "سطح کوانتیزاسیون"
+ },
+ "tooltip": {
+ "delete": "حذف مدل",
+ "repull": "دریافت دوباره مدل"
+ },
+ "confirm": {
+ "delete": "آیا مطمئن هستید که می خواهید این مدل را حذف کنید؟",
+ "repull": "آیا مطمئن هستید که می خواهید این مدل را دوباره دریافت کنید؟"
+ },
+ "modal": {
+ "title": "افزودن مدل جدید",
+ "placeholder": "نام مدل را وارد کنید",
+ "pull": "دریافت مدل"
+ },
+ "notification": {
+ "pullModel": "در حال دریافت مدل",
+ "pullModelDescription": "در حال دریافت مدل {{modelName}}. برای جزئیات بیشتر، آیکون افزونه را بررسی کنید.",
+ "success": "موفقیت",
+ "error": "خطا",
+ "successDescription": "مدل با موفقیت دریافت شد",
+ "successDeleteDescription": "مدل با موفقیت حذف شد",
+ "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید"
+ }
+ },
+ "managePrompts": {
+ "title": "مدیریت پرامپتها",
+ "addBtn": "اضافه کردن پرامپت جدید",
+ "option1": "عادی",
+ "option2": "RAG",
+ "questionPrompt": "سوال پرامپت",
+ "columns": {
+ "title": "عنولن",
+ "prompt": "پرامپت",
+ "type": "نوع پرامپت",
+ "actions": "اقدامات"
+ },
+ "segmented": {
+ "custom": "پرامپتهای سفارشی",
+ "copilot": "پرامپتهای کوپایلوت"
+ },
+ "systemPrompt": "پرامپت سیستم ",
+ "quickPrompt": "پرامپت سریع",
+ "tooltip": {
+ "delete": "پاک کردن پرامپت",
+ "edit": "ویرایش پرامپت"
+ },
+ "confirm": {
+ "delete": "آیا مطمئن هستید که می خواهید این پرامپت را حذف کنید؟ این عمل قابل برگشت نیست."
+ },
+ "modal": {
+ "addTitle": "اضافه کردن پرامپت جدید",
+ "editTitle": "ویرایش پرامپت"
+ },
+ "form": {
+ "title": {
+ "label": "عنوان",
+ "placeholder": "پرامپت عالی من",
+ "required": "لطفا یک عنوان وارد کنید"
+ },
+ "prompt": {
+ "label": "پرامپت",
+ "placeholder": "پرامپت وارد کنید",
+ "required": "لطفا یک پرامپت وارد کنید",
+ "help": "می توانید از {key} به عنوان متغیر در درخواست خود استفاده کنید."
+ },
+ "isSystem": {
+ "label": "یک پرامپت سیستمی باشد"
+ },
+ "btnSave": {
+ "saving": "در حال اضافه کردن پرامپت...",
+ "save": "اضافه کردن پرامپت"
+ },
+ "btnEdit": {
+ "saving": "در حال به روزرسانی پرامپت...",
+ "save": "به روزرسانی پرامپت"
+ }
+ },
+ "notification": {
+ "addSuccess": "پرامپت اضافه شد",
+ "addSuccessDesc": "پرامپت با موفقیت اضافه شد",
+ "error": "خطا",
+ "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید",
+ "updatedSuccess": "پرامپت به روز شد",
+ "updatedSuccessDesc": "پرامپت با موفقیت به روز شد",
+ "deletedSuccess": "پرامپت حذف شد",
+ "deletedSuccessDesc": "پرامپت با موفقیت حذف شد"
+ }
+ },
+ "manageShare": {
+ "title": "مدیریت اشتراک گذاری",
+ "heading": "پیکربندی URL اشتراک گذاری صفحه",
+ "form": {
+ "url": {
+ "label": "آدرس اشتراک گذاری صفحه",
+ "placeholder": "URL اشتراک گذاری صفحه را وارد کنید",
+ "required": "لطفا URL اشتراک گذاری صفحه خود را وارد کنید!",
+ "help": "به دلایل حفظ حریم خصوصی، می توانید اشتراک گذاری صفحه را خودتان میزبانی کنید و URL را در اینجا ارائه دهید. بیشتر بدانید."
+ }
+ },
+ "webshare": {
+ "heading": "اشتراک گذاری وب",
+ "columns": {
+ "title": "عنوان",
+ "url": "URL",
+ "actions": "اقدامات"
+ },
+ "tooltip": {
+ "delete": "حذف اشتراک گذاری"
+ },
+ "confirm": {
+ "delete": "آیا مطمئن هستید که می خواهید این اشتراک گذاری را حذف کنید؟ این عمل قابل برگشت نیست."
+ },
+ "label": "مدیریت اشتراک گذاری صفحه",
+ "description": "ویژگی اشتراک گذاری صفحه را فعال یا غیرفعال کنید"
+ },
+ "notification": {
+ "pageShareSuccess": "URL اشتراک گذاری صفحه با موفقیت به روز شد",
+ "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید",
+ "webShareDeleteSuccess": "اشتراک گذاری وب با موفقیت حذف شد"
+ }
+ },
+ "ollamaSettings": {
+ "title": "تنظیمات Ollama",
+ "heading": "پیکربندی Ollama",
+ "settings": {
+ "ollamaUrl": {
+ "label": "آدرس Ollama",
+ "placeholder": "URL Ollama را وارد کنید"
+ },
+ "advanced": {
+ "label": "پیکربندی پیشرفته URL Ollama",
+ "urlRewriteEnabled": {
+ "label": "URL مبدا سفارشی را فعال یا غیرفعال کنید"
+ },
+ "rewriteUrl": {
+ "label": "URL مبدا سفارشی",
+ "placeholder": "URL مبدا سفارشی را وارد کنید"
+ },
+ "headers": {
+ "label": "هدرهای سفارشی",
+ "add": "افزودن هدر",
+ "key": {
+ "label": "کلید هدر",
+ "placeholder": "Authorization"
+ },
+ "value": {
+ "label": "مقدار هدر",
+ "placeholder": "Bearer token"
+ }
+ },
+ "help": "اگر با Ollama در Page Assist مشکل اتصال دارید، می توانید یک URL اصلی سفارشی پیکربندی کنید. برای کسب اطلاعات بیشتر در مورد پیکربندی، اینجا را کلیک کنید."
+ }
+ }
+ },
+ "manageSearch": {
+ "title": "مدیریت جستجوی وب",
+ "heading": "پیکربندی جستجوی وب"
+ },
+ "about": {
+ "title": "درباره",
+ "heading": "درباره",
+ "chromeVersion": "نسخه دستیار صفحه",
+ "ollamaVersion": "نسخه Ollama",
+ "support": "شما می توانید با کمک مالی یا حمایت مالی از طریق پلتفرم های زیر از پروژه Page Assist حمایت کنید:",
+ "koFi": "پشتیبانی در Ko-fi",
+ "githubSponsor": "حمایت مالی در GitHub",
+ "githubRepo": "مخزن GitHub"
+ },
+ "manageKnowledge": {
+ "title": "مدیریت دانش",
+ "heading": "پیکربندی پایگاه دانش"
+ },
+ "rag": {
+ "title": "تنظیمات RAG",
+ "ragSettings": {
+ "label": "تنظیمات RAG",
+ "model": {
+ "label": "مدل جاسازی",
+ "required": "لطفا یک مدل را انتخاب کنید",
+ "help": "به شدت توصیه می شود از مدل های جاسازی مانند `nomic-embed-text` استفاده کنید.",
+ "placeholder": "یک مدل را انتخاب کنید"
+ },
+ "chunkSize": {
+ "label": "اندازه تکه",
+ "placeholder": "اندازه تکه را وارد کنید",
+ "required": "لطفا اندازه تکه را وارد کنید"
+ },
+ "chunkOverlap": {
+ "label": "همپوشانی تکه",
+ "placeholder": "داخل تکه همپوشانی شوید",
+ "required": "لطفا یک همپوشانی تکه ای وارد کنید"
+ },
+ "totalFilePerKB": {
+ "label": "محدودیت فایل پیش فرض پایگاه دانش",
+ "placeholder": "محدودیت فایل پیش فرض را وارد کنید (به عنوان مثال، 10)",
+ "required": "لطفا محدودیت پیش فرض فایل را وارد کنید"
+ }
+ },
+ "prompt": {
+ "label": "پیکربندی پرامپت RAG",
+ "option1": "عادی",
+ "option2": "وب",
+ "alert": "پیکربندی اعلان سیستم در اینجا منسوخ شده است. لطفا از بخش مدیریت پرامپتها برای افزودن یا ویرایش درخواستها استفاده کنید. این بخش در نسخه بعدی حذف خواهد شد",
+ "systemPrompt": "پرامپت سیستم",
+ "systemPromptPlaceholder": "پرامپت سیستم را وارد کنید",
+ "webSearchPrompt": "پرامپت جستجوی وب",
+ "webSearchPromptHelp": "«{search_results}» را از پرامپت حذف نکنید.",
+ "webSearchPromptError": "لطفا یک پرامپت جستجوی وب وارد کنید",
+ "webSearchPromptPlaceholder": "پرامپت جستجوی وب را وارد کنید",
+ "webSearchFollowUpPrompt": "پرامپت پیگیری جستجوی وب",
+ "webSearchFollowUpPromptHelp": "«{chat_history}» و «{question}» را از پرامپت حذف نکنید.",
+ "webSearchFollowUpPromptError": "لطفا پرامپت پیگیری جستجوی وب خود را وارد کنید!",
+ "webSearchFollowUpPromptPlaceholder": "پرامپت پیگیری جستجوی وب شما"
+ }
+ },
+ "chromeAiSettings": {
+ "title": "تنظیمات هوش مصنوعی کروم"
+ }
+}
diff --git a/src/assets/locale/fa/sidepanel.json b/src/assets/locale/fa/sidepanel.json
new file mode 100644
index 0000000..8ef8579
--- /dev/null
+++ b/src/assets/locale/fa/sidepanel.json
@@ -0,0 +1,7 @@
+{
+ "tooltip": {
+ "embed": "ممکن است چند دقیقه طول بکشد تا صفحه جاسازی شود. لطفاً منتظر بمانید...",
+ "clear": "پاک کردن تاریخچه گپ",
+ "history": "تاریخچه گپ"
+ }
+}
diff --git a/src/assets/locale/fr/common.json b/src/assets/locale/fr/common.json
index 328acae..cf601db 100644
--- a/src/assets/locale/fr/common.json
+++ b/src/assets/locale/fr/common.json
@@ -51,7 +51,7 @@
"chatWithCurrentPage": "Discuter avec la page actuelle",
"beta": "Bêta",
"tts": "Synthèse vocale",
- "currentChatModelSettings":"Paramètres actuels du modèle de chat",
+ "currentChatModelSettings": "Paramètres actuels du modèle de chat",
"modelSettings": {
"label": "Paramètres du modèle",
"description": "Définissez les options de modèle globale pour tous les chats",
@@ -84,5 +84,11 @@
}
},
"advanced": "Plus de paramètres du modèle"
+ },
+ "copilot": {
+ "summary": "Résumer",
+ "explain": "Expliquer",
+ "rephrase": "Reformuler",
+ "translate": "Traduire"
}
}
\ No newline at end of file
diff --git a/src/assets/locale/fr/settings.json b/src/assets/locale/fr/settings.json
index eaf89af..3310afd 100644
--- a/src/assets/locale/fr/settings.json
+++ b/src/assets/locale/fr/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Envoyer une notification après avoir terminé le traitement de la base de connaissances"
+ },
+ "generateTitle" :{
+ "label": "Générer le titre en utilisant l'IA"
}
},
"sidepanelRag": {
@@ -160,6 +163,10 @@
"addTitle": "Ajouter un nouveau prompt",
"editTitle": "Modifier le prompt"
},
+ "segmented": {
+ "custom": "Invites personnalisées",
+ "copilot": "Invites Copilot"
+ },
"form": {
"title": {
"label": "Titre",
@@ -170,7 +177,8 @@
"label": "Prompt",
"placeholder": "Entrer Prompt",
"required": "Veuillez entrer un prompt",
- "help": "Vous pouvez utiliser {key} comme variable dans votre prompt."
+ "help": "Vous pouvez utiliser {key} comme variable dans votre prompt.",
+ "missingTextPlaceholder": "La variable {text} est manquante dans le message. Veuillez l'ajouter."
},
"isSystem": {
"label": "Est un prompt système"
diff --git a/src/assets/locale/it/common.json b/src/assets/locale/it/common.json
index df4f4c3..0b0708e 100644
--- a/src/assets/locale/it/common.json
+++ b/src/assets/locale/it/common.json
@@ -84,5 +84,11 @@
}
},
"advanced": "Altre Impostazioni del Modello"
+ },
+ "copilot": {
+ "summary": "Riassumere",
+ "explain": "Spiegare",
+ "rephrase": "Riformulare",
+ "translate": "Tradurre"
}
-}
+}
\ No newline at end of file
diff --git a/src/assets/locale/it/settings.json b/src/assets/locale/it/settings.json
index 07f236f..ba6ce98 100644
--- a/src/assets/locale/it/settings.json
+++ b/src/assets/locale/it/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Inviare notifica dopo aver terminato l'elaborazione della base di conoscenza"
+ },
+ "generateTitle" :{
+ "label": "Genera titolo utilizzando l'IA"
}
},
"sidepanelRag": {
@@ -160,6 +163,10 @@
"addTitle": "Aggiungi Nuovo Prompt",
"editTitle": "Modifica Prompt"
},
+ "segmented": {
+ "custom": "Prompt personalizzati",
+ "copilot": "Prompt Copilot"
+ },
"form": {
"title": {
"label": "Titolo",
@@ -170,7 +177,8 @@
"label": "Prompt",
"placeholder": "Inserisci Prompt",
"required": "Scrivi il prompt",
- "help": "Puoi usare {key} come variabile nel tuo prompt."
+ "help": "Puoi usare {key} come variabile nel tuo prompt.",
+ "missingTextPlaceholder": "La variabile {text} manca nel prompt. Per favore, aggiungila."
},
"isSystem": {
"label": "Prompt di Sistema"
diff --git a/src/assets/locale/ja-JP/common.json b/src/assets/locale/ja-JP/common.json
index 7519408..4fcde04 100644
--- a/src/assets/locale/ja-JP/common.json
+++ b/src/assets/locale/ja-JP/common.json
@@ -1,88 +1,94 @@
{
- "pageAssist": "ページアシスト",
- "selectAModel": "モデルを選択",
- "save": "保存",
- "saved": "保存済み",
- "cancel": "キャンセル",
- "retry": "再試行",
- "share": {
- "tooltip": {
- "share": "共有"
+ "pageAssist": "ページアシスト",
+ "selectAModel": "モデルを選択",
+ "save": "保存",
+ "saved": "保存済み",
+ "cancel": "キャンセル",
+ "retry": "再試行",
+ "share": {
+ "tooltip": {
+ "share": "共有"
+ },
+ "modal": {
+ "title": "チャットリンクを共有"
+ },
+ "form": {
+ "defaultValue": {
+ "name": "匿名",
+ "title": "無題のチャット"
},
- "modal": {
- "title": "チャットリンクを共有"
+ "title": {
+ "label": "チャットタイトル",
+ "placeholder": "チャットタイトルを入力",
+ "required": "チャットタイトルは必須です"
},
- "form": {
- "defaultValue": {
- "name": "匿名",
- "title": "無題のチャット"
- },
- "title": {
- "label": "チャットタイトル",
- "placeholder": "チャットタイトルを入力",
- "required": "チャットタイトルは必須です"
- },
- "name": {
- "label": "あなたの名前",
- "placeholder": "名前を入力",
- "required": "名前は必須です"
- },
- "btn": {
- "save": "リンクを生成",
- "saving": "リンクを生成中..."
- }
+ "name": {
+ "label": "あなたの名前",
+ "placeholder": "名前を入力",
+ "required": "名前は必須です"
},
- "notification": {
- "successGenerate": "リンクがクリップボードにコピーされました",
- "failGenerate": "リンクの生成に失敗しました"
+ "btn": {
+ "save": "リンクを生成",
+ "saving": "リンクを生成中..."
}
},
- "copyToClipboard": "クリップボードにコピー",
- "webSearch": "ウェブを検索中",
- "regenerate": "再生成",
- "edit": "編集",
- "saveAndSubmit": "保存して送信",
- "editMessage": {
- "placeholder": "メッセージを入力..."
- },
- "submit": "送信",
- "noData": "データがありません",
- "noHistory": "チャット履歴がありません",
- "chatWithCurrentPage": "現在のページでチャット",
- "beta": "ベータ",
- "tts": "読み上げ",
- "currentChatModelSettings": "現在のチャットモデル設定",
- "modelSettings": {
- "label": "モデル設定",
- "description": "すべてのチャットに対してモデルオプションをグローバルに設定します",
- "form": {
- "keepAlive": {
- "label": "キープアライブ",
- "help": "リクエスト後にモデルがメモリに保持される時間をコントロールします(デフォルト: 5 分)",
- "placeholder": "キープアライブの期間を入力してください(例:5分、10分、1時間)"
- },
- "temperature": {
- "label": "温度",
- "placeholder": "温度値を入力してください(例:0.7、1.0)"
- },
- "numCtx": {
- "label": "コンテキストの数",
- "placeholder": "コンテキスト数を入力してください(デフォルト:2048)"
- },
- "seed": {
- "label": "シード",
- "placeholder": "シード値を入力してください(例:1234)",
- "help": "モデル出力の再現性"
- },
- "topK": {
- "label": "Top K",
- "placeholder": "Top K値を入力してください(例:40、100)"
- },
- "topP": {
- "label": "Top P",
- "placeholder": "Top P値を入力してください(例:0.9、0.95)"
- }
- },
- "advanced": "その他のモデル設定"
+ "notification": {
+ "successGenerate": "リンクがクリップボードにコピーされました",
+ "failGenerate": "リンクの生成に失敗しました"
}
- }
\ No newline at end of file
+ },
+ "copyToClipboard": "クリップボードにコピー",
+ "webSearch": "ウェブを検索中",
+ "regenerate": "再生成",
+ "edit": "編集",
+ "saveAndSubmit": "保存して送信",
+ "editMessage": {
+ "placeholder": "メッセージを入力..."
+ },
+ "submit": "送信",
+ "noData": "データがありません",
+ "noHistory": "チャット履歴がありません",
+ "chatWithCurrentPage": "現在のページでチャット",
+ "beta": "ベータ",
+ "tts": "読み上げ",
+ "currentChatModelSettings": "現在のチャットモデル設定",
+ "modelSettings": {
+ "label": "モデル設定",
+ "description": "すべてのチャットに対してモデルオプションをグローバルに設定します",
+ "form": {
+ "keepAlive": {
+ "label": "キープアライブ",
+ "help": "リクエスト後にモデルがメモリに保持される時間をコントロールします(デフォルト: 5 分)",
+ "placeholder": "キープアライブの期間を入力してください(例:5分、10分、1時間)"
+ },
+ "temperature": {
+ "label": "温度",
+ "placeholder": "温度値を入力してください(例:0.7、1.0)"
+ },
+ "numCtx": {
+ "label": "コンテキストの数",
+ "placeholder": "コンテキスト数を入力してください(デフォルト:2048)"
+ },
+ "seed": {
+ "label": "シード",
+ "placeholder": "シード値を入力してください(例:1234)",
+ "help": "モデル出力の再現性"
+ },
+ "topK": {
+ "label": "Top K",
+ "placeholder": "Top K値を入力してください(例:40、100)"
+ },
+ "topP": {
+ "label": "Top P",
+ "placeholder": "Top P値を入力してください(例:0.9、0.95)"
+ }
+ },
+ "advanced": "その他のモデル設定"
+ },
+ "copilot": {
+ "summary": "要約",
+ "explain": "説明",
+ "rephrase": "言い換え",
+ "translate": "翻訳"
+ }
+}
\ No newline at end of file
diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json
index 546f5fd..1180f4c 100644
--- a/src/assets/locale/ja-JP/settings.json
+++ b/src/assets/locale/ja-JP/settings.json
@@ -32,6 +32,9 @@
},
"sendNotificationAfterIndexing": {
"label": "ナレッジベースの処理完了後に通知を送信"
+ },
+ "generateTitle": {
+ "label": "AIを使用してタイトルを生成"
}
},
"sidepanelRag": {
@@ -163,6 +166,10 @@
"addTitle": "新しいプロンプトを追加",
"editTitle": "プロンプトを編集"
},
+ "segmented": {
+ "custom": "カスタムプロンプト",
+ "copilot": "Copilotプロンプト"
+ },
"form": {
"title": {
"label": "タイトル",
@@ -173,7 +180,8 @@
"label": "プロンプト",
"placeholder": "プロンプトを入力",
"required": "プロンプトを入力してください",
- "help": "プロンプト内で{key}を変数として使用できます。"
+ "help": "プロンプト内で{key}を変数として使用できます。",
+ "missingTextPlaceholder": "プロンプトに{text}変数がありません。追加してください。"
},
"isSystem": {
"label": "システムプロンプト"
diff --git a/src/assets/locale/ml/common.json b/src/assets/locale/ml/common.json
index ad3d9cc..4d923c0 100644
--- a/src/assets/locale/ml/common.json
+++ b/src/assets/locale/ml/common.json
@@ -83,5 +83,11 @@
}
},
"advanced": "കൂടുതൽ മോഡൽ ക്രമീകരണങ്ങൾ"
+ },
+ "copilot": {
+ "summary": "സംഗ്രഹിക്കുക",
+ "explain": "വിശദീകരിക്കുക",
+ "rephrase": "പുനഃരൂപീകരിക്കുക",
+ "translate": "വിവർത്തനം ചെയ്യുക"
}
}
\ No newline at end of file
diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json
index 9578064..c151ff9 100644
--- a/src/assets/locale/ml/settings.json
+++ b/src/assets/locale/ml/settings.json
@@ -32,6 +32,9 @@
},
"sendNotificationAfterIndexing": {
"label": "അറിവ് ശേഖരം പ്രോസസ്സ് ചെയ്ത് കഴിഞ്ഞതിന് ശേഷം അറിയിപ്പ് അയയ്ക്കുക"
+ },
+ "generateTitle": {
+ "label": "എഐ ഉപയോഗിച്ച് ശീർഷകം സൃഷ്ടിക്കുക"
}
},
"sidepanelRag": {
@@ -163,6 +166,10 @@
"addTitle": "പുതിയ പ്രോംപ്റ്റ് ചേര്ക്കുക",
"editTitle": "പ്രോംപ്റ്റ് എഡിറ്റുചെയ്യുക"
},
+ "segmented": {
+ "custom": "കസ്റ്റം പ്രോംപ്റ്റുകൾ",
+ "copilot": "കോപൈലറ്റ് പ്രോംപ്റ്റുകൾ"
+ },
"form": {
"title": {
"label": "തലക്കെട്ട്",
@@ -173,7 +180,8 @@
"label": "പ്രോംപ്റ്റ്",
"placeholder": "പ്രോംപ്റ്റ് നല്കുക",
"required": "ദയവായി ഒരു പ്രോംപ്റ്റ് നല്കുക",
- "help": "നിങ്ങള്ക്ക് {key} എന്ന രീതിയില് പ്രോംപ്റ്റില് വേരിയബിളുകള് ഉപയോഗിക്കാവുന്നതാണ്."
+ "help": "നിങ്ങള്ക്ക് {key} എന്ന രീതിയില് പ്രോംപ്റ്റില് വേരിയബിളുകള് ഉപയോഗിക്കാവുന്നതാണ്.",
+ "missingTextPlaceholder": "പ്രോംപ്റ്റിൽ {text} വേരിയബിൾ കാണുന്നില്ല. ദയവായി അത് ചേർക്കുക."
},
"isSystem": {
"label": "സിസ്റ്റം പ്രോംപ്റ്റ് ആണോ"
diff --git a/src/assets/locale/pt-BR/common.json b/src/assets/locale/pt-BR/common.json
index 6f3b70b..a322b7c 100644
--- a/src/assets/locale/pt-BR/common.json
+++ b/src/assets/locale/pt-BR/common.json
@@ -84,5 +84,11 @@
}
},
"advanced": "Mais Configurações do Modelo"
+ },
+ "copilot": {
+ "summary": "Resumir",
+ "explain": "Explicar",
+ "rephrase": "Reformular",
+ "translate": "Traduzir"
}
}
\ No newline at end of file
diff --git a/src/assets/locale/pt-BR/settings.json b/src/assets/locale/pt-BR/settings.json
index 5de4779..7840f31 100644
--- a/src/assets/locale/pt-BR/settings.json
+++ b/src/assets/locale/pt-BR/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Enviar notificação após concluir o processamento da base de conhecimento"
+ },
+ "generateTitle": {
+ "label": "Gerar título usando IA"
}
},
"sidepanelRag": {
@@ -160,6 +163,10 @@
"addTitle": "Adicionar Novo Prompt",
"editTitle": "Editar Prompt"
},
+ "segmented": {
+ "custom": "Prompts personalizados",
+ "copilot": "Prompts do Copilot"
+ },
"form": {
"title": {
"label": "Título",
@@ -170,7 +177,8 @@
"label": "Prompt",
"placeholder": "Digite o Prompt",
"required": "Por favor, insira um prompt",
- "help": "Você pode usar {key} como variável em seu prompt."
+ "help": "Você pode usar {key} como variável em seu prompt.",
+ "missingTextPlaceholder": "A variável {text} está faltando no prompt. Por favor, adicione-a."
},
"isSystem": {
"label": "É um Prompt do Sistema"
diff --git a/src/assets/locale/ru/common.json b/src/assets/locale/ru/common.json
index 7deac9a..b14c855 100644
--- a/src/assets/locale/ru/common.json
+++ b/src/assets/locale/ru/common.json
@@ -84,5 +84,11 @@
}
},
"advanced": "Больше настроек модели"
+ },
+ "copilot": {
+ "summary": "Обобщить",
+ "explain": "Объяснить",
+ "rephrase": "Перефразировать",
+ "translate": "Перевести"
}
-}
+}
\ No newline at end of file
diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json
index f2f4619..2a8f95b 100644
--- a/src/assets/locale/ru/settings.json
+++ b/src/assets/locale/ru/settings.json
@@ -29,6 +29,9 @@
},
"sendNotificationAfterIndexing": {
"label": "Отправить уведомление после завершения обработки базы знаний"
+ },
+ "generateTitle": {
+ "label": "Сгенерировать заголовок с помощью ИИ"
}
},
"sidepanelRag": {
@@ -161,6 +164,10 @@
"addTitle": "Добавить новую подсказку",
"editTitle": "Редактировать подсказку"
},
+ "segmented": {
+ "custom": "Пользовательские подсказки",
+ "copilot": "Подсказки Copilot"
+ },
"form": {
"title": {
"label": "Название",
@@ -171,7 +178,8 @@
"label": "Подсказка",
"placeholder": "Введите подсказку",
"required": "Пожалуйста, введите подсказку",
- "help": "Вы можете использовать {key} в качестве переменной в своей подсказке."
+ "help": "Вы можете использовать {key} в качестве переменной в своей подсказке.",
+ "missingTextPlaceholder": "Переменная {text} отсутствует в подсказке. Пожалуйста, добавьте ее."
},
"isSystem": {
"label": "Это системная подсказка"
diff --git a/src/assets/locale/zh/common.json b/src/assets/locale/zh/common.json
index dca8241..9145e48 100644
--- a/src/assets/locale/zh/common.json
+++ b/src/assets/locale/zh/common.json
@@ -84,5 +84,11 @@
}
},
"advanced": "更多模型设置"
+ },
+ "copilot": {
+ "summary": "总结",
+ "explain": "解释",
+ "rephrase": "重述",
+ "translate": "翻译"
}
}
\ No newline at end of file
diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json
index 6cfbad2..4716831 100644
--- a/src/assets/locale/zh/settings.json
+++ b/src/assets/locale/zh/settings.json
@@ -32,6 +32,9 @@
},
"sendNotificationAfterIndexing": {
"label": "完成知识库处理后发送通知"
+ },
+ "generateTitle": {
+ "label": "使用人工智能生成标题"
}
},
"sidepanelRag": {
@@ -163,6 +166,10 @@
"addTitle": "添加新提示词",
"editTitle": "编辑提示词"
},
+ "segmented": {
+ "custom": "自定义提示",
+ "copilot": "Copilot 提示"
+ },
"form": {
"title": {
"label": "标题",
@@ -174,7 +181,8 @@
"label": "提示词",
"placeholder": "输入提示词",
"required": "请输入提示词",
- "help": "您可以在提示词中使用 {key} 作为变量。"
+ "help": "您可以在提示词中使用 {key} 作为变量。",
+ "missingTextPlaceholder": "提示中缺少{text}变量。请添加它。"
},
"isSystem": {
"label": "是否为系统提示词"
diff --git a/src/components/Common/Playground/Message.tsx b/src/components/Common/Playground/Message.tsx
index c23c90e..b21bbe1 100644
--- a/src/components/Common/Playground/Message.tsx
+++ b/src/components/Common/Playground/Message.tsx
@@ -1,6 +1,6 @@
import Markdown from "../../Common/Markdown"
import React from "react"
-import { Image, Tooltip } from "antd"
+import { Tag, Image, Tooltip } from "antd"
import { WebSearch } from "./WebSearch"
import {
CheckIcon,
@@ -14,9 +14,11 @@ import { EditMessageForm } from "./EditMessageForm"
import { useTranslation } from "react-i18next"
import { MessageSource } from "./MessageSource"
import { useTTS } from "@/hooks/useTTS"
+import { tagColors } from "@/utils/color"
type Props = {
message: string
+ message_type?: string
hideCopy?: boolean
botAvatar?: JSX.Element
userAvatar?: JSX.Element
@@ -76,13 +78,23 @@ export const PlaygroundMessage = (props: Props) => {
props.currentMessageIndex === props.totalMessages - 1 ? (
) : null}
-
+
+ {props?.message_type && (
+
+ {t(`copilot.${props?.message_type}`)}
+
+ )}
+
{!editMode ? (
props.isBot ? (
) : (
-
+
{props.message}
)
diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx
index 8d248f8..274600e 100644
--- a/src/components/Option/Playground/PlaygroundForm.tsx
+++ b/src/components/Option/Playground/PlaygroundForm.tsx
@@ -14,7 +14,6 @@ import { useTranslation } from "react-i18next"
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobe } from "react-icons/pi"
-import { extractReadabilityContent } from "@/parser/reader"
type Props = {
dropedFile: File | undefined
@@ -39,15 +38,26 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
selectedKnowledge
} = useMessageOption()
+ const isMobile = () => {
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent
+ )
+ }
+
const textAreaFocus = () => {
if (textareaRef.current) {
if (
textareaRef.current.selectionStart === textareaRef.current.selectionEnd
) {
- textareaRef.current.focus()
+ if (!isMobile()) {
+ textareaRef.current.focus()
+ } else {
+ textareaRef.current.blur()
+ }
}
}
}
+
const form = useForm({
initialValues: {
message: "",
diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx
index 141bdf6..56e4362 100644
--- a/src/components/Option/Prompt/index.tsx
+++ b/src/components/Option/Prompt/index.tsx
@@ -8,16 +8,18 @@ import {
Input,
Form,
Switch,
+ Segmented,
+ Tag
} from "antd"
import { Trash2, Pen, Computer, Zap } from "lucide-react"
import { useState } from "react"
import { useTranslation } from "react-i18next"
+import { deletePromptById, getAllPrompts, savePrompt, updatePrompt } from "@/db"
import {
- deletePromptById,
- getAllPrompts,
- savePrompt,
- updatePrompt
-} from "@/db"
+ getAllCopilotPrompts,
+ setAllCopilotPrompts
+} from "@/services/application"
+import { tagColors } from "@/utils/color"
export const PromptBody = () => {
const queryClient = useQueryClient()
@@ -26,13 +28,25 @@ export const PromptBody = () => {
const [editId, setEditId] = useState("")
const [createForm] = Form.useForm()
const [editForm] = Form.useForm()
- const { t } = useTranslation("settings")
+ const { t } = useTranslation(["settings", "common"])
+ const [selectedSegment, setSelectedSegment] = useState<"custom" | "copilot">(
+ "custom"
+ )
+
+ const [openCopilotEdit, setOpenCopilotEdit] = useState(false)
+ const [editCopilotId, setEditCopilotId] = useState("")
+ const [editCopilotForm] = Form.useForm()
const { data, status } = useQuery({
queryKey: ["fetchAllPrompts"],
queryFn: getAllPrompts
})
+ const { data: copilotData, status: copilotStatus } = useQuery({
+ queryKey: ["fetchCopilotPrompts"],
+ queryFn: getAllCopilotPrompts
+ })
+
const { mutate: deletePrompt } = useMutation({
mutationFn: deletePromptById,
onSuccess: () => {
@@ -103,8 +117,38 @@ export const PromptBody = () => {
}
})
- return (
-
+ const { mutate: updateCopilotPrompt, isPending: isUpdatingCopilotPrompt } =
+ useMutation({
+ mutationFn: async (data: any) => {
+ return await setAllCopilotPrompts([
+ {
+ key: data.key,
+ prompt: data.prompt
+ }
+ ])
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ["fetchCopilotPrompts"]
+ })
+ setOpenCopilotEdit(false)
+ editCopilotForm.resetFields()
+ notification.success({
+ message: t("managePrompts.notification.updatedSuccess"),
+ description: t("managePrompts.notification.updatedSuccessDesc")
+ })
+ },
+ onError: (error) => {
+ notification.error({
+ message: t("managePrompts.notification.error"),
+ description:
+ error?.message || t("managePrompts.notification.someError")
+ })
+ }
+ })
+
+ function customPrompts() {
+ return (
@@ -127,30 +171,38 @@ export const PromptBody = () => {
title: t("managePrompts.columns.title"),
dataIndex: "title",
key: "title",
- render: (content) => ({content})
+ render: (content) => (
+ {content}
+ )
},
{
title: t("managePrompts.columns.prompt"),
dataIndex: "content",
key: "content",
- render: (content) => ({content})
+ render: (content) => (
+ {content}
+ )
},
{
title: t("managePrompts.columns.type"),
dataIndex: "is_system",
key: "is_system",
- render: (is_system) =>
+ render: (is_system) => (
{is_system ? (
<>
- {t("managePrompts.systemPrompt")}
+ {" "}
+ {t("managePrompts.systemPrompt")}
>
) : (
<>
- {t("managePrompts.quickPrompt")}
+ {" "}
+ {t("managePrompts.quickPrompt")}
>
)}
- },
+
+ )
+ },
{
title: t("managePrompts.columns.actions"),
render: (_, record) => (
@@ -189,6 +241,90 @@ export const PromptBody = () => {
/>
)}
+ )
+ }
+
+ function copilotPrompts() {
+ return (
+
+ {copilotStatus === "pending" &&
}
+
+ {copilotStatus === "success" && (
+
(
+
+
+ {t(`common:copilot.${content}`)}
+
+
+ )
+ },
+ {
+ title: t("managePrompts.columns.prompt"),
+ dataIndex: "prompt",
+ key: "prompt",
+ render: (content) => (
+ {content}
+ )
+ },
+ {
+ render: (_, record) => (
+
+
+
+
+
+ )
+ }
+ ]}
+ bordered
+ dataSource={copilotData}
+ rowKey={(record) => record.key}
+ />
+ )}
+
+ )
+ }
+
+ return (
+
+
+ {
+ setSelectedSegment(value as "custom" | "copilot")
+ }}
+ />
+
+ {selectedSegment === "custom" && customPrompts()}
+ {selectedSegment === "copilot" && copilotPrompts()}
{
+
+
setOpenCopilotEdit(false)}
+ footer={null}>
+ {
+ if (value && value.includes("{text}")) {
+ return Promise.resolve()
+ }
+ return Promise.reject(
+ new Error(
+ t("managePrompts.form.prompt.missingTextPlaceholder")
+ )
+ )
+ }
+ }
+ ]}>
+
+
+
+
+
+
+
+
)
}
diff --git a/src/components/Option/Settings/general-settings.tsx b/src/components/Option/Settings/general-settings.tsx
index 21f622a..c37b551 100644
--- a/src/components/Option/Settings/general-settings.tsx
+++ b/src/components/Option/Settings/general-settings.tsx
@@ -29,6 +29,8 @@ export const GeneralSettings = () => {
false
)
+ const [generateTitle, setGenerateTitle] = useStorage("titleGenEnabled", false)
+
const [hideCurrentChatModelSettings, setHideCurrentChatModelSettings] =
useStorage("hideCurrentChatModelSettings", false)
@@ -141,6 +143,19 @@ export const GeneralSettings = () => {
/>
+
+
+
+ {t("generalSettings.settings.generateTitle.label")}
+
+
+
+
setGenerateTitle(checked)}
+ />
+
+
{t("generalSettings.settings.darkMode.label")}
diff --git a/src/components/Sidepanel/Chat/body.tsx b/src/components/Sidepanel/Chat/body.tsx
index d9a2d8e..61d9071 100644
--- a/src/components/Sidepanel/Chat/body.tsx
+++ b/src/components/Sidepanel/Chat/body.tsx
@@ -35,6 +35,7 @@ export const SidePanelBody = () => {
currentMessageIndex={index}
totalMessages={messages.length}
onRengerate={regenerateLastMessage}
+ message_type={message.messageType}
isProcessing={streaming}
isSearchingInternet={isSearchingInternet}
sources={message.sources}
diff --git a/src/db/index.ts b/src/db/index.ts
index ea60233..6e27b3f 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -31,6 +31,7 @@ type Message = {
sources?: string[]
search?: WebSearch
createdAt: number
+ messageType?: string
}
type Webshare = {
@@ -241,7 +242,8 @@ export const saveMessage = async (
content: string,
images: string[],
source?: any[],
- time?: number
+ time?: number,
+ message_type?: string
) => {
const id = generateID()
let createdAt = Date.now()
@@ -256,7 +258,8 @@ export const saveMessage = async (
content,
images,
createdAt,
- sources: source
+ sources: source,
+ messageType: message_type
}
const db = new PageAssitDatabase()
await db.addMessage(message)
diff --git a/src/entries/background.ts b/src/entries/background.ts
index b238b9a..1f0de4b 100644
--- a/src/entries/background.ts
+++ b/src/entries/background.ts
@@ -1,84 +1,10 @@
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
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 = () => {
- setBadgeText({ text: "" })
- setTitle({ title: "" })
-}
-const streamDownload = async (url: string, model: string) => {
- url += "/api/pull"
- const response = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ model, stream: true })
- })
-
- const reader = response.body?.getReader()
-
- const decoder = new TextDecoder()
-
- let isSuccess = true
- while (true) {
- if (!reader) {
- break
- }
- const { done, value } = await reader.read()
-
- if (done) {
- break
- }
-
- const text = decoder.decode(value)
- try {
- const json = JSON.parse(text.trim()) as {
- status: string
- total?: number
- completed?: number
- }
- if (json.total && json.completed) {
- setBadgeText({
- text: progressHuman(json.completed, json.total)
- })
- setBadgeBackgroundColor({ color: "#0000FF" })
- } else {
- setBadgeText({ text: "🏋️♂️" })
- setBadgeBackgroundColor({ color: "#FFFFFF" })
- }
-
- setTitle({ title: json.status })
-
- if (json.status === "success") {
- isSuccess = true
- }
- } catch (e) {
- console.error(e)
- }
- }
-
- if (isSuccess) {
- setBadgeText({ text: "✅" })
- setBadgeBackgroundColor({ color: "#00FF00" })
- setTitle({ title: "Model pulled successfully" })
- } else {
- setBadgeText({ text: "❌" })
- setBadgeBackgroundColor({ color: "#FF0000" })
- setTitle({ title: "Model pull failed" })
- }
-
- setTimeout(() => {
- clearBadge()
- }, 5000)
-}
+import { clearBadge, streamDownload } from "@/utils/pull-ollama"
export default defineBackground({
main() {
+ let isCopilotRunning: boolean = false
browser.runtime.onMessage.addListener(async (message) => {
if (message.type === "sidepanel") {
await browser.sidebarAction.open()
@@ -100,6 +26,15 @@ export default defineBackground({
}
})
+ browser.runtime.onConnect.addListener((port) => {
+ if (port.name === "pgCopilot") {
+ isCopilotRunning = true
+ port.onDisconnect.addListener(() => {
+ isCopilotRunning = false
+ })
+ }
+ })
+
if (import.meta.env.BROWSER === "chrome") {
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({ url: chrome.runtime.getURL("/options.html") })
@@ -124,10 +59,41 @@ export default defineBackground({
browser.contextMenus.create({
id: contextMenuId["sidePanel"],
title: contextMenuTitle["sidePanel"],
- contexts: ["all"]
+ contexts: ["page", "selection"]
})
+
+ browser.contextMenus.create({
+ id: "summarize-pa",
+ title: "Summarize",
+ contexts: ["selection"]
+ })
+
+ browser.contextMenus.create({
+ id: "explain-pa",
+ title: "Explain",
+ contexts: ["selection"]
+ })
+
+ browser.contextMenus.create({
+ id: "rephrase-pa",
+ title: "Rephrase",
+ contexts: ["selection"]
+ })
+
+ browser.contextMenus.create({
+ id: "translate-pg",
+ title: "Translate",
+ contexts: ["selection"]
+ })
+
+ browser.contextMenus.create({
+ id: "custom-pg",
+ title: "Custom",
+ contexts: ["selection"]
+ })
+
if (import.meta.env.BROWSER === "chrome") {
- browser.contextMenus.onClicked.addListener((info, tab) => {
+ browser.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === "open-side-panel-pa") {
chrome.sidePanel.open({
tabId: tab.id!
@@ -136,6 +102,68 @@ export default defineBackground({
browser.tabs.create({
url: browser.runtime.getURL("/options.html")
})
+ } else if (info.menuItemId === "summarize-pa") {
+ chrome.sidePanel.open({
+ tabId: tab.id!
+ })
+ // this is a bad method hope somone can fix it :)
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ from: "background",
+ type: "summary",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+
+ } else if (info.menuItemId === "rephrase-pa") {
+ chrome.sidePanel.open({
+ tabId: tab.id!
+ })
+ setTimeout(async () => {
+
+ await browser.runtime.sendMessage({
+ type: "rephrase",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+
+ } else if (info.menuItemId === "translate-pg") {
+ chrome.sidePanel.open({
+ tabId: tab.id!
+ })
+
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "translate",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "explain-pa") {
+ chrome.sidePanel.open({
+ tabId: tab.id!
+ })
+
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "explain",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "custom-pg") {
+ chrome.sidePanel.open({
+ tabId: tab.id!
+ })
+
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "custom",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
}
})
@@ -166,6 +194,61 @@ export default defineBackground({
browser.tabs.create({
url: browser.runtime.getURL("/options.html")
})
+ } else if (info.menuItemId === "summarize-pa") {
+ if (!isCopilotRunning) {
+ browser.sidebarAction.toggle()
+ }
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ from: "background",
+ type: "summary",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "rephrase-pa") {
+ if (!isCopilotRunning) {
+ browser.sidebarAction.toggle()
+ }
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "rephrase",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "translate-pg") {
+ if (!isCopilotRunning) {
+ browser.sidebarAction.toggle()
+ }
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "translate",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "explain-pa") {
+ if (!isCopilotRunning) {
+ browser.sidebarAction.toggle()
+ }
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "explain",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
+ } else if (info.menuItemId === "custom-pg") {
+ if (!isCopilotRunning) {
+ browser.sidebarAction.toggle()
+ }
+ setTimeout(async () => {
+ await browser.runtime.sendMessage({
+ type: "custom",
+ from: "background",
+ text: info.selectionText
+ })
+ }, isCopilotRunning ? 0 : 5000)
}
})
diff --git a/src/entries/options/App.tsx b/src/entries/options/App.tsx
index 620b183..997e1de 100644
--- a/src/entries/options/App.tsx
+++ b/src/entries/options/App.tsx
@@ -1,5 +1,6 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { MemoryRouter } from "react-router-dom"
+import { useEffect, useState } from "react"
const queryClient = new QueryClient()
import { ConfigProvider, Empty, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs"
@@ -12,6 +13,16 @@ import { PageAssistProvider } from "@/components/Common/PageAssistProvider"
function IndexOption() {
const { mode } = useDarkMode()
const { t, i18n } = useTranslation()
+ const [direction, setDirection] = useState<"ltr" | "rtl">("ltr")
+
+ useEffect(() => {
+ if (i18n.resolvedLanguage) {
+ document.documentElement.lang = i18n.resolvedLanguage
+ document.documentElement.dir = i18n.dir(i18n.resolvedLanguage)
+ setDirection(i18n.dir(i18n.resolvedLanguage))
+ }
+ }, [i18n, i18n.resolvedLanguage])
+
return (
- )}>
+ )}
+ direction={direction}>
diff --git a/src/entries/sidepanel/App.tsx b/src/entries/sidepanel/App.tsx
index 0ee2e9d..3abbf33 100644
--- a/src/entries/sidepanel/App.tsx
+++ b/src/entries/sidepanel/App.tsx
@@ -1,5 +1,6 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { MemoryRouter } from "react-router-dom"
+import { useEffect } from "react"
import { SidepanelRouting } from "~/routes"
const queryClient = new QueryClient()
import { ConfigProvider, Empty, theme } from "antd"
@@ -13,6 +14,13 @@ function IndexSidepanel() {
const { mode } = useDarkMode()
const { t, i18n } = useTranslation()
+ useEffect(() => {
+ if (i18n.resolvedLanguage) {
+ document.documentElement.lang = i18n.resolvedLanguage;
+ document.documentElement.dir = i18n.dir(i18n.resolvedLanguage);
+ }
+ }, [i18n, i18n.resolvedLanguage]);
+
return (
void
@@ -26,6 +28,7 @@ export const saveMessageOnError = async ({
setHistoryId: (historyId: string) => void
isRegenerating: boolean
message_source?: "copilot" | "web-ui"
+ message_type?: string
}) => {
if (
e?.name === "AbortError" ||
@@ -55,7 +58,8 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
- 1
+ 1,
+ message_type
)
}
await saveMessage(
@@ -65,11 +69,13 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
- 2
+ 2,
+ message_type
)
await setLastUsedChatModel(historyId, selectedModel)
} else {
- const newHistoryId = await saveHistory(userMessage, false, message_source)
+ const title = await generateTitle(selectedModel, userMessage, userMessage)
+ const newHistoryId = await saveHistory(title, false, message_source)
if (!isRegenerating) {
await saveMessage(
newHistoryId.id,
@@ -78,7 +84,8 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
- 1
+ 1,
+ message_type
)
}
await saveMessage(
@@ -88,7 +95,8 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
- 2
+ 2,
+ message_type
)
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel)
@@ -109,7 +117,8 @@ export const saveMessageOnSuccess = async ({
image,
fullText,
source,
- message_source = "web-ui"
+ message_source = "web-ui",
+ message_type
}: {
historyId: string | null
setHistoryId: (historyId: string) => void
@@ -119,7 +128,8 @@ export const saveMessageOnSuccess = async ({
image: string
fullText: string
source: any[]
- message_source?: "copilot" | "web-ui"
+ message_source?: "copilot" | "web-ui",
+ message_type?: string
}) => {
if (historyId) {
if (!isRegenerate) {
@@ -130,7 +140,8 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
- 1
+ 1,
+ message_type
)
}
await saveMessage(
@@ -140,11 +151,13 @@ export const saveMessageOnSuccess = async ({
fullText,
[],
source,
- 2
+ 2,
+ message_type
)
await setLastUsedChatModel(historyId, selectedModel!)
} else {
- const newHistoryId = await saveHistory(message, false, message_source)
+ const title = await generateTitle(selectedModel, message, message)
+ const newHistoryId = await saveHistory(title, false, message_source)
await saveMessage(
newHistoryId.id,
selectedModel,
@@ -152,7 +165,8 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
- 1
+ 1,
+ message_type
)
await saveMessage(
newHistoryId.id,
@@ -161,7 +175,8 @@ export const saveMessageOnSuccess = async ({
fullText,
[],
source,
- 2
+ 2,
+ message_type
)
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel!)
diff --git a/src/hooks/useBackgroundMessage.tsx b/src/hooks/useBackgroundMessage.tsx
new file mode 100644
index 0000000..a7e282f
--- /dev/null
+++ b/src/hooks/useBackgroundMessage.tsx
@@ -0,0 +1,29 @@
+import { useState, useEffect } from "react"
+
+interface Message {
+ from: string
+ type: string
+ text: string
+}
+
+function useBackgroundMessage() {
+ const [message, setMessage] = useState(null)
+
+ useEffect(() => {
+ const messageListener = (request: Message) => {
+ if (request.from === "background") {
+ setMessage(request)
+ }
+ }
+ browser.runtime.connect({ name: 'pgCopilot' })
+ browser.runtime.onMessage.addListener(messageListener)
+
+ return () => {
+ browser.runtime.onMessage.removeListener(messageListener)
+ }
+ }, [])
+
+ return message
+}
+
+export default useBackgroundMessage
\ No newline at end of file
diff --git a/src/hooks/useMessage.tsx b/src/hooks/useMessage.tsx
index 896a2d2..3d61528 100644
--- a/src/hooks/useMessage.tsx
+++ b/src/hooks/useMessage.tsx
@@ -31,6 +31,7 @@ import { useStoreChatModelSettings } from "@/store/model"
import { getAllDefaultModelSettings } from "@/services/model-settings"
import { getSystemPromptForWeb } from "@/web/web"
import { pageAssistModel } from "@/models"
+import { getPrompt } from "@/services/application"
export const useMessage = () => {
const {
@@ -51,8 +52,10 @@ export const useMessage = () => {
isSearchingInternet
} = useStoreMessageOption()
-
- const [chatWithWebsiteEmbedding] = useStorage("chatWithWebsiteEmbedding", true)
+ const [chatWithWebsiteEmbedding] = useStorage(
+ "chatWithWebsiteEmbedding",
+ true
+ )
const [maxWebsiteContext] = useStorage("maxWebsiteContext", 4028)
const {
@@ -857,13 +860,206 @@ export const useMessage = () => {
}
}
+ const presetChatMode = async (
+ message: string,
+ image: string,
+ isRegenerate: boolean,
+ messages: Message[],
+ history: ChatHistory,
+ signal: AbortSignal,
+ messageType: string
+ ) => {
+ setStreaming(true)
+ const url = await getOllamaURL()
+ const userDefaultModelSettings = await getAllDefaultModelSettings()
+
+ if (image.length > 0) {
+ image = `data:image/jpeg;base64,${image.split(",")[1]}`
+ }
+
+ const ollama = await pageAssistModel({
+ model: selectedModel!,
+ baseUrl: cleanUrl(url),
+ keepAlive:
+ currentChatModelSettings?.keepAlive ??
+ userDefaultModelSettings?.keepAlive,
+ temperature:
+ currentChatModelSettings?.temperature ??
+ userDefaultModelSettings?.temperature,
+ topK: currentChatModelSettings?.topK ?? userDefaultModelSettings?.topK,
+ topP: currentChatModelSettings?.topP ?? userDefaultModelSettings?.topP,
+ numCtx:
+ currentChatModelSettings?.numCtx ?? userDefaultModelSettings?.numCtx,
+ seed: currentChatModelSettings?.seed
+ })
+
+ let newMessage: Message[] = []
+ let generateMessageId = generateID()
+
+ if (!isRegenerate) {
+ newMessage = [
+ ...messages,
+ {
+ isBot: false,
+ name: "You",
+ message,
+ sources: [],
+ images: [image],
+ messageType: messageType
+ },
+ {
+ isBot: true,
+ name: selectedModel,
+ message: "▋",
+ sources: [],
+ id: generateMessageId
+ }
+ ]
+ } else {
+ newMessage = [
+ ...messages,
+ {
+ isBot: true,
+ name: selectedModel,
+ message: "▋",
+ sources: [],
+ id: generateMessageId
+ }
+ ]
+ }
+ setMessages(newMessage)
+ let fullText = ""
+ let contentToSave = ""
+
+ try {
+
+
+ const prompt = await getPrompt(messageType)
+ let humanMessage = new HumanMessage({
+ content: [
+ {
+ text: prompt.replace("{text}", message),
+ type: "text"
+ }
+ ]
+ })
+ if (image.length > 0) {
+ humanMessage = new HumanMessage({
+ content: [
+ {
+ text: prompt.replace("{text}", message),
+ type: "text"
+ },
+ {
+ image_url: image,
+ type: "image_url"
+ }
+ ]
+ })
+ }
+
+ const chunks = await ollama.stream([humanMessage], {
+ signal: signal
+ })
+ let count = 0
+ for await (const chunk of chunks) {
+ contentToSave += chunk.content
+ fullText += chunk.content
+ if (count === 0) {
+ setIsProcessing(true)
+ }
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText + "▋"
+ }
+ }
+ return message
+ })
+ })
+ count++
+ }
+
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText
+ }
+ }
+ return message
+ })
+ })
+
+ setHistory([
+ ...history,
+ {
+ role: "user",
+ content: message,
+ image,
+ messageType
+ },
+ {
+ role: "assistant",
+ content: fullText
+ }
+ ])
+
+ await saveMessageOnSuccess({
+ historyId,
+ setHistoryId,
+ isRegenerate,
+ selectedModel: selectedModel,
+ message,
+ image,
+ fullText,
+ source: [],
+ message_source: "copilot",
+ message_type: messageType
+ })
+
+ setIsProcessing(false)
+ setStreaming(false)
+ } catch (e) {
+ const errorSave = await saveMessageOnError({
+ e,
+ botMessage: fullText,
+ history,
+ historyId,
+ image,
+ selectedModel,
+ setHistory,
+ setHistoryId,
+ userMessage: message,
+ isRegenerating: isRegenerate,
+ message_source: "copilot",
+ message_type: messageType
+ })
+
+ if (!errorSave) {
+ notification.error({
+ message: t("error"),
+ description: e?.message || t("somethingWentWrong")
+ })
+ }
+ setIsProcessing(false)
+ setStreaming(false)
+ } finally {
+ setAbortController(null)
+ }
+ }
+
const onSubmit = async ({
message,
image,
isRegenerate,
controller,
memory,
- messages: chatHistory
+ messages: chatHistory,
+ messageType
}: {
message: string
image: string
@@ -871,6 +1067,7 @@ export const useMessage = () => {
messages?: Message[]
memory?: ChatHistory
controller?: AbortController
+ messageType?: string
}) => {
let signal: AbortSignal
if (!controller) {
@@ -882,39 +1079,52 @@ export const useMessage = () => {
signal = controller.signal
}
- if (chatMode === "normal") {
- if (webSearch) {
- await searchChatMode(
- message,
- image,
- isRegenerate || false,
- messages,
- memory || history,
- signal
- )
- } else {
- await normalChatMode(
- message,
- image,
- isRegenerate,
- chatHistory || messages,
- memory || history,
- signal
- )
- }
- } else {
- const newEmbeddingController = new AbortController()
- let embeddingSignal = newEmbeddingController.signal
- setEmbeddingController(newEmbeddingController)
- await chatWithWebsiteMode(
+ // this means that the user is trying to send something from a selected text on the web
+ if (messageType) {
+ await presetChatMode(
message,
image,
isRegenerate,
chatHistory || messages,
memory || history,
signal,
- embeddingSignal
+ messageType
)
+ } else {
+ if (chatMode === "normal") {
+ if (webSearch) {
+ await searchChatMode(
+ message,
+ image,
+ isRegenerate || false,
+ messages,
+ memory || history,
+ signal
+ )
+ } else {
+ await normalChatMode(
+ message,
+ image,
+ isRegenerate,
+ chatHistory || messages,
+ memory || history,
+ signal
+ )
+ }
+ } else {
+ const newEmbeddingController = new AbortController()
+ let embeddingSignal = newEmbeddingController.signal
+ setEmbeddingController(newEmbeddingController)
+ await chatWithWebsiteMode(
+ message,
+ image,
+ isRegenerate,
+ chatHistory || messages,
+ memory || history,
+ signal,
+ embeddingSignal
+ )
+ }
}
}
@@ -982,7 +1192,8 @@ export const useMessage = () => {
image: lastMessage.image || "",
isRegenerate: true,
memory: newHistory,
- controller: newController
+ controller: newController,
+ messageType: lastMessage.messageType
})
}
}
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 6c5915f..405bc04 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -9,6 +9,7 @@ import { zh } from "./lang/zh";
import { ja } from "./lang/ja";
import { it } from "./lang/it";
import { es } from "./lang/es";
+import { fa } from "./lang/fa";
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
@@ -27,7 +28,9 @@ i18n
"ru-RU": ru,
zh: zh,
ja: ja,
- "ja-JP": ja
+ "ja-JP": ja,
+ fa: fa,
+ "fa-IR": fa
},
fallbackLng: "en",
lng: localStorage.getItem("i18nextLng") || "en",
diff --git a/src/i18n/lang/fa.ts b/src/i18n/lang/fa.ts
new file mode 100644
index 0000000..c9fc1ff
--- /dev/null
+++ b/src/i18n/lang/fa.ts
@@ -0,0 +1,17 @@
+import option from "@/assets/locale/fa/option.json"
+import playground from "@/assets/locale/fa/playground.json"
+import common from "@/assets/locale/fa/common.json"
+import sidepanel from "@/assets/locale/fa/sidepanel.json"
+import settings from "@/assets/locale/fa/settings.json"
+import knowledge from "@/assets/locale/fa/knowledge.json"
+import chrome from "@/assets/locale/fa/chrome.json"
+
+export const fa = {
+ option,
+ playground,
+ common,
+ sidepanel,
+ settings,
+ knowledge,
+ chrome
+}
diff --git a/src/i18n/support-language.ts b/src/i18n/support-language.ts
index 7cd56a0..56327dc 100644
--- a/src/i18n/support-language.ts
+++ b/src/i18n/support-language.ts
@@ -35,5 +35,9 @@ export const supportLanguage = [
{
label: "日本語",
value: "ja-JP"
+ },
+ {
+ label: "فارسی",
+ value: "fa"
}
]
diff --git a/src/models/index.ts b/src/models/index.ts
index d627a5d..4048cb3 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -13,12 +13,12 @@ export const pageAssistModel = async ({
}: {
model: string
baseUrl: string
- keepAlive: string
- temperature: number
- topK: number
- topP: number
- numCtx: number
- seed: number
+ keepAlive?: string
+ temperature?: number
+ topK?: number
+ topP?: number
+ numCtx?: number
+ seed?: number
}) => {
switch (model) {
case "chrome::gemini-nano::page-assist":
diff --git a/src/public/_locales/fa/messages.json b/src/public/_locales/fa/messages.json
new file mode 100644
index 0000000..2d047ed
--- /dev/null
+++ b/src/public/_locales/fa/messages.json
@@ -0,0 +1,14 @@
+{
+ "extName": {
+ "message": "دستیار صفحه - یک رابط کاربری وب برای مدل های هوش مصنوعی لوکال"
+ },
+ "extDescription": {
+ "message": "از مدل های هوش مصنوعی لوکال خود برای دریافت کمک در هنگام مرور وب استفاده کنید."
+ },
+ "openSidePanelToChat": {
+ "message": "باز کردن کوپایلت برای گفتگو"
+ },
+ "openOptionToChat": {
+ "message": "باز کردن رابط کاربری وب برای گفتگو"
+ }
+}
diff --git a/src/routes/sidepanel-chat.tsx b/src/routes/sidepanel-chat.tsx
index 112cdc5..60205a9 100644
--- a/src/routes/sidepanel-chat.tsx
+++ b/src/routes/sidepanel-chat.tsx
@@ -3,8 +3,11 @@ import {
formatToMessage,
getRecentChatFromCopilot
} from "@/db"
+import useBackgroundMessage from "@/hooks/useBackgroundMessage"
import { copilotResumeLastChat } from "@/services/app"
+import { notification } from "antd"
import React from "react"
+import { useTranslation } from "react-i18next"
import { SidePanelBody } from "~/components/Sidepanel/Chat/body"
import { SidepanelForm } from "~/components/Sidepanel/Chat/form"
import { SidepanelHeader } from "~/components/Sidepanel/Chat/header"
@@ -13,17 +16,27 @@ import { useMessage } from "~/hooks/useMessage"
const SidepanelChat = () => {
const drop = React.useRef(null)
const [dropedFile, setDropedFile] = React.useState()
+ const { t } = useTranslation(["playground"])
const [dropState, setDropState] = React.useState<
"idle" | "dragging" | "error"
>("idle")
- const { chatMode, messages, setHistory, setHistoryId, setMessages } =
- useMessage()
+ const {
+ chatMode,
+ streaming,
+ onSubmit,
+ messages,
+ setHistory,
+ setHistoryId,
+ setMessages,
+ selectedModel
+ } = useMessage()
+
+ const bgMsg = useBackgroundMessage()
const setRecentMessagesOnLoad = async () => {
-
- const isEnabled = await copilotResumeLastChat();
+ const isEnabled = await copilotResumeLastChat()
if (!isEnabled) {
- return;
+ return
}
if (messages.length === 0) {
const recentChat = await getRecentChatFromCopilot()
@@ -92,11 +105,26 @@ const SidepanelChat = () => {
}
}, [])
-
React.useEffect(() => {
setRecentMessagesOnLoad()
}, [])
+ React.useEffect(() => {
+ if (bgMsg && !streaming) {
+ if (selectedModel) {
+ onSubmit({
+ message: bgMsg.text,
+ messageType: bgMsg.type,
+ image: ""
+ })
+ } else {
+ notification.error({
+ message: t("formError.noModel")
+ })
+ }
+ }
+ }, [bgMsg])
+
return (
{
+ return (await storage.get("copilotSummaryPrompt")) || DEFAULT_SUMMARY_PROMPT
+}
+
+export const setSummaryPrompt = async (prompt: string) => {
+ await storage.set("copilotSummaryPrompt", prompt)
+}
+
+export const getRephrasePrompt = async () => {
+ return (await storage.get("copilotRephrasePrompt")) || DEFAULT_REPHRASE_PROMPT
+}
+
+export const setRephrasePrompt = async (prompt: string) => {
+ await storage.set("copilotRephrasePrompt", prompt)
+}
+
+export const getTranslatePrompt = async () => {
+ return (
+ (await storage.get("copilotTranslatePrompt")) || DEFAULT_TRANSLATE_PROMPT
+ )
+}
+
+export const setTranslatePrompt = async (prompt: string) => {
+ await storage.set("copilotTranslatePrompt", prompt)
+}
+
+export const getExplainPrompt = async () => {
+ return (await storage.get("copilotExplainPrompt")) || DEFAULT_EXPLAIN_PROMPT
+}
+
+export const setExplainPrompt = async (prompt: string) => {
+ await storage.set("copilotExplainPrompt", prompt)
+}
+
+export const getCustomPrompt = async () => {
+ return (await storage.get("copilotCustomPrompt")) || DEFAULT_CUSTOM_PROMPT
+}
+
+export const setCustomPrompt = async (prompt: string) => {
+ await storage.set("copilotCustomPrompt", prompt)
+}
+
+export const getAllCopilotPrompts = async () => {
+ const [
+ summaryPrompt,
+ rephrasePrompt,
+ translatePrompt,
+ explainPrompt,
+ customPrompt
+ ] = await Promise.all([
+ getSummaryPrompt(),
+ getRephrasePrompt(),
+ getTranslatePrompt(),
+ getExplainPrompt(),
+ getCustomPrompt()
+ ])
+
+ return [
+ { key: "summary", prompt: summaryPrompt },
+ { key: "rephrase", prompt: rephrasePrompt },
+ { key: "translate", prompt: translatePrompt },
+ { key: "explain", prompt: explainPrompt },
+ { key: "custom", prompt: customPrompt }
+ ]
+}
+
+export const setAllCopilotPrompts = async (
+ prompts: { key: string; prompt: string }[]
+) => {
+ for (const { key, prompt } of prompts) {
+ switch (key) {
+ case "summary":
+ await setSummaryPrompt(prompt)
+ break
+ case "rephrase":
+ await setRephrasePrompt(prompt)
+ break
+ case "translate":
+ await setTranslatePrompt(prompt)
+ break
+ case "explain":
+ await setExplainPrompt(prompt)
+ break
+ case "custom":
+ await setCustomPrompt(prompt)
+ break
+
+ }
+ }
+}
+
+export const getPrompt = async (key: string) => {
+ switch (key) {
+ case "summary":
+ return await getSummaryPrompt()
+ case "rephrase":
+ return await getRephrasePrompt()
+ case "translate":
+ return await getTranslatePrompt()
+ case "explain":
+ return await getExplainPrompt()
+ case "custom":
+ return await getCustomPrompt()
+ default:
+ return ""
+ }
+}
diff --git a/src/services/title.ts b/src/services/title.ts
new file mode 100644
index 0000000..ac4b8e8
--- /dev/null
+++ b/src/services/title.ts
@@ -0,0 +1,72 @@
+import { pageAssistModel } from "@/models"
+import { Storage } from "@plasmohq/storage"
+import { getOllamaURL } from "./ollama"
+import { cleanUrl } from "@/libs/clean-url"
+import { HumanMessage } from "langchain/schema"
+const storage = new Storage()
+
+// this prompt is copied from the OpenWebUI codebase
+export const DEFAULT_TITLE_GEN_PROMPT = `Here is the query:
+
+--------------
+
+{{query}}
+
+--------------
+
+Create a concise, 3-5 word phrase as a title for the previous query. Avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT. ANSWER USING THE SAME LANGUAGE AS THE QUERY.
+
+
+Examples of titles:
+
+Stellar Achievement Celebration
+Family Bonding Activities
+🇫🇷 Voyage à Paris
+🍜 Receta de Ramen Casero
+Shakespeare Analyse Literarische
+日本の春祭り体験
+Древнегреческая Философия Обзор
+
+Response:`
+
+
+export const isTitleGenEnabled = async () => {
+ const enabled = await storage.get("titleGenEnabled")
+ return enabled ?? false
+}
+
+export const setTitleGenEnabled = async (enabled: boolean) => {
+ await storage.set("titleGenEnabled", enabled)
+}
+
+
+export const generateTitle = async (model: string, query: string, fallBackTitle: string) => {
+
+ const isEnabled = await isTitleGenEnabled()
+
+ if (!isEnabled) {
+ return fallBackTitle
+ }
+
+ try {
+ const url = await getOllamaURL()
+
+ const titleModel = await pageAssistModel({
+ baseUrl: cleanUrl(url),
+ model
+ })
+
+ const prompt = DEFAULT_TITLE_GEN_PROMPT.replace("{{query}}", query)
+
+ const title = await titleModel.invoke([
+ new HumanMessage({
+ content: prompt
+ })
+ ])
+
+ return title.content.toString()
+ } catch (error) {
+ console.log(`Error generating title: ${error}`)
+ return fallBackTitle
+ }
+}
\ No newline at end of file
diff --git a/src/store/index.tsx b/src/store/index.tsx
index 8910705..c1929d3 100644
--- a/src/store/index.tsx
+++ b/src/store/index.tsx
@@ -12,6 +12,7 @@ export type ChatHistory = {
role: "user" | "assistant" | "system"
content: string
image?: string
+ messageType?: string
}[]
type State = {
diff --git a/src/store/option.tsx b/src/store/option.tsx
index 8e1e845..5184b8f 100644
--- a/src/store/option.tsx
+++ b/src/store/option.tsx
@@ -18,12 +18,14 @@ export type Message = {
images?: string[]
search?: WebSearch
id?: string
+ messageType?: string
}
export type ChatHistory = {
role: "user" | "assistant" | "system"
content: string
- image?: string
+ image?: string,
+ messageType?: string
}[]
type State = {
diff --git a/src/types/message.ts b/src/types/message.ts
index 66e954e..3be7cdc 100644
--- a/src/types/message.ts
+++ b/src/types/message.ts
@@ -14,5 +14,6 @@ type WebSearch = {
sources: any[]
images?: string[]
search?: WebSearch
+ messageType?: string
id?: string
}
\ No newline at end of file
diff --git a/src/utils/color.ts b/src/utils/color.ts
new file mode 100644
index 0000000..b0579a8
--- /dev/null
+++ b/src/utils/color.ts
@@ -0,0 +1,8 @@
+export const tagColors = {
+ summary: "blue",
+ explain: "green",
+ translate: "purple",
+ custom: "orange",
+ rephrase: "yellow"
+ }
+
\ No newline at end of file
diff --git a/src/utils/pull-ollama.ts b/src/utils/pull-ollama.ts
new file mode 100644
index 0000000..d15ff2b
--- /dev/null
+++ b/src/utils/pull-ollama.ts
@@ -0,0 +1,77 @@
+import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action"
+
+
+export const progressHuman = (completed: number, total: number) => {
+ return ((completed / total) * 100).toFixed(0) + "%"
+}
+
+export const clearBadge = () => {
+ setBadgeText({ text: "" })
+ setTitle({ title: "" })
+}
+export const streamDownload = async (url: string, model: string) => {
+ url += "/api/pull"
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({ model, stream: true })
+ })
+
+ const reader = response.body?.getReader()
+
+ const decoder = new TextDecoder()
+
+ let isSuccess = true
+ while (true) {
+ if (!reader) {
+ break
+ }
+ const { done, value } = await reader.read()
+
+ if (done) {
+ break
+ }
+
+ const text = decoder.decode(value)
+ try {
+ const json = JSON.parse(text.trim()) as {
+ status: string
+ total?: number
+ completed?: number
+ }
+ if (json.total && json.completed) {
+ setBadgeText({
+ text: progressHuman(json.completed, json.total)
+ })
+ setBadgeBackgroundColor({ color: "#0000FF" })
+ } else {
+ setBadgeText({ text: "🏋️♂️" })
+ setBadgeBackgroundColor({ color: "#FFFFFF" })
+ }
+
+ setTitle({ title: json.status })
+
+ if (json.status === "success") {
+ isSuccess = true
+ }
+ } catch (e) {
+ console.error(e)
+ }
+ }
+
+ if (isSuccess) {
+ setBadgeText({ text: "✅" })
+ setBadgeBackgroundColor({ color: "#00FF00" })
+ setTitle({ title: "Model pulled successfully" })
+ } else {
+ setBadgeText({ text: "❌" })
+ setBadgeBackgroundColor({ color: "#FF0000" })
+ setTitle({ title: "Model pull failed" })
+ }
+
+ setTimeout(() => {
+ clearBadge()
+ }, 5000)
+}
\ No newline at end of file
diff --git a/wxt.config.ts b/wxt.config.ts
index 74fda20..10b2eff 100644
--- a/wxt.config.ts
+++ b/wxt.config.ts
@@ -50,7 +50,7 @@ export default defineConfig({
outDir: "build",
manifest: {
- version: "1.1.16",
+ version: "1.2.0",
name:
process.env.TARGET === "firefox"
? "Page Assist - A Web UI for Local AI Models"