496 lines
16 KiB
TypeScript
496 lines
16 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
|
import {
|
|
Skeleton,
|
|
Table,
|
|
Tooltip,
|
|
notification,
|
|
Modal,
|
|
Input,
|
|
Form,
|
|
Switch,
|
|
Segmented,
|
|
Tag
|
|
} from "antd"
|
|
import { Trash2, Pen, Computer, Zap } from "lucide-react"
|
|
import { useState } from "react"
|
|
import { useTranslation } from "react-i18next"
|
|
import { deletePromptById, getAllPrompts, savePrompt, updatePrompt } from "@/db"
|
|
import {
|
|
getAllCopilotPrompts,
|
|
setAllCopilotPrompts
|
|
} from "@/services/application"
|
|
import { tagColors } from "@/utils/color"
|
|
|
|
export const PromptBody = () => {
|
|
const queryClient = useQueryClient()
|
|
const [open, setOpen] = useState(false)
|
|
const [openEdit, setOpenEdit] = useState(false)
|
|
const [editId, setEditId] = useState("")
|
|
const [createForm] = Form.useForm()
|
|
const [editForm] = Form.useForm()
|
|
const { t } = useTranslation(["settings", "common"])
|
|
const [selectedSegment, setSelectedSegment] = useState<"custom" | "copilot">(
|
|
"custom"
|
|
)
|
|
|
|
const [openCopilotEdit, setOpenCopilotEdit] = useState(false)
|
|
const [editCopilotId, setEditCopilotId] = useState("")
|
|
const [editCopilotForm] = Form.useForm()
|
|
|
|
const { data, status } = useQuery({
|
|
queryKey: ["fetchAllPrompts"],
|
|
queryFn: getAllPrompts
|
|
})
|
|
|
|
const { data: copilotData, status: copilotStatus } = useQuery({
|
|
queryKey: ["fetchCopilotPrompts"],
|
|
queryFn: getAllCopilotPrompts
|
|
})
|
|
|
|
const { mutate: deletePrompt } = useMutation({
|
|
mutationFn: deletePromptById,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["fetchAllPrompts"]
|
|
})
|
|
notification.success({
|
|
message: t("managePrompts.notification.deletedSuccess"),
|
|
description: t("managePrompts.notification.deletedSuccessDesc")
|
|
})
|
|
},
|
|
onError: (error) => {
|
|
notification.error({
|
|
message: t("managePrompts.notification.error"),
|
|
description: error?.message || t("managePrompts.notification.someError")
|
|
})
|
|
}
|
|
})
|
|
|
|
const { mutate: savePromptMutation, isPending: savePromptLoading } =
|
|
useMutation({
|
|
mutationFn: savePrompt,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["fetchAllPrompts"]
|
|
})
|
|
setOpen(false)
|
|
createForm.resetFields()
|
|
notification.success({
|
|
message: t("managePrompts.notification.addSuccess"),
|
|
description: t("managePrompts.notification.addSuccessDesc")
|
|
})
|
|
},
|
|
onError: (error) => {
|
|
notification.error({
|
|
message: t("managePrompts.notification.error"),
|
|
description:
|
|
error?.message || t("managePrompts.notification.someError")
|
|
})
|
|
}
|
|
})
|
|
|
|
const { mutate: updatePromptMutation, isPending: isUpdatingPrompt } =
|
|
useMutation({
|
|
mutationFn: async (data: any) => {
|
|
return await updatePrompt({
|
|
...data,
|
|
id: editId
|
|
})
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["fetchAllPrompts"]
|
|
})
|
|
setOpenEdit(false)
|
|
editForm.resetFields()
|
|
notification.success({
|
|
message: t("managePrompts.notification.updatedSuccess"),
|
|
description: t("managePrompts.notification.updatedSuccessDesc")
|
|
})
|
|
},
|
|
onError: (error) => {
|
|
notification.error({
|
|
message: t("managePrompts.notification.error"),
|
|
description:
|
|
error?.message || t("managePrompts.notification.someError")
|
|
})
|
|
}
|
|
})
|
|
|
|
const { mutate: updateCopilotPrompt, isPending: isUpdatingCopilotPrompt } =
|
|
useMutation({
|
|
mutationFn: async (data: any) => {
|
|
return await setAllCopilotPrompts([
|
|
{
|
|
key: data.key,
|
|
prompt: data.prompt
|
|
}
|
|
])
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["fetchCopilotPrompts"]
|
|
})
|
|
setOpenCopilotEdit(false)
|
|
editCopilotForm.resetFields()
|
|
notification.success({
|
|
message: t("managePrompts.notification.updatedSuccess"),
|
|
description: t("managePrompts.notification.updatedSuccessDesc")
|
|
})
|
|
},
|
|
onError: (error) => {
|
|
notification.error({
|
|
message: t("managePrompts.notification.error"),
|
|
description:
|
|
error?.message || t("managePrompts.notification.someError")
|
|
})
|
|
}
|
|
})
|
|
|
|
function customPrompts() {
|
|
return (
|
|
<div>
|
|
<div className="mb-6">
|
|
<div className="-ml-4 -mt-2 flex flex-wrap items-center justify-end sm:flex-nowrap">
|
|
<div className="ml-4 mt-2 flex-shrink-0">
|
|
<button
|
|
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">
|
|
{t("managePrompts.addBtn")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{status === "pending" && <Skeleton paragraph={{ rows: 8 }} />}
|
|
|
|
{status === "success" && (
|
|
<Table
|
|
columns={[
|
|
{
|
|
title: t("managePrompts.columns.title"),
|
|
dataIndex: "title",
|
|
key: "title",
|
|
render: (content) => (
|
|
<span className="line-clamp-1">{content}</span>
|
|
)
|
|
},
|
|
{
|
|
title: t("managePrompts.columns.prompt"),
|
|
dataIndex: "content",
|
|
key: "content",
|
|
render: (content) => (
|
|
<span className="line-clamp-1">{content}</span>
|
|
)
|
|
},
|
|
{
|
|
title: t("managePrompts.columns.type"),
|
|
dataIndex: "is_system",
|
|
key: "is_system",
|
|
render: (is_system) => (
|
|
<span className="flex items-center gap-2 text-xs w-32">
|
|
{is_system ? (
|
|
<>
|
|
<Computer className="size-4" />{" "}
|
|
{t("managePrompts.systemPrompt")}
|
|
</>
|
|
) : (
|
|
<>
|
|
<Zap className="size-4" />{" "}
|
|
{t("managePrompts.quickPrompt")}
|
|
</>
|
|
)}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: t("managePrompts.columns.actions"),
|
|
render: (_, record) => (
|
|
<div className="flex gap-4">
|
|
<Tooltip title={t("managePrompts.tooltip.delete")}>
|
|
<button
|
|
onClick={() => {
|
|
if (
|
|
window.confirm(t("managePrompts.confirm.delete"))
|
|
) {
|
|
deletePrompt(record.id)
|
|
}
|
|
}}
|
|
className="text-red-500 dark:text-red-400">
|
|
<Trash2 className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
<Tooltip title={t("managePrompts.tooltip.edit")}>
|
|
<button
|
|
onClick={() => {
|
|
setEditId(record.id)
|
|
editForm.setFieldsValue(record)
|
|
setOpenEdit(true)
|
|
}}
|
|
className="text-gray-500 dark:text-gray-400">
|
|
<Pen className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
</div>
|
|
)
|
|
}
|
|
]}
|
|
bordered
|
|
dataSource={data}
|
|
rowKey={(record) => record.id}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function copilotPrompts() {
|
|
return (
|
|
<div>
|
|
{copilotStatus === "pending" && <Skeleton paragraph={{ rows: 8 }} />}
|
|
|
|
{copilotStatus === "success" && (
|
|
<Table
|
|
columns={[
|
|
{
|
|
title: t("managePrompts.columns.title"),
|
|
dataIndex: "key",
|
|
key: "key",
|
|
render: (content) => (
|
|
<span className="line-clamp-1">
|
|
<Tag color={tagColors[content || "default"]}>
|
|
{t(`common:copilot.${content}`)}
|
|
</Tag>
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: t("managePrompts.columns.prompt"),
|
|
dataIndex: "prompt",
|
|
key: "prompt",
|
|
render: (content) => (
|
|
<span className="line-clamp-1">{content}</span>
|
|
)
|
|
},
|
|
{
|
|
render: (_, record) => (
|
|
<div className="flex gap-4">
|
|
<Tooltip title={t("managePrompts.tooltip.edit")}>
|
|
<button
|
|
onClick={() => {
|
|
setEditCopilotId(record.key)
|
|
editCopilotForm.setFieldsValue(record)
|
|
setOpenCopilotEdit(true)
|
|
}}
|
|
className="text-gray-500 dark:text-gray-400">
|
|
<Pen className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
</div>
|
|
)
|
|
}
|
|
]}
|
|
bordered
|
|
dataSource={copilotData}
|
|
rowKey={(record) => record.key}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex items-center justify-end mb-6">
|
|
<Segmented
|
|
size="large"
|
|
options={[
|
|
{
|
|
label: t(
|
|
"managePrompts.segmented.custom"
|
|
),
|
|
value: "custom"
|
|
},
|
|
{
|
|
label: t(
|
|
"managePrompts.segmented.copilot"
|
|
),
|
|
value: "copilot"
|
|
}
|
|
]}
|
|
onChange={(value) => {
|
|
setSelectedSegment(value as "custom" | "copilot")
|
|
}}
|
|
/>
|
|
</div>
|
|
{selectedSegment === "custom" && customPrompts()}
|
|
{selectedSegment === "copilot" && copilotPrompts()}
|
|
|
|
<Modal
|
|
title={t("managePrompts.modal.addTitle")}
|
|
open={open}
|
|
onCancel={() => setOpen(false)}
|
|
footer={null}>
|
|
<Form
|
|
onFinish={(values) => savePromptMutation(values)}
|
|
layout="vertical"
|
|
form={createForm}>
|
|
<Form.Item
|
|
name="title"
|
|
label={t("managePrompts.form.title.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("managePrompts.form.title.required")
|
|
}
|
|
]}>
|
|
<Input placeholder={t("managePrompts.form.title.placeholder")} />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="content"
|
|
label={t("managePrompts.form.prompt.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("managePrompts.form.prompt.required")
|
|
}
|
|
]}
|
|
help={t("managePrompts.form.prompt.help")}>
|
|
<Input.TextArea
|
|
placeholder={t("managePrompts.form.prompt.placeholder")}
|
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="is_system"
|
|
label={t("managePrompts.form.isSystem.label")}
|
|
valuePropName="checked">
|
|
<Switch />
|
|
</Form.Item>
|
|
|
|
<Form.Item>
|
|
<button
|
|
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 ">
|
|
{savePromptLoading
|
|
? t("managePrompts.form.btnSave.saving")
|
|
: t("managePrompts.form.btnSave.save")}
|
|
</button>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title={t("managePrompts.modal.editTitle")}
|
|
open={openEdit}
|
|
onCancel={() => setOpenEdit(false)}
|
|
footer={null}>
|
|
<Form
|
|
onFinish={(values) => updatePromptMutation(values)}
|
|
layout="vertical"
|
|
form={editForm}>
|
|
<Form.Item
|
|
name="title"
|
|
label={t("managePrompts.form.title.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("managePrompts.form.title.required")
|
|
}
|
|
]}>
|
|
<Input placeholder={t("managePrompts.form.title.placeholder")} />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="content"
|
|
label={t("managePrompts.form.prompt.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("managePrompts.form.prompt.required")
|
|
}
|
|
]}
|
|
help={t("managePrompts.form.prompt.help")}>
|
|
<Input.TextArea
|
|
placeholder={t("managePrompts.form.prompt.placeholder")}
|
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="is_system"
|
|
label={t("managePrompts.form.isSystem.label")}
|
|
valuePropName="checked">
|
|
<Switch />
|
|
</Form.Item>
|
|
|
|
<Form.Item>
|
|
<button
|
|
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 ">
|
|
{isUpdatingPrompt
|
|
? t("managePrompts.form.btnEdit.saving")
|
|
: t("managePrompts.form.btnEdit.save")}
|
|
</button>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title={t("managePrompts.modal.editTitle")}
|
|
open={openCopilotEdit}
|
|
onCancel={() => setOpenCopilotEdit(false)}
|
|
footer={null}>
|
|
<Form
|
|
onFinish={(values) =>
|
|
updateCopilotPrompt({
|
|
key: editCopilotId,
|
|
prompt: values.prompt
|
|
})
|
|
}
|
|
layout="vertical"
|
|
form={editCopilotForm}>
|
|
<Form.Item
|
|
name="prompt"
|
|
label={t("managePrompts.form.prompt.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("managePrompts.form.prompt.required")
|
|
},
|
|
{
|
|
validator: (_, value) => {
|
|
if (value && value.includes("{text}")) {
|
|
return Promise.resolve()
|
|
}
|
|
return Promise.reject(
|
|
new Error(
|
|
t("managePrompts.form.prompt.missingTextPlaceholder")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
]}>
|
|
<Input.TextArea
|
|
placeholder={t("managePrompts.form.prompt.placeholder")}
|
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item>
|
|
<button
|
|
disabled={isUpdatingCopilotPrompt}
|
|
className="inline-flex justify-center w-full text-center mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
|
|
{isUpdatingCopilotPrompt
|
|
? t("managePrompts.form.btnEdit.saving")
|
|
: t("managePrompts.form.btnEdit.save")}
|
|
</button>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|