Update language files and fix UI issues

This commit is contained in:
n4ze3m 2024-03-24 17:07:21 +05:30
parent d9ce1e2d2c
commit 502759fae6
24 changed files with 485 additions and 54 deletions

View File

@ -2,6 +2,7 @@
"save": "Save", "save": "Save",
"saved": "Saved", "saved": "Saved",
"cancel": "Cancel", "cancel": "Cancel",
"retry": "Retry",
"share": { "share": {
"tooltip": { "tooltip": {
"share": "Share" "share": "Share"
@ -41,5 +42,8 @@
"saveAndSubmit": "Save & Submit", "saveAndSubmit": "Save & Submit",
"editMessage": { "editMessage": {
"placeholder": "Type a message..." "placeholder": "Type a message..."
} },
"submit": "Submit",
"noData": "No data",
"noHistory": "No chat history"
} }

View File

@ -1 +0,0 @@
{}

View File

@ -8,6 +8,8 @@
"error": "Error", "error": "Error",
"somethingWentWrong": "Something went wrong", "somethingWentWrong": "Something went wrong",
"validationSelectModel": "Please select a model to continue", "validationSelectModel": "Please select a model to continue",
"deleteHistoryConfirmation": "Are you sure you want to delete this history?",
"editHistoryTitle": "Enter a new title",
"generalSettings": { "generalSettings": {
"title": "General Settings", "title": "General Settings",
"heading": "Web UI Settings", "heading": "Web UI Settings",
@ -16,6 +18,10 @@
"label": "Speech Recognition Language", "label": "Speech Recognition Language",
"placeholder": "Select a language" "placeholder": "Select a language"
}, },
"language": {
"label": "Language",
"placeholder": "Select a language"
},
"darkMode": { "darkMode": {
"label": "Change Theme", "label": "Change Theme",
"options": { "options": {

View File

@ -0,0 +1,27 @@
{
"ollamaState": {
"searching": "Searching for Your Ollama 🦙",
"running": "Ollama is running 🦙",
"notRunning": "Unable to connect to Ollama 🦙"
},
"formError": {
"noModel": "Please select a model",
"noEmbeddingModel": "Please set an embedding model on the Settings > Ollama page"
},
"form": {
"textarea": {
"placeholder": "Type a message..."
},
"webSearch": {
"on": "On",
"off": "Off"
}
},
"tooltip": {
"searchInternet": "Search Internet",
"speechToText": "Speech to Text",
"uploadImage": "Upload Image",
"stopStreaming": "Stop Streaming"
},
"sendWhenEnter": "Send when Enter pressed"
}

View File

@ -0,0 +1,49 @@
{
"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": "ചാറ്റ് ചരിത്രം ലഭ്യമല്ല"
}

View File

@ -0,0 +1,215 @@
{
"newChat": "പുതിയ ചാറ്റ്",
"selectAModel": "ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക",
"selectAPrompt": "ഒരു പ്രോംപ്റ്റ് തിരഞ്ഞെടുക്കുക",
"githubRepository": "ഗിറ്റ്ഹബ് റെപ്പോസിറ്ററി",
"settings": "ക്രമീകരണങ്ങള്‍",
"sidebarTitle": "ചാറ്റ് ചരിത്രം",
"error": "പിശക്",
"somethingWentWrong": "എന്തോ തെറ്റായി",
"deleteHistoryConfirmation": "നിങ്ങളുടെ ചാറ്റ് ചരിത്രം ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ?",
"editHistoryTitle": "ചാറ്റ് title എഡിറ്റുചെയ്യുക",
"validationSelectModel": "തുടരുന്നതിന് ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക",
"generalSettings": {
"title": "പൊതുവായ ക്രമീകരണങ്ങള്‍",
"heading": "വെബ് UI ക്രമീകരണങ്ങള്‍",
"settings": {
"speechRecognitionLang": {
"label": "സംഭാഷണ തിരിച്ചറിയല്‍ ഭാഷ",
"placeholder": "ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക"
},
"language": {
"label": "ഭാഷ",
"placeholder": "ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക"
},
"darkMode": {
"label": "തീം മാറ്റുക",
"options": {
"light": "ലൈറ്റ്",
"dark": "ഡാര്‍ക്ക്"
}
},
"searchMode": {
"label": "സാധാരണ ഇന്റർനെറ്റ് അന്വേഷണം നടത്തുക"
},
"deleteChatHistory": {
"label": "ചാറ്റ് ചരിത്രം ഇല്ലാതാക്കുക",
"button": "ഇല്ലാതാക്കുക",
"confirm": "നിങ്ങളുടെ ചാറ്റ് ചരിത്രം ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഈ പ്രവർത്തനം പിന്നീട് പിൻവലിക്കാനാകില്ല."
}
}
},
"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": "പുതിയ പ്രോംപ്റ്റ് ചേര്‍ക്കുക",
"columns": {
"title": "തലക്കെട്ട്",
"prompt": "പ്രോംപ്റ്റ്",
"type": "പ്രോംപ്റ്റ് തരം",
"actions": "പ്രവർത്തനങ്ങൾ"
},
"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": "പേജ് പങ്കിടാനുള്ള URL",
"placeholder": "പേജ് പങ്കിടാനുള്ള URL നല്കുക",
"required": "ദയവായി നിങ്ങളുടെ പേജ് പങ്കിടാനുള്ള URL നല്കുക!",
"help": "സ്വകാര്യതക്കായി, നിങ്ങള്‍ക്ക് സ്വന്തമായി പേജ് പങ്കിടുന്ന സൗകര്യം ഹോസ്റ്റ് ചെയ്യാനും അവിടെയുള്ള URL ഇവിടെ നല്കാനും കഴിയും. <anchor>കൂടുതല്‍ അറിയുക</anchor>."
}
},
"webshare": {
"heading": "വെബ് പങ്കിടല്‍",
"columns": {
"title": "തലക്കെട്ട്",
"url": "URL",
"actions": "പ്രവർത്തനങ്ങൾ"
},
"tooltip": {
"delete": "പങ്കിടല്‍ ഇല്ലാതാക്കുക"
},
"confirm": {
"delete": "ഈ പങ്കിടല്‍ ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഈ പ്രവർത്തനം പിന്നീട് പിൻവലിക്കാനാകില്ല."
}
},
"notification": {
"pageShareSuccess": "പേജ് പങ്കിടാനുള്ള URL വിജയകരമായി അപ്ഡേറ്റ് ചെയ്തു",
"someError": "എന്തോ തെറ്റായി. ദയവായി പിന്നീട് വീണ്ടും ശ്രമിക്കുക",
"webShareDeleteSuccess": "വെബ് പങ്കിടല്‍ വിജയകരമായി ഇല്ലാതാക്കി"
}
},
"ollamaSettings": {
"title": "ഒല്ലാമാ ക്രമീകരണങ്ങള്‍",
"heading": "ഒല്ലാമാ കോൺഫിഗർ ചെയ്യുക",
"settings": {
"ollamaUrl": {
"label": "ഒല്ലാമാ URL",
"placeholder": "ഒല്ലാമാ URL നല്കുക"
},
"ragSettings": {
"label": "RAG ക്രമീകരണങ്ങള്‍",
"model": {
"label": "എംബെഡിംഗ് മോഡല്‍",
"required": "ദയവായി ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക",
"help": "`nomic-embed-text` പോലുള്ള എംബെഡിംഗ് മോഡലുകള്‍ ഉപയോഗിക്കുന്നത് വളരെ നന്നായിരിക്കും.",
"placeholder": "ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക"
},
"chunkSize": {
"label": "ചങ്ക് വലുപ്പം",
"placeholder": "ചങ്ക് വലുപ്പം നല്കുക",
"required": "ദയവായി ചങ്ക് വലുപ്പം നല്കുക"
},
"chunkOverlap": {
"label": "ചങ്ക് ഓവര്‍ലാപ്പ്",
"placeholder": "ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക",
"required": "ദയവായി ചങ്ക് ഓവര്‍ലാപ്പ് നല്കുക"
}
},
"prompt": {
"label": "RAG പ്രോംപ്റ്റ് കോൺഫിഗർ ചെയ്യുക",
"option1": "സാധാരണ",
"option2": "വെബ്",
"alert": "സിസ്റ്റം പ്രോംപ്റ്റ് ഇവിടെ കോൺഫിഗർ ചെയ്യുന്നത് പഴയൗഖികമായി. ദയവായി പ്രോംപ്റ്റുകള്‍ ചേര്‍ക്കാനോ എഡിറ്റുചെയ്യാനോ മാനേജ് പ്രോംപ്റ്റ്‌സ് സെക്ഷന്‍ ഉപയോഗിക്കുക. ഈ സെക്ഷന്‍ ഭാവിയില്‍ നീക്കം ചെയ്യപ്പെടും.",
"systemPrompt": "സിസ്റ്റം പ്രോംപ്റ്റ്",
"systemPromptPlaceholder": "സിസ്റ്റം പ്രോംപ്റ്റ് നല്കുക",
"webSearchPrompt": "വെബ് തിരയല്‍ പ്രോംപ്റ്റ്",
"webSearchPromptHelp": "പ്രോംപ്റ്റില്‍ നിന്ന് `{search_results}` നീക്കം ചെയ്യരുത്.",
"webSearchPromptError": "ദയവായി ഒരു വെബ് തിരയല്‍ പ്രോംപ്റ്റ് നല്കുക",
"webSearchPromptPlaceholder": "വെബ് തിരയല്‍ പ്രോംപ്റ്റ് നല്കുക",
"webSearchFollowUpPrompt": "വെബ് തിരയല്‍ തുടര്‍പ്രോംപ്റ്റ്",
"webSearchFollowUpPromptHelp": "പ്രോംപ്റ്റില്‍ നിന്ന് `{chat_history}` യും `{question}` യും നീക്കം ചെയ്യരുത്.",
"webSearchFollowUpPromptError": "ദയവായി നിങ്ങളുടെ വെബ് തിരയല്‍ തുടര്‍പ്രോംപ്റ്റ് നല്കുക!",
"webSearchFollowUpPromptPlaceholder": "നിങ്ങളുടെ വെബ് തിരയല്‍ തുടര്‍പ്രോംപ്റ്റ്"
}
}
}
}

View File

@ -0,0 +1,27 @@
{
"ollamaState": {
"searching": "നിങ്ങളുടെ ഒല്ലാമയ്ക്കായി തിരയുന്നു 🦙",
"running": "ഒല്ലാമ പ്രവര്‍ത്തിക്കുന്നു 🦙",
"notRunning": "ഒല്ലാമയുമായി ബന്ധിപ്പിക്കാന്‍ കഴിയുന്നില്ല 🦙"
},
"formError": {
"noModel": "ദയവായി ഒരു മോഡല്‍ തിരഞ്ഞെടുക്കുക",
"noEmbeddingModel": "ക്രമീകരണങ്ങള്‍ > ഒല്ലാമ പേജിലുള്ള എംബെഡിംഗ് മോഡല്‍ സജ്ജീകരിക്കുക"
},
"form": {
"textarea": {
"placeholder": "ഒരു സന്ദേശം ടൈപ്പ് ചെയ്യുക..."
},
"webSearch": {
"on": "ഓണ്‍",
"off": "ഓഫ്"
}
},
"tooltip": {
"searchInternet": "ഇന്റര്‍നെറ്റ് തിരയുക",
"speechToText": "സംഭാഷണം ടെക്സ്റ്റായി",
"uploadImage": "ഇമേജ് അപ്‌ലോഡ് ചെയ്യുക",
"stopStreaming": "സ്ട്രീമിംഗ് നിർത്തുക"
},
"sendWhenEnter": "എന്റര്‍ അമര്‍ത്തുമ്പോള്‍ അയയ്ക്കുക"
}

View File

@ -26,7 +26,7 @@ const LinkComponent = (item: {
item.current === item.href item.current === item.href
? "bg-gray-100 text-gray-600 dark:bg-[#262626] dark:text-white" ? "bg-gray-100 text-gray-600 dark:bg-[#262626] dark:text-white"
: "text-gray-700 hover:text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:text-white dark:hover:bg-[#262626]", : "text-gray-700 hover:text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:text-white dark:hover:bg-[#262626]",
"group flex gap-x-3 rounded-md py-2 pl-2 pr-3 text-sm leading-6 font-semibold" "group flex gap-x-3 rounded-md py-2 pl-2 pr-3 text-sm font-semibold"
)}> )}>
<item.icon <item.icon
className={classNames( className={classNames(

View File

@ -14,7 +14,7 @@ dayjs.extend(relativeTime)
export const ModelsBody = () => { export const ModelsBody = () => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { t } = useTranslation("option") const { t } = useTranslation(["option", "common"])
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
@ -188,9 +188,13 @@ export const ModelsBody = () => {
} }
]} ]}
dataSource={[record.details]} dataSource={[record.details]}
locale={{
emptyText: t("common:noData")
}}
/> />
), ),
defaultExpandAllRows: false defaultExpandAllRows: false,
}} }}
bordered bordered
dataSource={data} dataSource={data}

