Merge pull request #164 from n4ze3m/next

1.2.0
This commit is contained in:
Muhammed Nazeem 2024-08-08 11:43:04 +05:30 committed by GitHub
commit 066db5dfb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1883 additions and 252 deletions

View File

@ -84,5 +84,12 @@
}
},
"advanced": "More Model Settings"
},
"copilot": {
"summary": "Summarize",
"explain": "Explain",
"rephrase": "Rephrase",
"translate": "Translate",
"custom": "Custom"
}
}

View File

@ -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"

View File

@ -84,5 +84,11 @@
}
},
"advanced": "Más Configuraciones del Modelo"
},
"copilot": {
"summary": "Resumir",
"explain": "Explicar",
"rephrase": "Reformular",
"translate": "Traducir"
}
}

View File

@ -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"

View File

@ -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\" کلیک کنید. این کار مدل را دریافت خواهد کرد. اگر این تنظیمات را نمی بینید، گام های ۱ و ۲ را تکرار و مرورگر را مجددا اجرا کنید."
}

View File

@ -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": "تنظیمات بیشتر مدل"
}
}

View File

@ -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 اضافه کنید"
}

View File

@ -0,0 +1,12 @@
{
"newChat": "گپ جدید",
"selectAPrompt": "یک پرامپت را انتخاب کنید",
"githubRepository": "مخزن GitHub",
"settings": "تنظیمات",
"sidebarTitle": "تاریخچه گپ",
"error": "خطا",
"somethingWentWrong": "مشکلی پیش آمد",
"validationSelectModel": "لطفا یک مدل را برای ادامه انتخاب کنید",
"deleteHistoryConfirmation": "آیا مطمئن هستید که می خواهید این تاریخچه را حذف کنید؟",
"editHistoryTitle": "یک عنوان جدید وارد کنید"
}

View File

@ -0,0 +1,29 @@
{
"ollamaState": {
"searching": "در حال جستجوی Ollama شما 🦙",
"running": "Ollama در حال اجرا است 🦙",
"notRunning": "امکان اتصال به Ollama وجود ندارد 🦙",
"connectionError": "به نظر می رسد که خطای اتصال دارید. لطفا برای عیب یابی به این <anchor>مستندات</anchor> مراجعه کنید."
},
"formError": {
"noModel": "لطفا یک مدل را انتخاب کنید",
"noEmbeddingModel": "لطفا یک مدل جاسازی در صفحه تنظیمات > RAG تنظیم کنید"
},
"form": {
"textarea": {
"placeholder": "یک پیام تایپ کنید..."
},
"webSearch": {
"on": "روشن",
"off": "خاموش"
}
},
"tooltip": {
"searchInternet": "جستجوی اینترنت",
"speechToText": "گفتار به متن",
"uploadImage": "آپلود تصویر",
"stopStreaming": "توقف Streaming",
"knowledge": "دانش"
},
"sendWhenEnter": "با فشار دادن Enter ارسال شود"
}

View File

@ -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 را در اینجا ارائه دهید. <anchor>بیشتر بدانید</anchor>."
}
},
"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 اصلی سفارشی پیکربندی کنید. برای کسب اطلاعات بیشتر در مورد پیکربندی، <anchor>اینجا را کلیک</anchor> کنید."
}
}
},
"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": "تنظیمات هوش مصنوعی کروم"
}
}

View File

@ -0,0 +1,7 @@
{
"tooltip": {
"embed": "ممکن است چند دقیقه طول بکشد تا صفحه جاسازی شود. لطفاً منتظر بمانید...",
"clear": "پاک کردن تاریخچه گپ",
"history": "تاریخچه گپ"
}
}

View File

@ -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"
}
}

View File

@ -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"

View File

@ -84,5 +84,11 @@
}
},
"advanced": "Altre Impostazioni del Modello"
},
"copilot": {
"summary": "Riassumere",
"explain": "Spiegare",
"rephrase": "Riformulare",
"translate": "Tradurre"
}
}

