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}> +
+ updateCopilotPrompt({ + key: editCopilotId, + prompt: values.prompt + }) + } + layout="vertical" + form={editCopilotForm}> + { + 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"