View File

@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query"
import { RotateCcw } from "lucide-react" import { RotateCcw } from "lucide-react"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { import {
getOllamaURL, getOllamaURL,
isOllamaRunning, isOllamaRunning,
@ -9,6 +10,7 @@ import {
export const PlaygroundEmpty = () => { export const PlaygroundEmpty = () => {
const [ollamaURL, setOllamaURL] = useState<string>("") const [ollamaURL, setOllamaURL] = useState<string>("")
const { t } = useTranslation(["playground", "common"])
const { const {
data: ollamaInfo, data: ollamaInfo,
status: ollamaStatus, status: ollamaStatus,
@ -40,7 +42,7 @@ export const PlaygroundEmpty = () => {
<div className="inline-flex items-center space-x-2"> <div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-blue-500 rounded-full animate-bounce"></div> <div className="w-3 h-3 bg-blue-500 rounded-full animate-bounce"></div>
<p className="dark:text-gray-400 text-gray-900"> <p className="dark:text-gray-400 text-gray-900">
Searching for Your Ollama 🦙 {t("ollamaState.searching")}
</p> </p>
</div> </div>
)} )}
@ -49,7 +51,7 @@ export const PlaygroundEmpty = () => {
<div className="inline-flex items-center space-x-2"> <div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div> <div className="w-3 h-3 bg-green-500 rounded-full"></div>
<p className="dark:text-gray-400 text-gray-900"> <p className="dark:text-gray-400 text-gray-900">
Ollama is running 🦙 {t("ollamaState.running")}
</p> </p>
</div> </div>
) : ( ) : (
@ -57,7 +59,7 @@ export const PlaygroundEmpty = () => {
<div className="inline-flex space-x-2"> <div className="inline-flex space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div> <div className="w-3 h-3 bg-red-500 rounded-full"></div>
<p className="dark:text-gray-400 text-gray-900"> <p className="dark:text-gray-400 text-gray-900">
Unable to connect to Ollama 🦙 {t("ollamaState.notRunning")}
</p> </p>
</div> </div>
@ -75,7 +77,7 @@ export const PlaygroundEmpty = () => {
}} }}
className="inline-flex 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-indigo-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 "> className="inline-flex 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-indigo-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 ">
<RotateCcw className="h-4 w-4 mr-3" /> <RotateCcw className="h-4 w-4 mr-3" />
Retry {t("common:retry")}
</button> </button>
</div> </div>
) )

View File

@ -11,12 +11,14 @@ import { useWebUI } from "~/store/webui"
import { defaultEmbeddingModelForRag } from "~/services/ollama" import { defaultEmbeddingModelForRag } from "~/services/ollama"
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react" import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
import { getVariable } from "~/utils/select-varaible" import { getVariable } from "~/utils/select-varaible"
import { useTranslation } from "react-i18next"
type Props = { type Props = {
dropedFile: File | undefined dropedFile: File | undefined
} }
export const PlaygroundForm = ({ dropedFile }: Props) => { export const PlaygroundForm = ({ dropedFile }: Props) => {
const { t } = useTranslation(["playground", "common"])
const inputRef = React.useRef<HTMLInputElement>(null) const inputRef = React.useRef<HTMLInputElement>(null)
const [typing, setTyping] = React.useState<boolean>(false) const [typing, setTyping] = React.useState<boolean>(false)
const { const {
@ -131,16 +133,13 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
return return
} }
if (!selectedModel || selectedModel.length === 0) { if (!selectedModel || selectedModel.length === 0) {
form.setFieldError("message", "Please select a model") form.setFieldError("message", t("formError.noModel"))
return return
} }
if (webSearch) { if (webSearch) {
const defaultEM = await defaultEmbeddingModelForRag() const defaultEM = await defaultEmbeddingModelForRag()
if (!defaultEM) { if (!defaultEM) {
form.setFieldError( form.setFieldError("message", t("formError.noEmbeddingModel"))
"message",
"Please set an embedding model on the Settings > Ollama page"
)
return return
} }
} }
@ -181,16 +180,13 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<form <form
onSubmit={form.onSubmit(async (value) => { onSubmit={form.onSubmit(async (value) => {
if (!selectedModel || selectedModel.length === 0) { if (!selectedModel || selectedModel.length === 0) {
form.setFieldError("message", "Please select a model") form.setFieldError("message", t("formError.noModel"))
return return
} }
if (webSearch) { if (webSearch) {
const defaultEM = await defaultEmbeddingModelForRag() const defaultEM = await defaultEmbeddingModelForRag()
if (!defaultEM) { if (!defaultEM) {
form.setFieldError( form.setFieldError("message", t("formError.noEmbeddingModel"))
"message",
"Please set an embedding model on the Settings > Ollama page"
)
return return
} }
} }
@ -223,12 +219,12 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
rows={1} rows={1}
style={{ minHeight: "60px" }} style={{ minHeight: "60px" }}
tabIndex={0} tabIndex={0}
placeholder="Type a message..." placeholder={t("form.textarea.placeholder")}
{...form.getInputProps("message")} {...form.getInputProps("message")}
/> />
<div className="mt-4 flex justify-between items-center"> <div className="mt-4 flex justify-between items-center">
<div className="flex"> <div className="flex">
<Tooltip title="Search Internet"> <Tooltip title={t("tooltip.searchInternet")}>
<div className="inline-flex items-center gap-2"> <div className="inline-flex items-center gap-2">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -246,14 +242,14 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<Switch <Switch
value={webSearch} value={webSearch}
onChange={(e) => setWebSearch(e)} onChange={(e) => setWebSearch(e)}
checkedChildren="On" checkedChildren={t("form.webSearch.on")}
unCheckedChildren="Off" unCheckedChildren={t("form.webSearch.off")}
/> />
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
<div className="flex !justify-end gap-3"> <div className="flex !justify-end gap-3">
<Tooltip title="Voice Message"> <Tooltip title={t("tooltip.speechToText")}>
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -277,7 +273,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
)} )}
</button> </button>
</Tooltip> </Tooltip>
<Tooltip title="Upload Image"> <Tooltip title={t("tooltip.uploadImage")}>
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -319,7 +315,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
onChange={(e) => onChange={(e) =>
setSendWhenEnter(e.target.checked) setSendWhenEnter(e.target.checked)
}> }>
Send when Enter pressed {t("sendWhenEnter")}
</Checkbox> </Checkbox>
) )
} }
@ -340,11 +336,11 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<path d="M20 4v7a4 4 0 01-4 4H4"></path> <path d="M20 4v7a4 4 0 01-4 4H4"></path>
</svg> </svg>
) : null} ) : null}
Submit {t("common:submit")}
</div> </div>
</Dropdown.Button> </Dropdown.Button>
) : ( ) : (
<Tooltip title="Stop Streaming"> <Tooltip title={t("tooltip.stopStreaming")}>
<button <button
type="button" type="button"
onClick={stopStreamingRequest} onClick={stopStreamingRequest}

View File

@ -7,7 +7,8 @@ import {
Modal, Modal,
Input, Input,
Form, Form,
Switch Switch,
Empty
} from "antd" } from "antd"
import { Trash2, Pen, Computer, Zap } from "lucide-react" import { Trash2, Pen, Computer, Zap } from "lucide-react"
import { useState } from "react" import { useState } from "react"