View File

@ -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"

View File

@ -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": "リンクの生成に失敗しました"
}
},
"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": "翻訳"
}
}

View File

@ -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": "システムプロンプト"

View File

@ -83,5 +83,11 @@
}
},
"advanced": "കൂടുതൽ മോഡൽ ക്രമീകരണങ്ങൾ"
},
"copilot": {
"summary": "സംഗ്രഹിക്കുക",
"explain": "വിശദീകരിക്കുക",
"rephrase": "പുനഃരൂപീകരിക്കുക",
"translate": "വിവർത്തനം ചെയ്യുക"
}
}

View File

@ -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": "സിസ്റ്റം പ്രോംപ്റ്റ് ആണോ"

View File

@ -84,5 +84,11 @@
}
},
"advanced": "Mais Configurações do Modelo"
},
"copilot": {
"summary": "Resumir",
"explain": "Explicar",
"rephrase": "Reformular",
"translate": "Traduzir"
}
}

View File

@ -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"

View File

@ -84,5 +84,11 @@
}
},
"advanced": "Больше настроек модели"
},
"copilot": {
"summary": "Обобщить",
"explain": "Объяснить",
"rephrase": "Перефразировать",
"translate": "Перевести"
}
}

View File

@ -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": "Это системная подсказка"

View File

@ -84,5 +84,11 @@
}
},
"advanced": "更多模型设置"
},
"copilot": {
"summary": "总结",
"explain": "解释",
"rephrase": "重述",
"translate": "翻译"
}
}

View File

@ -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": "是否为系统提示词"

View File

