Add i18n
This commit is contained in:
parent
4055231bbc
commit
9a2adbd859
@ -31,12 +31,14 @@
|
|||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
|
"i18next": "^23.10.1",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"langchain": "^0.1.9",
|
"langchain": "^0.1.9",
|
||||||
"lucide-react": "^0.350.0",
|
"lucide-react": "^0.350.0",
|
||||||
"plasmo": "0.84.1",
|
|
||||||
"property-information": "^6.4.1",
|
"property-information": "^6.4.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-i18next": "^14.1.0",
|
||||||
"react-markdown": "8.0.0",
|
"react-markdown": "8.0.0",
|
||||||
"react-router-dom": "6.10.0",
|
"react-router-dom": "6.10.0",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
1
src/assets/locale/en/option-playground.json
Normal file
1
src/assets/locale/en/option-playground.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
209
src/assets/locale/en/option.json
Normal file
209
src/assets/locale/en/option.json
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"newChat": "New Chat",
|
||||||
|
"selectAModel": "Select a Model",
|
||||||
|
"selectAPrompt": "Select a Prompt",
|
||||||
|
"githubRepository": "GitHub Repository",
|
||||||
|
"settings": "Settings",
|
||||||
|
"sidebarTitle": "Chat History",
|
||||||
|
"error": "Error",
|
||||||
|
"somethingWentWrong": "Something went wrong",
|
||||||
|
"validationSelectModel": "Please select a model to continue",
|
||||||
|
"generalSettings": {
|
||||||
|
"title": "General Settings",
|
||||||
|
"heading": "Web UI Settings",
|
||||||
|
"settings": {
|
||||||
|
"speechRecognitionLang": {
|
||||||
|
"label": "Speech Recognition Language",
|
||||||
|
"placeholder": "Select a language"
|
||||||
|
},
|
||||||
|
"darkMode": {
|
||||||
|
"label": "Change Theme",
|
||||||
|
"options": {
|
||||||
|
"light": "Light",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"searchMode": {
|
||||||
|
"label": "Perform Simple Internet Search"
|
||||||
|
},
|
||||||
|
"deleteChatHistory": {
|
||||||
|
"label": "Delete Chat History",
|
||||||
|
"button": "Delete",
|
||||||
|
"confirm": "Are you sure you want to delete your chat history? This action cannot be undone."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"manageModels": {
|
||||||
|
"title": "Manage Models",
|
||||||
|
"addBtn": "Add New Model",
|
||||||
|
"columns": {
|
||||||
|
"name": "Name",
|
||||||
|
"digest": "Digest",
|
||||||
|
"modifiedAt": "Modified At",
|
||||||
|
"size": "Size",
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"expandedColumns": {
|
||||||
|
"parentModel": "Parent Model",
|
||||||
|
"format": "Format",
|
||||||
|
"family": "Family",
|
||||||
|
"parameterSize": "Parameter Size",
|
||||||
|
"quantizationLevel": "Quantization Level"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"delete": "Delete Model",
|
||||||
|
"repull": "Re-Pull Model"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"delete": "Are you sure you want to delete this model?",
|
||||||
|
"repull": "Are you sure you want to re-pull this model?"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"title": "Add New Model",
|
||||||
|
"placeholder": "Enter Model Name",
|
||||||
|
"pull": "Pull Model"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"pullModel": "Pulling Model",
|
||||||
|
"pullModelDescription": "Pulling {{modelName}} model. For more details, check the extension icon.",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"successDescription": "Successfully pulled the model",
|
||||||
|
"successDeleteDescription": "Successfully deleted the model",
|
||||||
|
"someError": "Something went wrong. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"managePrompts": {
|
||||||
|
"title": "Manage Prompts",
|
||||||
|
"addBtn": "Add New Prompt",
|
||||||
|
"columns": {
|
||||||
|
"title": "Title",
|
||||||
|
"prompt": "Prompt",
|
||||||
|
"type": "Prompt Type",
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"systemPrompt": "System Prompt",
|
||||||
|
"quickPrompt": "Quick Prompt",
|
||||||
|
"tooltip": {
|
||||||
|
"delete": "Delete Prompt",
|
||||||
|
"edit": "Edit Prompt"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"delete": "Are you sure you want to delete this prompt? This action cannot be undone."
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"addTitle": "Add New Prompt",
|
||||||
|
"editTitle": "Edit Prompt"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": {
|
||||||
|
"label": "Title",
|
||||||
|
"placeholder": "My Awesome Prompt",
|
||||||
|
"required": "Please enter a title"
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"label": "Prompt",
|
||||||
|
"placeholder": "Enter Prompt",
|
||||||
|
"required": "Please enter a prompt",
|
||||||
|
"help": "You can use {key} as variable in your prompt."
|
||||||
|
},
|
||||||
|
"isSystem": {
|
||||||
|
"label": "Is System Prompt"
|
||||||
|
},
|
||||||
|
"btnSave": {
|
||||||
|
"saving": "Adding Prompt...",
|
||||||
|
"save": "Add Prompt"
|
||||||
|
},
|
||||||
|
"btnEdit": {
|
||||||
|
"saving": "Updating Prompt...",
|
||||||
|
"save": "Update Prompt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"addSuccess": "Prompt Added",
|
||||||
|
"addSuccessDesc": "Prompt has been added successfully",
|
||||||
|
"error": "Error",
|
||||||
|
"someError": "Something went wrong. Please try again later",
|
||||||
|
"updatedSuccess": "Prompt Updated",
|
||||||
|
"updatedSuccessDesc": "Prompt has been updated successfully",
|
||||||
|
"deletedSuccess": "Prompt Deleted",
|
||||||
|
"deletedSuccessDesc": "Prompt has been deleted successfully"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"manageShare": {
|
||||||
|
"title": "Manage Share",
|
||||||
|
"heading": "Configure Page Share URL",
|
||||||
|
"form": {
|
||||||
|
"url": {
|
||||||
|
"label": "Page Share URL",
|
||||||
|
"placeholder": "Enter Page Share URL",
|
||||||
|
"required": "Please input your Page Share URL!",
|
||||||
|
"help": "For privacy reasons, you can self-host the page share and provide the URL here. <anchor>Learn More</anchor>."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webshare": {
|
||||||
|
"heading": "Web Share",
|
||||||
|
"columns": {
|
||||||
|
"title": "Title",
|
||||||
|
"url": "URL",
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"delete": "Delete Share"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"delete": "Are you sure you want to delete this share? This action cannot be undone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"pageShareSuccess": "Page Share URL updated successfully",
|
||||||
|
"someError": "Something went wrong. Please try again later",
|
||||||
|
"webShareDeleteSuccess": "Web Share deleted successfully"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ollamaSettings": {
|
||||||
|
"title": "Ollama Settings",
|
||||||
|
"heading": "Configure Ollama",
|
||||||
|
"settings": {
|
||||||
|
"ollamaUrl": {
|
||||||
|
"label": "Ollama URL",
|
||||||
|
"placeholder": "Enter Ollama URL"
|
||||||
|
},
|
||||||
|
"ragSettings": {
|
||||||
|
"label": "RAG Settings",
|
||||||
|
"model": {
|
||||||
|
"label": "Embedding Model",
|
||||||
|
"required": "Please select a model",
|
||||||
|
"help": "Highly recommended to use embedding models like `nomic-embed-text`.",
|
||||||
|
"placeholder": "Select a model"
|
||||||
|
},
|
||||||
|
"chunkSize": {
|
||||||
|
"label": "Chunk Size",
|
||||||
|
"placeholder": "Enter Chunk Size",
|
||||||
|
"required": "Please enter a chunk size"
|
||||||
|
},
|
||||||
|
"chunkOverlap": {
|
||||||
|
"label": "Chunk Overlap",
|
||||||
|
"placeholder": "Enter Chunk Overlap",
|
||||||
|
"required": "Please enter a chunk overlap"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"label": "Configure RAG Prompt",
|
||||||
|
"option1": "Normal",
|
||||||
|
"option2": "Web",
|
||||||
|
"alert": "Configuring the system prompt here is deprecated. Please use the Manage Prompts section to add or edit prompts. This section will be removed in a future release",
|
||||||
|
"systemPrompt": "System Prompt",
|
||||||
|
"systemPromptPlaceholder": "Enter System Prompt",
|
||||||
|
"webSearchPrompt": "Web Search Prompt",
|
||||||
|
"webSearchPromptHelp": "Do not remove `{search_results}` from the prompt.",
|
||||||
|
"webSearchPromptError": "Please enter a web search prompt",
|
||||||
|
"webSearchPromptPlaceholder": "Enter Web Search Prompt",
|
||||||
|
"webSearchFollowUpPrompt": "Web Search Follow Up Prompt",
|
||||||
|
"webSearchFollowUpPromptHelp": "Do not remove `{chat_history}` and `{question}` from the prompt.",
|
||||||
|
"webSearchFollowUpPromptError": "Please input your Web Search Follow Up Prompt!",
|
||||||
|
"webSearchFollowUpPromptPlaceholder": "Your Web Search Follow Up Prompt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import {
|
|||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { getAllPrompts } from "~/libs/db"
|
import { getAllPrompts } from "~/libs/db"
|
||||||
import { ShareBtn } from "~/components/Common/ShareBtn"
|
import { ShareBtn } from "~/components/Common/ShareBtn"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
export default function OptionLayout({
|
export default function OptionLayout({
|
||||||
children
|
children
|
||||||
@ -24,6 +25,8 @@ export default function OptionLayout({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedModel,
|
selectedModel,
|
||||||
setSelectedModel,
|
setSelectedModel,
|
||||||
@ -61,8 +64,8 @@ export default function OptionLayout({
|
|||||||
if (prompt?.is_system) {
|
if (prompt?.is_system) {
|
||||||
setSelectedSystemPrompt(prompt.id)
|
setSelectedSystemPrompt(prompt.id)
|
||||||
} else {
|
} else {
|
||||||
setSelectedQuickPrompt(prompt.content)
|
setSelectedQuickPrompt(prompt!.content)
|
||||||
setSelectedSystemPrompt(null)
|
setSelectedSystemPrompt("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +96,7 @@ export default function OptionLayout({
|
|||||||
onClick={clearChat}
|
onClick={clearChat}
|
||||||
className="inline-flex items-center rounded-lg border dark:border-gray-700 bg-transparent px-3 py-3 text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ">
|
className="inline-flex items-center rounded-lg border dark:border-gray-700 bg-transparent px-3 py-3 text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ">
|
||||||
<SquarePen className="h-4 w-4 mr-3" />
|
<SquarePen className="h-4 w-4 mr-3" />
|
||||||
New Chat
|
{t("newChat")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
||||||
@ -106,12 +109,13 @@ export default function OptionLayout({
|
|||||||
size="large"
|
size="large"
|
||||||
loading={isModelsLoading || isModelsFetching}
|
loading={isModelsLoading || isModelsFetching}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option.label.toLowerCase().indexOf(input.toLowerCase()) >=
|
option!.label.toLowerCase().indexOf(input.toLowerCase()) >=
|
||||||
0 ||
|
0 ||
|
||||||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option!.value.toLowerCase().indexOf(input.toLowerCase()) >=
|
||||||
|
0
|
||||||
}
|
}
|
||||||
showSearch
|
showSearch
|
||||||
placeholder="Select a model"
|
placeholder={t("selectAModel")}
|
||||||
className="w-64 "
|
className="w-64 "
|
||||||
options={models?.map((model) => ({
|
options={models?.map((model) => ({
|
||||||
label: model.name,
|
label: model.name,
|
||||||
@ -127,12 +131,13 @@ export default function OptionLayout({
|
|||||||
size="large"
|
size="large"
|
||||||
loading={isPromptLoading}
|
loading={isPromptLoading}
|
||||||
showSearch
|
showSearch
|
||||||
placeholder="Select a prompt"
|
placeholder={t("selectAPrompt")}
|
||||||
className="w-60"
|
className="w-60"
|
||||||
allowClear
|
allowClear
|
||||||
onChange={handlePromptChange}
|
onChange={handlePromptChange}
|
||||||
value={selectedSystemPrompt}
|
value={selectedSystemPrompt}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
|
//@ts-ignore
|
||||||
option.label.key
|
option.label.key
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.indexOf(input.toLowerCase()) >= 0
|
.indexOf(input.toLowerCase()) >= 0
|
||||||
@ -161,7 +166,8 @@ export default function OptionLayout({
|
|||||||
{pathname === "/" && messages.length > 0 && !streaming && (
|
{pathname === "/" && messages.length > 0 && !streaming && (
|
||||||
<ShareBtn messages={messages} />
|
<ShareBtn messages={messages} />
|
||||||
)}
|
)}
|
||||||
<Tooltip title="Github Repository">
|
<Tooltip title={t("githubRepository")}
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/n4ze3m/page-assist"
|
href="https://github.com/n4ze3m/page-assist"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -169,7 +175,8 @@ export default function OptionLayout({
|
|||||||
<GithubIcon className="w-6 h-6" />
|
<GithubIcon className="w-6 h-6" />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Manage Ollama Models">
|
<Tooltip title={t("settings")}
|
||||||
|
>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/settings"
|
to="/settings"
|
||||||
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
@ -185,14 +192,12 @@ export default function OptionLayout({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
title={"Chat History"}
|
title={t("sidebarTitle")}
|
||||||
placement="left"
|
placement="left"
|
||||||
closeIcon={null}
|
closeIcon={null}
|
||||||
onClose={() => setSidebarOpen(false)}
|
onClose={() => setSidebarOpen(false)}
|
||||||
open={sidebarOpen}>
|
open={sidebarOpen}>
|
||||||
<Sidebar
|
<Sidebar onClose={() => setSidebarOpen(false)} />
|
||||||
onClose={() => setSidebarOpen(false)}
|
|
||||||
/>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Orbit,
|
Orbit,
|
||||||
Share
|
Share
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link, useLocation } from "react-router-dom"
|
import { Link, useLocation } from "react-router-dom"
|
||||||
|
|
||||||
function classNames(...classes: string[]) {
|
function classNames(...classes: string[]) {
|
||||||
@ -44,6 +45,8 @@ const LinkComponent = (item: {
|
|||||||
|
|
||||||
export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto max-w-7xl lg:flex lg:gap-x-16 lg:px-8">
|
<div className="mx-auto max-w-7xl lg:flex lg:gap-x-16 lg:px-8">
|
||||||
@ -54,31 +57,31 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
className="flex gap-x-3 gap-y-1 whitespace-nowrap lg:flex-col">
|
className="flex gap-x-3 gap-y-1 whitespace-nowrap lg:flex-col">
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings"
|
href="/settings"
|
||||||
name="General Settings"
|
name={t("generalSettings.title")}
|
||||||
icon={Orbit}
|
icon={Orbit}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
/>
|
/>
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/ollama"
|
href="/settings/ollama"
|
||||||
name="Ollama Settings"
|
name={t("ollamaSettings.title")}
|
||||||
icon={CircuitBoardIcon}
|
icon={CircuitBoardIcon}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
/>
|
/>
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/model"
|
href="/settings/model"
|
||||||
name="Manage Model"
|
name={t("manageModels.title")}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
icon={BrainCircuit}
|
icon={BrainCircuit}
|
||||||
/>
|
/>
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/prompt"
|
href="/settings/prompt"
|
||||||
name="Manage Prompt"
|
name={t("managePrompts.title")}
|
||||||
icon={Book}
|
icon={Book}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
/>
|
/>
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/share"
|
href="/settings/share"
|
||||||
name="Manage Share"
|
name={t("manageShare.title")}
|
||||||
icon={Share}
|
icon={Share}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
/>
|
/>
|
||||||
|
@ -7,12 +7,14 @@ import relativeTime from "dayjs/plugin/relativeTime"
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { Download, RotateCcw, Trash2 } from "lucide-react"
|
import { Download, RotateCcw, Trash2 } from "lucide-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
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 form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@ -32,22 +34,24 @@ export const ModelsBody = () => {
|
|||||||
queryKey: ["fetchAllModels"]
|
queryKey: ["fetchAllModels"]
|
||||||
})
|
})
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Model Deleted",
|
message: t("manageModels.notification.success"),
|
||||||
description: "Model has been deleted successfully"
|
description: t("manageModels.notification.successDeleteDescription")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: "Error",
|
||||||
description: error?.message || "Something went wrong"
|
description: error?.message || t("manageModels.notification.someError")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const pullModel = async (modelName: string) => {
|
const pullModel = async (modelName: string) => {
|
||||||
notification.info({
|
notification.info({
|
||||||
message: "Pulling Model",
|
message: t("manageModels.notification.pullModel"),
|
||||||
description: `Pulling ${modelName} model. For more details, check the extension icon.`
|
description: t("manageModels.notification.pullModelDescription", {
|
||||||
|
modelName
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -76,7 +80,7 @@ export const ModelsBody = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="inline-flex items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 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 items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 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">
|
||||||
Add New Model
|
{t("manageModels.addBtn")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -88,12 +92,12 @@ export const ModelsBody = () => {
|
|||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Name",
|
title: t("manageModels.columns.name"),
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name"
|
key: "name"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Digest",
|
title: t("manageModels.columns.digest"),
|
||||||
dataIndex: "digest",
|
dataIndex: "digest",
|
||||||
key: "digest",
|
key: "digest",
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
@ -105,28 +109,26 @@ export const ModelsBody = () => {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Modified",
|
title: t("manageModels.columns.modifiedAt"),
|
||||||
dataIndex: "modified_at",
|
dataIndex: "modified_at",
|
||||||
key: "modified_at",
|
key: "modified_at",
|
||||||
render: (text: string) => dayjs(text).fromNow(true)
|
render: (text: string) => dayjs(text).fromNow(true)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Size",
|
title: t("manageModels.columns.size"),
|
||||||
dataIndex: "size",
|
dataIndex: "size",
|
||||||
key: "size",
|
key: "size",
|
||||||
render: (text: number) => bytePerSecondFormatter(text)
|
render: (text: number) => bytePerSecondFormatter(text)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Action",
|
title: t("manageModels.columns.actions"),
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Tooltip title="Delete Model">
|
<Tooltip title={t("manageModels.tooltip.delete")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(t("manageModels.confirm.delete"))
|
||||||
"Are you sure you want to delete this model?"
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
deleteOllamaModel(record.model)
|
deleteOllamaModel(record.model)
|
||||||
}
|
}
|
||||||
@ -135,13 +137,11 @@ export const ModelsBody = () => {
|
|||||||
<Trash2 className="w-5 h-5" />
|
<Trash2 className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Re-Pull Model">
|
<Tooltip title={t("manageModels.tooltip.repull")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(t("manageModels.confirm.repull"))
|
||||||
"Are you sure you want to re-pull this model?"
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
pullOllamaModel(record.model)
|
pullOllamaModel(record.model)
|
||||||
}
|
}
|
||||||
@ -160,27 +160,29 @@ export const ModelsBody = () => {
|
|||||||
pagination={false}
|
pagination={false}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Parent Model",
|
title: t("manageModels.expandedColumns.parentModel"),
|
||||||
key: "parent_model",
|
key: "parent_model",
|
||||||
dataIndex: "parent_model"
|
dataIndex: "parent_model"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Format",
|
title: t("manageModels.expandedColumns.format"),
|
||||||
key: "format",
|
key: "format",
|
||||||
dataIndex: "format"
|
dataIndex: "format"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Family",
|
title: t("manageModels.expandedColumns.family"),
|
||||||
key: "family",
|
key: "family",
|
||||||
dataIndex: "family"
|
dataIndex: "family"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Parameter Size",
|
title: t("manageModels.expandedColumns.parameterSize"),
|
||||||
key: "parameter_size",
|
key: "parameter_size",
|
||||||
dataIndex: "parameter_size"
|
dataIndex: "parameter_size"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Quantization Level",
|
title: t(
|
||||||
|
"manageModels.expandedColumns.quantizationLevel"
|
||||||
|
),
|
||||||
key: "quantization_level",
|
key: "quantization_level",
|
||||||
dataIndex: "quantization_level"
|
dataIndex: "quantization_level"
|
||||||
}
|
}
|
||||||
@ -200,13 +202,13 @@ export const ModelsBody = () => {
|
|||||||
<Modal
|
<Modal
|
||||||
footer={null}
|
footer={null}
|
||||||
open={open}
|
open={open}
|
||||||
title="Add New Model"
|
title={t("manageModels.modal.title")}
|
||||||
onCancel={() => setOpen(false)}>
|
onCancel={() => setOpen(false)}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((values) => pullOllamaModel(values.model))}>
|
onSubmit={form.onSubmit((values) => pullOllamaModel(values.model))}>
|
||||||
<Input
|
<Input
|
||||||
{...form.getInputProps("model")}
|
{...form.getInputProps("model")}
|
||||||
placeholder="Enter model name"
|
placeholder={t("manageModels.modal.placeholder")}
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -214,7 +216,7 @@ export const ModelsBody = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
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 ">
|
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 ">
|
||||||
<Download className="w-5 h-5 mr-3" />
|
<Download className="w-5 h-5 mr-3" />
|
||||||
Pull Model
|
{t("manageModels.modal.pull")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { PencilIcon } from "lucide-react"
|
import { PencilIcon } from "lucide-react"
|
||||||
import { useMessage } from "../../../hooks/useMessage"
|
import { useMessage } from "../../../hooks/useMessage"
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const PlaygroundNewChat = () => {
|
export const PlaygroundNewChat = () => {
|
||||||
const { setHistory, setMessages, setHistoryId } = useMessage()
|
const { setHistory, setMessages, setHistoryId } = useMessage()
|
||||||
|
const { t } = useTranslation('optionChat')
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
setHistoryId(null)
|
setHistoryId(null)
|
||||||
@ -16,7 +18,7 @@ export const PlaygroundNewChat = () => {
|
|||||||
className="flex w-full border bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100 rounded-md p-2 dark:border-gray-800">
|
className="flex w-full border bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100 rounded-md p-2 dark:border-gray-800">
|
||||||
<PencilIcon className="mx-3 h-5 w-5" aria-hidden="true" />
|
<PencilIcon className="mx-3 h-5 w-5" aria-hidden="true" />
|
||||||
<span className="inline-flex font-semibol text-white text-sm">
|
<span className="inline-flex font-semibol text-white text-sm">
|
||||||
New Chat
|
{t('newChat')}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} 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"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import {
|
import {
|
||||||
deletePromptById,
|
deletePromptById,
|
||||||
getAllPrompts,
|
getAllPrompts,
|
||||||
@ -25,6 +26,7 @@ export const PromptBody = () => {
|
|||||||
const [editId, setEditId] = useState("")
|
const [editId, setEditId] = useState("")
|
||||||
const [createForm] = Form.useForm()
|
const [createForm] = Form.useForm()
|
||||||
const [editForm] = Form.useForm()
|
const [editForm] = Form.useForm()
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const { data, status } = useQuery({
|
const { data, status } = useQuery({
|
||||||
queryKey: ["fetchAllPrompts"],
|
queryKey: ["fetchAllPrompts"],
|
||||||
@ -38,14 +40,14 @@ export const PromptBody = () => {
|
|||||||
queryKey: ["fetchAllPrompts"]
|
queryKey: ["fetchAllPrompts"]
|
||||||
})
|
})
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Model Deleted",
|
message: t("managePrompts.notification.deletedSuccess"),
|
||||||
description: "Model has been deleted successfully"
|
description: t("managePrompts.notification.deletedSuccessDesc")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("managePrompts.notification.error"),
|
||||||
description: error?.message || "Something went wrong"
|
description: error?.message || t("managePrompts.notification.someError")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -60,14 +62,15 @@ export const PromptBody = () => {
|
|||||||
setOpen(false)
|
setOpen(false)
|
||||||
createForm.resetFields()
|
createForm.resetFields()
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Prompt Added",
|
message: t("managePrompts.notification.addSuccess"),
|
||||||
description: "Prompt has been added successfully"
|
description: t("managePrompts.notification.addSuccessDesc")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("managePrompts.notification.error"),
|
||||||
description: error?.message || "Something went wrong"
|
description:
|
||||||
|
error?.message || t("managePrompts.notification.someError")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -87,14 +90,15 @@ export const PromptBody = () => {
|
|||||||
setOpenEdit(false)
|
setOpenEdit(false)
|
||||||
editForm.resetFields()
|
editForm.resetFields()
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Prompt Updated",
|
message: t("managePrompts.notification.updatedSuccess"),
|
||||||
description: "Prompt has been updated successfully"
|
description: t("managePrompts.notification.updatedSuccessDesc")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("managePrompts.notification.error"),
|
||||||
description: error?.message || "Something went wrong"
|
description:
|
||||||
|
error?.message || t("managePrompts.notification.someError")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -108,7 +112,7 @@ export const PromptBody = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="inline-flex items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 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 items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 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">
|
||||||
Add New Prompt
|
{t("managePrompts.addBtn")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -120,43 +124,41 @@ export const PromptBody = () => {
|
|||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Title",
|
title: t("managePrompts.columns.title"),
|
||||||
dataIndex: "title",
|
dataIndex: "title",
|
||||||
key: "title"
|
key: "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Prompt",
|
title: t("managePrompts.columns.prompt"),
|
||||||
dataIndex: "content",
|
dataIndex: "content",
|
||||||
key: "content"
|
key: "content"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Prompt Type",
|
title: t("managePrompts.columns.type"),
|
||||||
dataIndex: "is_system",
|
dataIndex: "is_system",
|
||||||
key: "is_system",
|
key: "is_system",
|
||||||
render: (is_system) =>
|
render: (is_system) =>
|
||||||
is_system ? (
|
is_system ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Computer className="w-5 h-5 " />
|
<Computer className="w-5 h-5 " />
|
||||||
System Prompt
|
{t("managePrompts.systemPrompt")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Zap className="w-5 h-5" />
|
<Zap className="w-5 h-5" />
|
||||||
Quick Prompt
|
{t("managePrompts.quickPrompt")}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Action",
|
title: t("managePrompts.columns.actions"),
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Tooltip title="Delete Prompt">
|
<Tooltip title={t("managePrompts.tooltip.delete")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(t("managePrompts.confirm.delete"))
|
||||||
"Are you sure you want to delete this prompt? This action cannot be undone."
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
deletePrompt(record.id)
|
deletePrompt(record.id)
|
||||||
}
|
}
|
||||||
@ -165,7 +167,7 @@ export const PromptBody = () => {
|
|||||||
<Trash2 className="w-5 h-5" />
|
<Trash2 className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Edit Prompt">
|
<Tooltip title={t("managePrompts.tooltip.edit")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditId(record.id)
|
setEditId(record.id)
|
||||||
@ -188,7 +190,7 @@ export const PromptBody = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Add New Prompt"
|
title={t("managePrompts.modal.addTitle")}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
footer={null}>
|
footer={null}>
|
||||||
@ -198,25 +200,35 @@ export const PromptBody = () => {
|
|||||||
form={createForm}>
|
form={createForm}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="title"
|
name="title"
|
||||||
label="Title"
|
label={t("managePrompts.form.title.label")}
|
||||||
rules={[{ required: true, message: "Title is required" }]}>
|
rules={[
|
||||||
<Input placeholder="My Awesome Prompt" />
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("managePrompts.form.title.required")
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<Input placeholder={t("managePrompts.form.title.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="content"
|
name="content"
|
||||||
label="Prompt"
|
label={t("managePrompts.form.prompt.label")}
|
||||||
rules={[{ required: true, message: "Prompt is required" }]}
|
rules={[
|
||||||
help="You can use {key} as variable in your prompt.">
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("managePrompts.form.prompt.required")
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
help={t("managePrompts.form.prompt.help")}>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
placeholder="Your prompt goes here..."
|
placeholder={t("managePrompts.form.prompt.placeholder")}
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="is_system"
|
name="is_system"
|
||||||
label="Is System Prompt"
|
label={t("managePrompts.form.isSystem.label")}
|
||||||
valuePropName="checked">
|
valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -225,14 +237,16 @@ export const PromptBody = () => {
|
|||||||
<button
|
<button
|
||||||
disabled={savePromptLoading}
|
disabled={savePromptLoading}
|
||||||
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 ">
|
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 ">
|
||||||
{savePromptLoading ? "Adding Prompt..." : "Add Prompt"}
|
{savePromptLoading
|
||||||
|
? t("managePrompts.form.btnSave.saving")
|
||||||
|
: t("managePrompts.form.btnSave.save")}
|
||||||
</button>
|
</button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Update Prompt"
|
title={t("managePrompts.modal.editTitle")}
|
||||||
open={openEdit}
|
open={openEdit}
|
||||||
onCancel={() => setOpenEdit(false)}
|
onCancel={() => setOpenEdit(false)}
|
||||||
footer={null}>
|
footer={null}>
|
||||||
@ -242,25 +256,35 @@ export const PromptBody = () => {
|
|||||||
form={editForm}>
|
form={editForm}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="title"
|
name="title"
|
||||||
label="Title"
|
label={t("managePrompts.form.title.label")}
|
||||||
rules={[{ required: true, message: "Title is required" }]}>
|
rules={[
|
||||||
<Input placeholder="My Awesome Prompt" />
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("managePrompts.form.title.required")
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<Input placeholder={t("managePrompts.form.title.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="content"
|
name="content"
|
||||||
label="Prompt"
|
label={t("managePrompts.form.prompt.label")}
|
||||||
rules={[{ required: true, message: "Prompt is required" }]}
|
rules={[
|
||||||
help="You can use {key} as variable in your prompt.">
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("managePrompts.form.prompt.required")
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
help={t("managePrompts.form.prompt.help")}>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
placeholder="Your prompt goes here..."
|
placeholder={t("managePrompts.form.prompt.placeholder")}
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="is_system"
|
name="is_system"
|
||||||
label="Is System Prompt"
|
label={t("managePrompts.form.isSystem.label")}
|
||||||
valuePropName="checked">
|
valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -269,7 +293,9 @@ export const PromptBody = () => {
|
|||||||
<button
|
<button
|
||||||
disabled={isUpdatingPrompt}
|
disabled={isUpdatingPrompt}
|
||||||
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 ">
|
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 ">
|
||||||
{isUpdatingPrompt ? "Updating Prompt..." : "Update Prompt"}
|
{isUpdatingPrompt
|
||||||
|
? t("managePrompts.form.btnEdit.saving")
|
||||||
|
: t("managePrompts.form.btnEdit.save")}
|
||||||
</button>
|
</button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -12,16 +12,19 @@ import {
|
|||||||
setOllamaURL as saveOllamaURL
|
setOllamaURL as saveOllamaURL
|
||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
import { SettingPrompt } from "./prompt"
|
import { SettingPrompt } from "./prompt"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
export const SettingsOllama = () => {
|
export const SettingsOllama = () => {
|
||||||
const [ollamaURL, setOllamaURL] = useState<string>("")
|
const [ollamaURL, setOllamaURL] = useState<string>("")
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const { data: ollamaInfo, status } = useQuery({
|
const { data: ollamaInfo, status } = useQuery({
|
||||||
queryKey: ["fetchOllamURL"],
|
queryKey: ["fetchOllamURL"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const [ollamaURL, allModels, chunkOverlap, chunkSize, defaultEM] =
|
const [ollamaURL, allModels, chunkOverlap, chunkSize, defaultEM] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getOllamaURL(),
|
getOllamaURL(),
|
||||||
getAllModels({returnEmpty: true}),
|
getAllModels({ returnEmpty: true }),
|
||||||
defaultEmbeddingChunkOverlap(),
|
defaultEmbeddingChunkOverlap(),
|
||||||
defaultEmbeddingChunkSize(),
|
defaultEmbeddingChunkSize(),
|
||||||
defaultEmbeddingModelForRag()
|
defaultEmbeddingModelForRag()
|
||||||
@ -54,7 +57,7 @@ export const SettingsOllama = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Configure Ollama
|
{t("ollamaSettings.heading")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -62,7 +65,7 @@ export const SettingsOllama = () => {
|
|||||||
<label
|
<label
|
||||||
htmlFor="ollamaURL"
|
htmlFor="ollamaURL"
|
||||||
className="text-sm font-medium dark:text-gray-200">
|
className="text-sm font-medium dark:text-gray-200">
|
||||||
Ollama URL
|
{t("ollamaSettings.settings.ollamaUrl.label")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
@ -71,7 +74,7 @@ export const SettingsOllama = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setOllamaURL(e.target.value)
|
setOllamaURL(e.target.value)
|
||||||
}}
|
}}
|
||||||
placeholder="Your Ollama URL"
|
placeholder={t("ollamaSettings.settings.ollamaUrl.placeholder")}
|
||||||
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -88,7 +91,7 @@ export const SettingsOllama = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Configure RAG
|
{t("ollamaSettings.settings.ragSettings.label")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,18 +111,26 @@ export const SettingsOllama = () => {
|
|||||||
}}>
|
}}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="defaultEM"
|
name="defaultEM"
|
||||||
label="Embedding Model"
|
label={t("ollamaSettings.settings.ragSettings.model.label")}
|
||||||
help="Highly recommended to use embedding models like `nomic-embed-text`."
|
help={t("ollamaSettings.settings.ragSettings.model.help")}
|
||||||
rules={[{ required: true, message: "Please select a model!" }]}>
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t(
|
||||||
|
"ollamaSettings.settings.ragSettings.model.required"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}>
|
||||||
<Select
|
<Select
|
||||||
size="large"
|
size="large"
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option.label.toLowerCase().indexOf(input.toLowerCase()) >=
|
option!.label.toLowerCase().indexOf(input.toLowerCase()) >=
|
||||||
0 ||
|
0 ||
|
||||||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option!.value.toLowerCase().indexOf(input.toLowerCase()) >=
|
||||||
|
0
|
||||||
}
|
}
|
||||||
showSearch
|
showSearch
|
||||||
placeholder="Select a model"
|
placeholder={t("ollamaSettings.settings.ragSettings.model.placeholder")}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
options={ollamaInfo.models?.map((model) => ({
|
options={ollamaInfo.models?.map((model) => ({
|
||||||
@ -131,27 +142,28 @@ export const SettingsOllama = () => {
|
|||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="chunkSize"
|
name="chunkSize"
|
||||||
label="Chunk Size"
|
label={t("ollamaSettings.settings.ragSettings.chunkSize.label")}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: "Please input your chunk size!" }
|
{ required: true, message: t("ollamaSettings.settings.ragSettings.chunkSize.required")
|
||||||
|
}
|
||||||
]}>
|
]}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
placeholder="Chunk Size"
|
placeholder={t("ollamaSettings.settings.ragSettings.chunkSize.placeholder")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="chunkOverlap"
|
name="chunkOverlap"
|
||||||
label="Chunk Overlap"
|
label={t("ollamaSettings.settings.ragSettings.chunkOverlap.label")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "Please input your chunk overlap!"
|
message: t("ollamaSettings.settings.ragSettings.chunkOverlap.required")
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
placeholder="Chunk Overlap"
|
placeholder={t("ollamaSettings.settings.ragSettings.chunkOverlap.placeholder")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -164,7 +176,7 @@ export const SettingsOllama = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Configure RAG Prompt
|
{t("ollamaSettings.settings.prompt.label")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { Select } from "antd"
|
|||||||
import { SUPPORTED_LANGUAGES } from "~/utils/supporetd-languages"
|
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"
|
||||||
|
|
||||||
export const SettingOther = () => {
|
export const SettingOther = () => {
|
||||||
const { clearChat, speechToTextLanguage, setSpeechToTextLanguage } =
|
const { clearChat, speechToTextLanguage, setSpeechToTextLanguage } =
|
||||||
@ -14,29 +15,31 @@ export const SettingOther = () => {
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const { mode, toggleDarkMode } = useDarkMode()
|
const { mode, toggleDarkMode } = useDarkMode()
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dl className="flex flex-col space-y-6">
|
<dl className="flex flex-col space-y-6 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Web UI Settings
|
{t("generalSettings.heading")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
|
||||||
</div>
|
</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">
|
||||||
Speech Recognition Language
|
{t("generalSettings.settings.speechRecognitionLang.label")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
placeholder="Select Language"
|
placeholder={t("generalSettings.settings.speechRecognitionLang.placeholder")}
|
||||||
allowClear
|
allowClear
|
||||||
showSearch
|
showSearch
|
||||||
options={SUPPORTED_LANGUAGES}
|
options={SUPPORTED_LANGUAGES}
|
||||||
value={speechToTextLanguage}
|
value={speechToTextLanguage}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
|
option!.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
|
||||||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option!.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}
|
}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSpeechToTextLanguage(value)
|
setSpeechToTextLanguage(value)
|
||||||
@ -44,7 +47,9 @@ export const SettingOther = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<span className="text-gray-500 dark:text-neutral-50 ">Change Theme</span>
|
<span className="text-gray-500 dark:text-neutral-50 ">
|
||||||
|
{t("generalSettings.settings.darkMode.label")}
|
||||||
|
</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleDarkMode}
|
onClick={toggleDarkMode}
|
||||||
@ -54,19 +59,19 @@ export const SettingOther = () => {
|
|||||||
) : (
|
) : (
|
||||||
<MoonIcon className="w-4 h-4 mr-2" />
|
<MoonIcon className="w-4 h-4 mr-2" />
|
||||||
)}
|
)}
|
||||||
{mode === "dark" ? "Light" : "Dark"}
|
{mode === "dark" ? t("generalSettings.settings.darkMode.options.light") : t("generalSettings.settings.darkMode.options.dark")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<SearchModeSettings />
|
<SearchModeSettings />
|
||||||
<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 ">
|
||||||
Delete Chat History
|
{t("generalSettings.settings.deleteChatHistory.label")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const confirm = window.confirm(
|
const confirm = window.confirm(
|
||||||
"Are you sure you want to delete your chat history? This action cannot be undone."
|
t("generalSettings.settings.deleteChatHistory.confirm")
|
||||||
)
|
)
|
||||||
|
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
@ -79,7 +84,7 @@ export const SettingOther = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="bg-red-500 dark:bg-red-600 text-white dark:text-gray-200 px-4 py-2 rounded-md">
|
className="bg-red-500 dark:bg-red-600 text-white dark:text-gray-200 px-4 py-2 rounded-md">
|
||||||
Delete
|
{t("generalSettings.settings.deleteChatHistory.button")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Skeleton, Radio, Form, Alert } from "antd"
|
import { Skeleton, Radio, Form, Alert } from "antd"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { SaveButton } from "~/components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import {
|
import {
|
||||||
getWebSearchPrompt,
|
getWebSearchPrompt,
|
||||||
@ -11,6 +12,8 @@ import {
|
|||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const SettingPrompt = () => {
|
export const SettingPrompt = () => {
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const [selectedValue, setSelectedValue] = React.useState<"normal" | "web">(
|
const [selectedValue, setSelectedValue] = React.useState<"normal" | "web">(
|
||||||
"web"
|
"web"
|
||||||
)
|
)
|
||||||
@ -45,8 +48,12 @@ export const SettingPrompt = () => {
|
|||||||
<Radio.Group
|
<Radio.Group
|
||||||
defaultValue={selectedValue}
|
defaultValue={selectedValue}
|
||||||
onChange={(e) => setSelectedValue(e.target.value)}>
|
onChange={(e) => setSelectedValue(e.target.value)}>
|
||||||
<Radio.Button value="normal">Normal</Radio.Button>
|
<Radio.Button value="normal">
|
||||||
<Radio.Button value="web">Web</Radio.Button>
|
{t("ollamaSettings.settings.prompt.option1")}
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value="web">
|
||||||
|
{t("ollamaSettings.settings.prompt.option2")}
|
||||||
|
</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -64,18 +71,22 @@ export const SettingPrompt = () => {
|
|||||||
}}>
|
}}>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Alert
|
<Alert
|
||||||
message="Configuring the system prompt here is deprecated. Please use the Manage Prompts section to add or edit prompts. This section will be removed in a future release"
|
message={t("ollamaSettings.settings.prompt.alert")}
|
||||||
type="warning"
|
type="warning"
|
||||||
showIcon
|
showIcon
|
||||||
closable
|
closable
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="System Prompt" name="prompt">
|
<Form.Item
|
||||||
|
label={t("ollamaSettings.settings.prompt.systemPrompt")}
|
||||||
|
name="prompt">
|
||||||
<textarea
|
<textarea
|
||||||
value={data.prompt}
|
value={data.prompt}
|
||||||
rows={5}
|
rows={5}
|
||||||
id="ollamaPrompt"
|
id="ollamaPrompt"
|
||||||
placeholder="Your System Prompt"
|
placeholder={t(
|
||||||
|
"ollamaSettings.settings.prompt.systemPromptPlaceholder"
|
||||||
|
)}
|
||||||
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -104,38 +115,42 @@ export const SettingPrompt = () => {
|
|||||||
webSearchFollowUpPrompt: data.webSearchFollowUpPrompt
|
webSearchFollowUpPrompt: data.webSearchFollowUpPrompt
|
||||||
}}>
|
}}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Web Search Prompt"
|
label={t("ollamaSettings.settings.prompt.webSearchPrompt")}
|
||||||
name="webSearchPrompt"
|
name="webSearchPrompt"
|
||||||
help="Do not remove `{search_results}` from the prompt."
|
help={t("ollamaSettings.settings.prompt.webSearchPromptHelp")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "Please input your Web Search Prompt!"
|
message: t(
|
||||||
|
"ollamaSettings.settings.prompt.webSearchPromptError"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<textarea
|
<textarea
|
||||||
value={data.webSearchPrompt}
|
value={data.webSearchPrompt}
|
||||||
rows={5}
|
rows={5}
|
||||||
id="ollamaWebSearchPrompt"
|
id="ollamaWebSearchPrompt"
|
||||||
placeholder="Your Web Search Prompt"
|
placeholder={t(
|
||||||
|
"ollamaSettings.settings.prompt.webSearchPromptPlaceholder"
|
||||||
|
)}
|
||||||
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Web Search Follow Up Prompt"
|
label={t("ollamaSettings.settings.prompt.webSearchFollowUpPrompt")}
|
||||||
name="webSearchFollowUpPrompt"
|
name="webSearchFollowUpPrompt"
|
||||||
help="Do not remove `{chat_history}` and `{question}` from the prompt."
|
help={t("ollamaSettings.settings.prompt.webSearchFollowUpPromptHelp")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "Please input your Web Search Follow Up Prompt!"
|
message: t("ollamaSettings.settings.prompt.webSearchFollowUpPromptError")
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<textarea
|
<textarea
|
||||||
value={data.webSearchFollowUpPrompt}
|
value={data.webSearchFollowUpPrompt}
|
||||||
rows={5}
|
rows={5}
|
||||||
id="ollamaWebSearchFollowUpPrompt"
|
id="ollamaWebSearchFollowUpPrompt"
|
||||||
placeholder="Your Web Search Follow Up Prompt"
|
placeholder={t("ollamaSettings.settings.prompt.webSearchFollowUpPromptPlaceholder")}
|
||||||
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Skeleton, Switch } from "antd"
|
import { Skeleton, Switch } from "antd"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import {
|
import {
|
||||||
getIsSimpleInternetSearch,
|
getIsSimpleInternetSearch,
|
||||||
setIsSimpleInternetSearch
|
setIsSimpleInternetSearch
|
||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const SearchModeSettings = () => {
|
export const SearchModeSettings = () => {
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const { data, status } = useQuery({
|
const { data, status } = useQuery({
|
||||||
queryKey: ["fetchIsSimpleInternetSearch"],
|
queryKey: ["fetchIsSimpleInternetSearch"],
|
||||||
queryFn: () => getIsSimpleInternetSearch()
|
queryFn: () => getIsSimpleInternetSearch()
|
||||||
@ -20,7 +23,7 @@ export const SearchModeSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<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 ">
|
||||||
Perform Simple Internet Search
|
{t("generalSettings.settings.searchMode.label")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Form, Input, Skeleton, Table, Tooltip, message } from "antd"
|
import { Form, Input, Skeleton, Table, Tooltip, message } from "antd"
|
||||||
import { Trash2 } from "lucide-react"
|
import { Trash2 } from "lucide-react"
|
||||||
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
import { SaveButton } from "~/components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import { deleteWebshare, getAllWebshares, getUserId } from "~/libs/db"
|
import { deleteWebshare, getAllWebshares, getUserId } from "~/libs/db"
|
||||||
import { getPageShareUrl, setPageShareUrl } from "~/services/ollama"
|
import { getPageShareUrl, setPageShareUrl } from "~/services/ollama"
|
||||||
@ -8,6 +9,8 @@ import { verifyPageShareURL } from "~/utils/verify-page-share"
|
|||||||
|
|
||||||
export const OptionShareBody = () => {
|
export const OptionShareBody = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ["fetchShareInfo"],
|
queryKey: ["fetchShareInfo"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -58,10 +61,10 @@ export const OptionShareBody = () => {
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["fetchShareInfo"]
|
queryKey: ["fetchShareInfo"]
|
||||||
})
|
})
|
||||||
message.success("Page Share URL updated successfully")
|
message.success(t("manageShare.notification.pageShareSuccess"))
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
message.error(error?.message || "Failed to update Page Share URL")
|
message.error(error?.message || t("manageShare.notification.someError"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -69,12 +72,16 @@ export const OptionShareBody = () => {
|
|||||||
mutationFn: onDelete,
|
mutationFn: onDelete,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["fetchShareInfo"]
|
queryKey: ["fetchShareInfo"],
|
||||||
})
|
})
|
||||||
message.success("Webshare deleted successfully")
|
message.success(
|
||||||
|
t("manageShare.notification.webShareDeleteSuccess")
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
message.error(error?.message || "Failed to delete Webshare")
|
message.error(
|
||||||
|
error?.message || t("manageShare.notification.someError")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,7 +93,7 @@ export const OptionShareBody = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Configure Page Share URL
|
{t("manageShare.heading")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -99,25 +106,29 @@ export const OptionShareBody = () => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="url"
|
name="url"
|
||||||
help={
|
help={
|
||||||
<span>
|
<Trans
|
||||||
For privacy reasons, you can self-host the page share and
|
i18nKey="option:manageShare.form.url.help"
|
||||||
provide the URL here.{" "}
|
components={{
|
||||||
<a
|
anchor: (
|
||||||
href="https://github.com/n4ze3m/page-assist/blob/main/page-share.md"
|
<a
|
||||||
target="__blank"
|
href="https://github.com/n4ze3m/page-assist/blob/main/page-share.md"
|
||||||
className="text-blue-600 dark:text-blue-400">
|
target="__blank"
|
||||||
Learn more
|
className="text-blue-600 dark:text-blue-400"></a>
|
||||||
</a>
|
)
|
||||||
</span>
|
}}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "Please input your Page Share URL!"
|
message: t("manageShare.form.url.required")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
label="Page Share URL">
|
label={t("manageShare.form.url.label")}>
|
||||||
<Input placeholder="Page Share URL" size="large" />
|
<Input
|
||||||
|
placeholder={t("manageShare.form.url.placeholder")}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
@ -129,7 +140,7 @@ export const OptionShareBody = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
Webshares
|
{t("manageShare.webshare.heading")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -138,12 +149,12 @@ export const OptionShareBody = () => {
|
|||||||
dataSource={data.shares}
|
dataSource={data.shares}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Title",
|
title: t("manageShare.webshare.columns.title"),
|
||||||
dataIndex: "title",
|
dataIndex: "title",
|
||||||
key: "title"
|
key: "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "URL",
|
title: t("manageShare.webshare.columns.url"),
|
||||||
dataIndex: "url",
|
dataIndex: "url",
|
||||||
key: "url",
|
key: "url",
|
||||||
render: (url: string) => (
|
render: (url: string) => (
|
||||||
@ -156,14 +167,14 @@ export const OptionShareBody = () => {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Actions",
|
title: t("manageShare.webshare.columns.actions"),
|
||||||
render: (_, render) => (
|
render: (_, render) => (
|
||||||
<Tooltip title="Delete Share">
|
<Tooltip title={t("manageShare.webshare.tooltip.delete")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(
|
||||||
"Are you sure you want to delete this webshare?"
|
t("manageShare.webshare.confirm.delete")
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
deleteMutation({
|
deleteMutation({
|
||||||
|
@ -7,6 +7,8 @@ import { ConfigProvider, 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"
|
||||||
|
|
||||||
function IndexOption() {
|
function IndexOption() {
|
||||||
const { mode } = useDarkMode()
|
const { mode } = useDarkMode()
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page Assist - Web UI</title>
|
<title>Page Assist - A Web UI for Local AI Models</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
||||||
|
@ -7,6 +7,8 @@ const queryClient = new QueryClient()
|
|||||||
import { ConfigProvider, theme } from "antd"
|
import { ConfigProvider, 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"
|
||||||
|
|
||||||
function IndexSidepanel() {
|
function IndexSidepanel() {
|
||||||
const { mode } = useDarkMode()
|
const { mode } = useDarkMode()
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page Assist - Web UI</title>
|
<title>Page Assist - A Web UI for Local AI Models</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
||||||
|
@ -7,12 +7,7 @@ import {
|
|||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
import { type ChatHistory, type Message } from "~/store/option"
|
import { type ChatHistory, type Message } from "~/store/option"
|
||||||
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
||||||
import {
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages"
|
||||||
HumanMessage,
|
|
||||||
AIMessage,
|
|
||||||
type MessageContent,
|
|
||||||
SystemMessage
|
|
||||||
} from "@langchain/core/messages"
|
|
||||||
import { useStoreMessageOption } from "~/store/option"
|
import { useStoreMessageOption } from "~/store/option"
|
||||||
import {
|
import {
|
||||||
deleteChatForEdit,
|
deleteChatForEdit,
|
||||||
@ -25,65 +20,8 @@ import {
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { notification } from "antd"
|
import { notification } from "antd"
|
||||||
import { getSystemPromptForWeb } from "~/web/web"
|
import { getSystemPromptForWeb } from "~/web/web"
|
||||||
|
import { generateHistory } from "@/utils/generate-history"
|
||||||
export type BotResponse = {
|
import { useTranslation } from "react-i18next"
|
||||||
bot: {
|
|
||||||
text: string
|
|
||||||
sourceDocuments: any[]
|
|
||||||
}
|
|
||||||
history: ChatHistory
|
|
||||||
history_id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateHistory = (
|
|
||||||
messages: {
|
|
||||||
role: "user" | "assistant" | "system"
|
|
||||||
content: string
|
|
||||||
image?: string
|
|
||||||
}[]
|
|
||||||
) => {
|
|
||||||
let history = []
|
|
||||||
for (const message of messages) {
|
|
||||||
if (message.role === "user") {
|
|
||||||
let content: MessageContent = [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message.content
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
if (message.image) {
|
|
||||||
content = [
|
|
||||||
{
|
|
||||||
type: "image_url",
|
|
||||||
image_url: message.image
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message.content
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
history.push(
|
|
||||||
new HumanMessage({
|
|
||||||
content: content
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else if (message.role === "assistant") {
|
|
||||||
history.push(
|
|
||||||
new AIMessage({
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message.content
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return history
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMessageOption = () => {
|
export const useMessageOption = () => {
|
||||||
const {
|
const {
|
||||||
@ -116,7 +54,7 @@ export const useMessageOption = () => {
|
|||||||
setSelectedSystemPrompt
|
setSelectedSystemPrompt
|
||||||
} = useStoreMessageOption()
|
} = useStoreMessageOption()
|
||||||
|
|
||||||
// const { notification } = App.useApp()
|
const { t } = useTranslation("option")
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
||||||
@ -150,7 +88,7 @@ export const useMessageOption = () => {
|
|||||||
abortControllerRef.current = new AbortController()
|
abortControllerRef.current = new AbortController()
|
||||||
|
|
||||||
const ollama = new ChatOllama({
|
const ollama = new ChatOllama({
|
||||||
model: selectedModel,
|
model: selectedModel!,
|
||||||
baseUrl: cleanUrl(url)
|
baseUrl: cleanUrl(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -204,7 +142,7 @@ export const useMessageOption = () => {
|
|||||||
.replaceAll("{chat_history}", chat_history)
|
.replaceAll("{chat_history}", chat_history)
|
||||||
.replaceAll("{question}", message)
|
.replaceAll("{question}", message)
|
||||||
const questionOllama = new ChatOllama({
|
const questionOllama = new ChatOllama({
|
||||||
model: selectedModel,
|
model: selectedModel!,
|
||||||
baseUrl: cleanUrl(url)
|
baseUrl: cleanUrl(url)
|
||||||
})
|
})
|
||||||
const response = await questionOllama.invoke(promptForQuestion)
|
const response = await questionOllama.invoke(promptForQuestion)
|
||||||
@ -308,11 +246,11 @@ export const useMessageOption = () => {
|
|||||||
|
|
||||||
if (historyId) {
|
if (historyId) {
|
||||||
if (!isRegenerate) {
|
if (!isRegenerate) {
|
||||||
await saveMessage(historyId, selectedModel, "user", message, [image])
|
await saveMessage(historyId, selectedModel!, "user", message, [image])
|
||||||
}
|
}
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
historyId,
|
historyId,
|
||||||
selectedModel,
|
selectedModel!,
|
||||||
"assistant",
|
"assistant",
|
||||||
newMessage[appendingIndex].message,
|
newMessage[appendingIndex].message,
|
||||||
[],
|
[],
|
||||||
@ -320,12 +258,12 @@ export const useMessageOption = () => {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const newHistoryId = await saveHistory(message)
|
const newHistoryId = await saveHistory(message)
|
||||||
await saveMessage(newHistoryId.id, selectedModel, "user", message, [
|
await saveMessage(newHistoryId.id, selectedModel!, "user", message, [
|
||||||
image
|
image
|
||||||
])
|
])
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
newHistoryId.id,
|
newHistoryId.id,
|
||||||
selectedModel,
|
selectedModel!,
|
||||||
"assistant",
|
"assistant",
|
||||||
newMessage[appendingIndex].message,
|
newMessage[appendingIndex].message,
|
||||||
[],
|
[],
|
||||||
@ -337,6 +275,7 @@ export const useMessageOption = () => {
|
|||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
setStreaming(false)
|
setStreaming(false)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
//@ts-ignore
|
||||||
if (e?.name === "AbortError") {
|
if (e?.name === "AbortError") {
|
||||||
newMessage[appendingIndex].message = newMessage[
|
newMessage[appendingIndex].message = newMessage[
|
||||||
appendingIndex
|
appendingIndex
|
||||||
@ -356,22 +295,22 @@ export const useMessageOption = () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
if (historyId) {
|
if (historyId) {
|
||||||
await saveMessage(historyId, selectedModel, "user", message, [image])
|
await saveMessage(historyId, selectedModel!, "user", message, [image])
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
historyId,
|
historyId,
|
||||||
selectedModel,
|
selectedModel!,
|
||||||
"assistant",
|
"assistant",
|
||||||
newMessage[appendingIndex].message,
|
newMessage[appendingIndex].message,
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const newHistoryId = await saveHistory(message)
|
const newHistoryId = await saveHistory(message)
|
||||||
await saveMessage(newHistoryId.id, selectedModel, "user", message, [
|
await saveMessage(newHistoryId.id, selectedModel!, "user", message, [
|
||||||
image
|
image
|
||||||
])
|
])
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
newHistoryId.id,
|
newHistoryId.id,
|
||||||
selectedModel,
|
selectedModel!,
|
||||||
"assistant",
|
"assistant",
|
||||||
newMessage[appendingIndex].message,
|
newMessage[appendingIndex].message,
|
||||||
[]
|
[]
|
||||||
@ -379,9 +318,10 @@ export const useMessageOption = () => {
|
|||||||
setHistoryId(newHistoryId.id)
|
setHistoryId(newHistoryId.id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//@ts-ignore
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("error"),
|
||||||
description: e?.message || "Something went wrong"
|
description: e?.message || t("somethingWentWrong")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +345,7 @@ export const useMessageOption = () => {
|
|||||||
abortControllerRef.current = new AbortController()
|
abortControllerRef.current = new AbortController()
|
||||||
|
|
||||||
const ollama = new ChatOllama({
|
const ollama = new ChatOllama({
|
||||||
model: selectedModel,
|
model: selectedModel!,
|
||||||
baseUrl: cleanUrl(url)
|
baseUrl: cleanUrl(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -620,8 +560,8 @@ export const useMessageOption = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("error"),
|
||||||
description: e?.message || "Something went wrong"
|
description: e?.message || t("somethingWentWrong")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,8 +639,8 @@ export const useMessageOption = () => {
|
|||||||
const validateBeforeSubmit = () => {
|
const validateBeforeSubmit = () => {
|
||||||
if (!selectedModel || selectedModel?.trim()?.length === 0) {
|
if (!selectedModel || selectedModel?.trim()?.length === 0) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: t("error"),
|
||||||
description: "Please select a model to continue"
|
description: t("validationSelectModel")
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
18
src/i18n/index.ts
Normal file
18
src/i18n/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import { en } from "./lang/en";
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
debug: true,
|
||||||
|
resources: {
|
||||||
|
en: en
|
||||||
|
},
|
||||||
|
fallbackLng: "en",
|
||||||
|
lng: localStorage.getItem("i18nextLng") || "en",
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n;
|
6
src/i18n/lang/en.ts
Normal file
6
src/i18n/lang/en.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import option from "@/assets/locale/en/option.json";
|
||||||
|
|
||||||
|
|
||||||
|
export const en = {
|
||||||
|
option
|
||||||
|
}
|
8
src/public/_locales/en/messages.json
Normal file
8
src/public/_locales/en/messages.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Page Assist - A Web UI for Local AI Models"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "Use your locally running AI models to assist you in your web browsing."
|
||||||
|
}
|
||||||
|
}
|
@ -62,31 +62,36 @@ export const isOllamaRunning = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getAllModels = async ({ returnEmpty = false }: { returnEmpty?: boolean }) => {
|
export const getAllModels = async ({ returnEmpty = false }: { returnEmpty?: boolean }) => {
|
||||||
const baseUrl = await getOllamaURL()
|
try {
|
||||||
const response = await fetch(`${cleanUrl(baseUrl)}/api/tags`)
|
const baseUrl = await getOllamaURL()
|
||||||
if (!response.ok) {
|
const response = await fetch(`${cleanUrl(baseUrl)}/api/tags`)
|
||||||
if (returnEmpty) {
|
if (!response.ok) {
|
||||||
return []
|
if (returnEmpty) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
throw new Error(response.statusText)
|
||||||
}
|
}
|
||||||
throw new Error(response.statusText)
|
const json = await response.json()
|
||||||
}
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
return json.models as {
|
return json.models as {
|
||||||
name: string
|
name: string
|
||||||
model: string
|
model: string
|
||||||
modified_at: string
|
modified_at: string
|
||||||
size: number
|
size: number
|
||||||
digest: string
|
digest: string
|
||||||
details: {
|
details: {
|
||||||
parent_model: string
|
parent_model: string
|
||||||
format: string
|
format: string
|
||||||
family: string
|
family: string
|
||||||
families: string[]
|
families: string[]
|
||||||
parameter_size: string
|
parameter_size: string
|
||||||
quantization_level: string
|
quantization_level: string
|
||||||
}
|
}
|
||||||
}[]
|
}[]
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteModel = async (model: string) => {
|
export const deleteModel = async (model: string) => {
|
||||||
|
10
src/types/index.ts
Normal file
10
src/types/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ChatHistory } from "@/store"
|
||||||
|
|
||||||
|
export type BotResponse = {
|
||||||
|
bot: {
|
||||||
|
text: string
|
||||||
|
sourceDocuments: any[]
|
||||||
|
}
|
||||||
|
history: ChatHistory
|
||||||
|
history_id: string
|
||||||
|
}
|
55
src/utils/generate-history.ts
Normal file
55
src/utils/generate-history.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
HumanMessage,
|
||||||
|
AIMessage,
|
||||||
|
type MessageContent,
|
||||||
|
} from "@langchain/core/messages"
|
||||||
|
|
||||||
|
export const generateHistory = (
|
||||||
|
messages: {
|
||||||
|
role: "user" | "assistant" | "system"
|
||||||
|
content: string
|
||||||
|
image?: string
|
||||||
|
}[]
|
||||||
|
) => {
|
||||||
|
let history = []
|
||||||
|
for (const message of messages) {
|
||||||
|
if (message.role === "user") {
|
||||||
|
let content: MessageContent = [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: message.content
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (message.image) {
|
||||||
|
content = [
|
||||||
|
{
|
||||||
|
type: "image_url",
|
||||||
|
image_url: message.image
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: message.content
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
history.push(
|
||||||
|
new HumanMessage({
|
||||||
|
content: content
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else if (message.role === "assistant") {
|
||||||
|
history.push(
|
||||||
|
new AIMessage({
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: message.content
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return history
|
||||||
|
}
|
@ -5,7 +5,8 @@
|
|||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx",
|
||||||
|
"strict": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
@ -10,10 +10,10 @@ export default defineConfig({
|
|||||||
srcDir: "src",
|
srcDir: "src",
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "Page Assist - A Web UI for Local AI Models",
|
|
||||||
version: "1.1.0",
|
version: "1.1.0",
|
||||||
description:
|
name: '__MSG_extName__',
|
||||||
"Use your locally running AI models to assist you in your web browsing.",
|
description: '__MSG_extDescription__',
|
||||||
|
default_locale: 'en',
|
||||||
action: {},
|
action: {},
|
||||||
author: "n4ze3m",
|
author: "n4ze3m",
|
||||||
host_permissions: ["http://*/*", "https://*/*"],
|
host_permissions: ["http://*/*", "https://*/*"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user