View File

@ -7,6 +7,7 @@ import { SUPPORTED_LANGUAGES } from "~/utils/supporetd-languages"
import { MoonIcon, SunIcon } from "lucide-react" import { MoonIcon, SunIcon } from "lucide-react"
import { SearchModeSettings } from "./search-mode" import { SearchModeSettings } from "./search-mode"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useI18n } from "@/hooks/useI18n"
export const SettingOther = () => { export const SettingOther = () => {
const { clearChat, speechToTextLanguage, setSpeechToTextLanguage } = const { clearChat, speechToTextLanguage, setSpeechToTextLanguage } =
@ -16,6 +17,11 @@ export const SettingOther = () => {
const { mode, toggleDarkMode } = useDarkMode() const { mode, toggleDarkMode } = useDarkMode()
const { t } = useTranslation("option") const { t } = useTranslation("option")
const {
changeLocale,
locale,
supportLanguage
}= useI18n()
return ( return (
@ -46,6 +52,26 @@ export const SettingOther = () => {
}} }}
/> />
</div> </div>
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-neutral-50">
{t("generalSettings.settings.language.label")}
</span>
<Select
placeholder={t("generalSettings.settings.language.placeholder")}
allowClear
showSearch
options={supportLanguage}
value={locale}
filterOption={(input, option) =>
option!.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
option!.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
onChange={(value) => {
changeLocale(value)
}}
/>
</div>
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-neutral-50 "> <span className="text-gray-500 dark:text-neutral-50 ">
{t("generalSettings.settings.darkMode.label")} {t("generalSettings.settings.darkMode.label")}

View File

@ -8,9 +8,9 @@ import {
} from "~/libs/db" } from "~/libs/db"
import { Empty, Skeleton } from "antd" import { Empty, Skeleton } from "antd"
import { useMessageOption } from "~/hooks/useMessageOption" import { useMessageOption } from "~/hooks/useMessageOption"
import { useState } from "react"
import { PencilIcon, Trash2 } from "lucide-react" import { PencilIcon, Trash2 } from "lucide-react"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { useTranslation } from "react-i18next"
type Props = { type Props = {
onClose: () => void onClose: () => void
@ -19,6 +19,7 @@ type Props = {
export const Sidebar = ({ onClose }: Props) => { export const Sidebar = ({ onClose }: Props) => {
const { setMessages, setHistory, setHistoryId, historyId, clearChat } = const { setMessages, setHistory, setHistoryId, historyId, clearChat } =
useMessageOption() useMessageOption()
const { t } = useTranslation(["option", "common"])
const client = useQueryClient() const client = useQueryClient()
const navigate = useNavigate() const navigate = useNavigate()
@ -60,7 +61,7 @@ export const Sidebar = ({ onClose }: Props) => {
<div className="overflow-y-auto z-99"> <div className="overflow-y-auto z-99">
{status === "success" && chatHistories.length === 0 && ( {status === "success" && chatHistories.length === 0 && (
<div className="flex justify-center items-center mt-20 overflow-hidden"> <div className="flex justify-center items-center mt-20 overflow-hidden">
<Empty description="No history yet" /> <Empty description={t("common:noHistory")} />
</div> </div>
)} )}
{status === "pending" && ( {status === "pending" && (
@ -95,7 +96,7 @@ export const Sidebar = ({ onClose }: Props) => {
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<button <button
onClick={() => { onClick={() => {
const newTitle = prompt("Enter new title", chat.title) const newTitle = prompt(t("editHistoryTitle"), chat.title)
if (newTitle) { if (newTitle) {
editHistory({ id: chat.id, title: newTitle }) editHistory({ id: chat.id, title: newTitle })
@ -107,10 +108,7 @@ export const Sidebar = ({ onClose }: Props) => {
<button <button
onClick={() => { onClick={() => {
if ( if (!confirm(t("deleteHistoryConfirmation"))) return
!confirm("Are you sure you want to delete this history?")
)
return
deleteHistory(chat.id) deleteHistory(chat.id)
}} }}
className="text-red-500 dark:text-red-400 opacity-80"> className="text-red-500 dark:text-red-400 opacity-80">

View File

@ -92,7 +92,8 @@ export default defineBackground({
if (!isRunning) { if (!isRunning) {
chrome.action.setBadgeText({ text: "E" }) chrome.action.setBadgeText({ text: "E" })
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" }) chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
chrome.action.setTitle({ title: "Ollama is not running" }) chrome.action.setTitle({ title: "Ollama is not running"
})
setTimeout(() => { setTimeout(() => {
clearBadge() clearBadge()
}, 5000) }, 5000)
@ -123,7 +124,7 @@ export default defineBackground({
chrome.contextMenus.create({ chrome.contextMenus.create({
id: "open-side-panel-pa", id: "open-side-panel-pa",
title: "Open Side Panel to Chat", title: browser.i18n.getMessage("openSidePanelToChat"),
contexts: ["all"] contexts: ["all"]
}) })

View File

@ -3,21 +3,32 @@ import { MemoryRouter } from "react-router-dom"
import { ToastContainer } from "react-toastify" import { ToastContainer } from "react-toastify"
import "react-toastify/dist/ReactToastify.css" import "react-toastify/dist/ReactToastify.css"
const queryClient = new QueryClient() const queryClient = new QueryClient()
import { ConfigProvider, theme } from "antd" import { ConfigProvider, Empty, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs" import { StyleProvider } from "@ant-design/cssinjs"
import { useDarkMode } from "~/hooks/useDarkmode" import { useDarkMode } from "~/hooks/useDarkmode"
import { OptionRouting } from "~/routes" import { OptionRouting } from "~/routes"
import "~/i18n" import "~/i18n"
import { useTranslation } from "react-i18next"
function IndexOption() { function IndexOption() {
const { mode } = useDarkMode() const { mode } = useDarkMode()
const { t } = useTranslation()
return ( return (
<MemoryRouter> <MemoryRouter>
<ConfigProvider <ConfigProvider
theme={{ theme={{
algorithm: algorithm:
mode === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm mode === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm
}}> }}
renderEmpty={() => (
<Empty
imageStyle={{
height: 60
}}
description={t("common:noData")}
/>
)}
>
<StyleProvider hashPriority="high"> <StyleProvider hashPriority="high">
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<OptionRouting /> <OptionRouting />

View File

@ -4,13 +4,15 @@ import { SidepanelRouting } from "~/routes"
import { ToastContainer } from "react-toastify" import { ToastContainer } from "react-toastify"
import "react-toastify/dist/ReactToastify.css" import "react-toastify/dist/ReactToastify.css"
const queryClient = new QueryClient() const queryClient = new QueryClient()
import { ConfigProvider, theme } from "antd" import { ConfigProvider, Empty, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs" import { StyleProvider } from "@ant-design/cssinjs"
import { useDarkMode } from "~/hooks/useDarkmode" import { useDarkMode } from "~/hooks/useDarkmode"
import "~/i18n" import "~/i18n"
import { useTranslation } from "react-i18next"
function IndexSidepanel() { function IndexSidepanel() {
const { mode } = useDarkMode() const { mode } = useDarkMode()
const { t } = useTranslation()
return ( return (
<MemoryRouter> <MemoryRouter>
@ -18,7 +20,16 @@ function IndexSidepanel() {
theme={{ theme={{
algorithm: algorithm:
mode === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm mode === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm
}}> }}
renderEmpty={() => (
<Empty
imageStyle={{
height: 60
}}
description={t("common:noData")}
/>
)}
>
<StyleProvider hashPriority="high"> <StyleProvider hashPriority="high">
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<SidepanelRouting /> <SidepanelRouting />

17
src/hooks/useI18n.tsx Normal file
View File

@ -0,0 +1,17 @@
import { supportLanguage } from "@/i18n/support-language"
import { useState } from "react"
import { useTranslation } from "react-i18next"
export const useI18n = () => {
const { i18n } = useTranslation()
const [locale, setLocale] = useState<string>(
localStorage.getItem("i18nextLng") || "en"
)
const changeLocale = (lang: string) => {
setLocale(lang)
i18n.changeLanguage(lang)
}
return { locale, changeLocale, supportLanguage }
}

View File

@ -2,6 +2,7 @@ import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import { en } from "./lang/en"; import { en } from "./lang/en";
import { ml } from "./lang/ml";
i18n i18n
.use(LanguageDetector) .use(LanguageDetector)
@ -9,7 +10,8 @@ i18n
.init({ .init({
debug: true, debug: true,
resources: { resources: {
en: en en: en,
ml: ml
}, },
fallbackLng: "en", fallbackLng: "en",
lng: localStorage.getItem("i18nextLng") || "en", lng: localStorage.getItem("i18nextLng") || "en",

View File

@ -1,10 +1,10 @@
import option from "@/assets/locale/en/option.json"; import option from "@/assets/locale/en/option.json";
import optionPlayground from "@/assets/locale/en/option-playground.json"; import playground from "@/assets/locale/en/playground.json";
import common from "@/assets/locale/en/common.json"; import common from "@/assets/locale/en/common.json";
export const en = { export const en = {
option, option,
optionPlayground, playground,
common common
} }

10
src/i18n/lang/ml.ts Normal file
View File

@ -0,0 +1,10 @@
import option from "@/assets/locale/ml/option.json";
import playground from "@/assets/locale/ml/playground.json";
import common from "@/assets/locale/ml/common.json";
export const ml = {
option,
playground,
common
}

View File

@ -0,0 +1,11 @@
// Please add new language code to supportLanguage array
export const supportLanguage = [
{
label: "English",
value: "en"
},
{
label: "മലയാളം",
value: "ml"
}
]

View File

@ -4,5 +4,8 @@
}, },
"extDescription": { "extDescription": {
"message": "Use your locally running AI models to assist you in your web browsing." "message": "Use your locally running AI models to assist you in your web browsing."
},
"openSidePanelToChat": {
"message": "Open Side Panel to Chat"
} }
} }

View File

@ -0,0 +1,11 @@
{
"extName": {
"message": "പേജ് അസിസ്റ്റ് - പ്രാദേശികമായി പ്രവര്‍ത്തിക്കുന്ന AI മോഡലുകള്‍ക്കുള്ള വെബ് UI"
},
"extDescription": {
"message": "നിങ്ങളുടെ പ്രാദേശികമായി പ്രവര്‍ത്തിക്കുന്ന AI മോഡലുകള്‍ വെബ് ബ്രൗസിംഗില്‍ നിങ്ങളെ സഹായിക്കുന്നതിന് ഉപയോഗിക്കുക."
},
"openSidePanelToChat": {
"message": "ചാറ്റ് ചെയ്യാന്‍ സൈഡ് പാനല്‍ തുറക്കുക"
}
}