@ -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 ? (
<WebSearch />
) : null}
<div>
{props?.message_type && (
<Tag color={tagColors[props?.message_type] || "default"}>
{t(`copilot.${props?.message_type}`)}
</Tag>
)}
</div>
<div className="flex flex-grow flex-col">
{!editMode ? (
props.isBot ? (
<Markdown message={props.message} />
) : (
<p className="prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark">
<p
className={`prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
props.message_type &&
"italic text-gray-500 dark:text-gray-400 text-sm"
}`}>
{props.message}
</p>
)

View File

@ -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: "",

View File

@ -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 (
<div>
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 (
<div>
<div className="mb-6">
<div className="-ml-4 -mt-2 flex flex-wrap items-center justify-end sm:flex-nowrap">
@ -127,30 +171,38 @@ export const PromptBody = () => {
title: t("managePrompts.columns.title"),
dataIndex: "title",
key: "title",
render: (content) => (<span className="line-clamp-1">{content}</span>)
render: (content) => (
<span className="line-clamp-1">{content}</span>
)
},
{
title: t("managePrompts.columns.prompt"),
dataIndex: "content",
key: "content",
render: (content) => (<span className="line-clamp-1">{content}</span>)
render: (content) => (
<span className="line-clamp-1">{content}</span>
)
},
{
title: t("managePrompts.columns.type"),
dataIndex: "is_system",
key: "is_system",
render: (is_system) =>
render: (is_system) => (
<span className="flex items-center gap-2 text-xs w-32">
{is_system ? (
<>
<Computer className="size-4" /> {t("managePrompts.systemPrompt")}
<Computer className="size-4" />{" "}
{t("managePrompts.systemPrompt")}
</>
) : (
<>
<Zap className="size-4" /> {t("managePrompts.quickPrompt")}
<Zap className="size-4" />{" "}
{t("managePrompts.quickPrompt")}
</>
)}
</span> },
</span>
)
},
{
title: t("managePrompts.columns.actions"),
render: (_, record) => (
@ -189,6 +241,90 @@ export const PromptBody = () => {
/>
)}
</div>
)
}
function copilotPrompts() {
return (
<div>
{copilotStatus === "pending" && <Skeleton paragraph={{ rows: 8 }} />}
{copilotStatus === "success" && (
<Table
columns={[
{
title: t("managePrompts.columns.title"),
dataIndex: "key",
key: "key",
render: (content) => (
<span className="line-clamp-1">
<Tag color={tagColors[content || "default"]}>
{t(`common:copilot.${content}`)}
</Tag>
</span>
)
},
{
title: t("managePrompts.columns.prompt"),
dataIndex: "prompt",
key: "prompt",
render: (content) => (
<span className="line-clamp-1">{content}</span>
)
},
{
render: (_, record) => (
<div className="flex gap-4">
<Tooltip title={t("managePrompts.tooltip.edit")}>
<button
onClick={() => {
setEditCopilotId(record.key)
editCopilotForm.setFieldsValue(record)
setOpenCopilotEdit(true)
}}
className="text-gray-500 dark:text-gray-400">
<Pen className="size-4" />
</button>
</Tooltip>
</div>
)
}
]}
bordered
dataSource={copilotData}
rowKey={(record) => record.key}
/>
)}
</div>
)
}
return (
<div>
<div className="flex items-center justify-end mb-6">
<Segmented
size="large"
options={[
{
label: t(
"managePrompts.segmented.custom"
),
value: "custom"
},
{
label: t(
"managePrompts.segmented.copilot"
),
value: "copilot"
}
]}
onChange={(value) => {
setSelectedSegment(value as "custom" | "copilot")
}}
/>
</div>
{selectedSegment === "custom" && customPrompts()}
{selectedSegment === "copilot" && copilotPrompts()}
<Modal
title={t("managePrompts.modal.addTitle")}
@ -301,6 +437,59 @@ export const PromptBody = () => {
</Form.Item>
</Form>
</Modal>
<Modal
title={t("managePrompts.modal.editTitle")}
open={openCopilotEdit}
onCancel={() => setOpenCopilotEdit(false)}
footer={null}>
<Form
onFinish={(values) =>
updateCopilotPrompt({
key: editCopilotId,
prompt: values.prompt
})
}
layout="vertical"
form={editCopilotForm}>
<Form.Item
name="prompt"
label={t("managePrompts.form.prompt.label")}
rules={[
{
required: true,
message: t("managePrompts.form.prompt.required")
},
{
validator: (_, value) => {
if (value && value.includes("{text}")) {
return Promise.resolve()
}
return Promise.reject(
new Error(
t("managePrompts.form.prompt.missingTextPlaceholder")
)
)
}
}
]}>
<Input.TextArea
placeholder={t("managePrompts.form.prompt.placeholder")}
autoSize={{ minRows: 3, maxRows: 10 }}
/>
</Form.Item>
<Form.Item>
<button
disabled={isUpdatingCopilotPrompt}
className="inline-flex justify-center w-full text-center mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
{isUpdatingCopilotPrompt
? t("managePrompts.form.btnEdit.saving")
: t("managePrompts.form.btnEdit.save")}
</button>
</Form.Item>
</Form>
</Modal>
</div>
)
}

View File

@ -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 = () => {
/>
</div>
<div className="flex flex-row justify-between">
<div className="inline-flex items-center gap-2">
<span className="text-gray-700 dark:text-neutral-50">
{t("generalSettings.settings.generateTitle.label")}
</span>
</div>
<Switch
checked={generateTitle}
onChange={(checked) => setGenerateTitle(checked)}
/>
</div>
<div className="flex flex-row justify-between">
<span className="text-gray-700 dark:text-neutral-50 ">
{t("generalSettings.settings.darkMode.label")}

View File

@ -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}

View File

@ -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)

View File

@ -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)
}
})

View File

@ -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 (
<MemoryRouter>
<ConfigProvider
@ -29,7 +40,8 @@ function IndexOption() {
}}
description={t("common:noData")}
/>
)}>
)}
direction={direction}>
<StyleProvider hashPriority="high">
<QueryClientProvider client={queryClient}>
<PageAssistProvider>

View File

@ -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 (
<MemoryRouter>
<ConfigProvider

View File

@ -1,5 +1,6 @@
import { saveHistory, saveMessage } from "@/db"
import { setLastUsedChatModel } from "@/services/model-settings"
import { generateTitle } from "@/services/title"
import { ChatHistory } from "@/store/option"
export const saveMessageOnError = async ({
@ -13,7 +14,8 @@ export const saveMessageOnError = async ({
selectedModel,
setHistoryId,
isRegenerating,
message_source = "web-ui"
message_source = "web-ui",
message_type
}: {
e: any
setHistory: (history: ChatHistory) => 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!)

View File

@ -0,0 +1,29 @@
import { useState, useEffect } from "react"
interface Message {
from: string
type: string
text: string
}
function useBackgroundMessage() {
const [message, setMessage] = useState<Message | null>(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

View File

@ -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
})
}
}

View File

@ -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",

17
src/i18n/lang/fa.ts Normal file
View File

@ -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
}

View File

@ -35,5 +35,9 @@ export const supportLanguage = [
{
label: "日本語",
value: "ja-JP"
},
{
label: "فارسی",
value: "fa"
}
]

View File

@ -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":

View File

@ -0,0 +1,14 @@
{
"extName": {
"message": "دستیار صفحه - یک رابط کاربری وب برای مدل های هوش مصنوعی لوکال"
},
"extDescription": {
"message": "از مدل های هوش مصنوعی لوکال خود برای دریافت کمک در هنگام مرور وب استفاده کنید."
},
"openSidePanelToChat": {
"message": "باز کردن کوپایلت برای گفتگو"
},
"openOptionToChat": {
"message": "باز کردن رابط کاربری وب برای گفتگو"
}
}

View File

@ -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<HTMLDivElement>(null)
const [dropedFile, setDropedFile] = React.useState<File | undefined>()
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 (
<div
ref={drop}

162
src/services/application.ts Normal file
View File

@ -0,0 +1,162 @@
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
const DEFAULT_SUMMARY_PROMPT = `Provide a concise summary of the following text, capturing its main ideas and key points:
Text:
---------
{text}
---------
Summarize the text in no more than 3-4 sentences.
Response:`
const DEFAULT_REPHRASE_PROMPT = `Rewrite the following text in a different way, maintaining its original meaning but using alternative vocabulary and sentence structures:
Text:
---------
{text}
---------
Ensure that your rephrased version conveys the same information and intent as the original.
Response:`
const DEFAULT_TRANSLATE_PROMPT = `Translate the following text from its original language into "english". Maintain the tone and style of the original text as much as possible:
Text:
---------
{text}
---------
Response:`
const DEFAULT_EXPLAIN_PROMPT = `Provide a detailed explanation of the following text, breaking down its key concepts, implications, and context:
Text:
---------
{text}
---------
Your explanation should:
Clarify any complex terms or ideas
Provide relevant background information
Discuss the significance or implications of the content
Address any potential questions a reader might have
Use examples or analogies to illustrate points when appropriate
Aim for a comprehensive explanation that would help someone with little prior knowledge fully understand the text.
Response:`
const DEFAULT_CUSTOM_PROMPT = `{text}`
export const getSummaryPrompt = async () => {
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 ""
}
}

72
src/services/title.ts Normal file
View File

@ -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<boolean | undefined>("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
}
}

View File

@ -12,6 +12,7 @@ export type ChatHistory = {
role: "user" | "assistant" | "system"
content: string
image?: string
messageType?: string
}[]
type State = {

View File

@ -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 = {

View File

@ -14,5 +14,6 @@ type WebSearch = {
sources: any[]
images?: string[]
search?: WebSearch
messageType?: string
id?: string
}

8
src/utils/color.ts Normal file
View File

@ -0,0 +1,8 @@
export const tagColors = {
summary: "blue",
explain: "green",
translate: "purple",
custom: "orange",
rephrase: "yellow"
}

77
src/utils/pull-ollama.ts Normal file
View File

@ -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)
}

View File

@ -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"