feat(web): add prompt menu
This commit is contained in:
82
web/src/views/Prompt/components/PromptDetail.tsx
Normal file
82
web/src/views/Prompt/components/PromptDetail.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Flex, Button, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
import type { HistoryItem, PromptDetailRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
|
||||
const PromptDetail = forwardRef<PromptDetailRef, { handleEdit: (item: HistoryItem) => void; handleDelete: (item: HistoryItem) => void; }>(({ handleEdit, handleDelete }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [data, setData] = useState<HistoryItem>({} as HistoryItem)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (vo: HistoryItem) => {
|
||||
setVisible(true);
|
||||
setData(vo)
|
||||
};
|
||||
const handleCopy = (text = '') => {
|
||||
copy(text)
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
return (
|
||||
<RbModal
|
||||
title={<div>
|
||||
{data.title}
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-normal rb:mt-1!">{formatDateTime(data.created_at)}</div>
|
||||
</div>}
|
||||
open={visible}
|
||||
footer={
|
||||
<Flex justify="end" gap={8}>
|
||||
<Button danger onClick={() => handleDelete(data)}>{t('common.delete')}</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
handleClose()
|
||||
handleEdit(data)
|
||||
}}>{t('common.edit')}</Button>
|
||||
</Flex>
|
||||
}
|
||||
onCancel={handleClose}
|
||||
width={1000}
|
||||
>
|
||||
<Flex justify="space-between">
|
||||
{t('prompt.initialInput')}
|
||||
<Button className="rb:group" size="small" disabled={!data.first_message || data.first_message.trim() === ''} onClick={() => handleCopy(data.first_message)}>
|
||||
<div
|
||||
className="rb:w-4 rb:h-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
||||
></div>
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<div className="rb:my-3 rb:bg-[#F6F8FC] rb:border-[#DFE4ED] rb:rounded-lg rb:p-3">
|
||||
<Markdown content={data.first_message} className="rb:min-h-5 rb:max-h-50 rb:overflow-y-auto" />
|
||||
</div>
|
||||
|
||||
<Flex justify="space-between">
|
||||
{t('prompt.conversationOptimizationPrompt')}
|
||||
<Button className="rb:group" size="small" onClick={() => handleCopy(data.prompt)}>
|
||||
<div
|
||||
className="rb:w-4 rb:h-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
||||
></div>
|
||||
</Button>
|
||||
</Flex>
|
||||
<div className="rb:relative rb:my-3 rb:overflow-hidden rb:bg-[#F6F8FC] rb:border-[#DFE4ED] rb:rounded-lg rb:p-3">
|
||||
<Markdown content={data.prompt} className="rb:min-h-5 rb:max-h-70 rb:overflow-y-auto" />
|
||||
</div>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default PromptDetail;
|
||||
90
web/src/views/Prompt/components/PromptSaveModal.tsx
Normal file
90
web/src/views/Prompt/components/PromptSaveModal.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { PromptSaveModalRef, PromptReleaseData } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { savePrompt } from '@/api/prompt'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface PromptSaveModalProps {
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const PromptSaveModal = forwardRef<PromptSaveModalRef, PromptSaveModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<{ title?: string; }>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<PromptReleaseData | null>(null)
|
||||
const title = Form.useWatch(['title'], form)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setData(null)
|
||||
};
|
||||
|
||||
const handleOpen = (vo: PromptReleaseData) => {
|
||||
setData(vo)
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
if (!title || title.trim() === '') {
|
||||
message.warning(t('common.inputPlaceholder', { title: t('prompt.saveTitle') }))
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
savePrompt({
|
||||
...data,
|
||||
title
|
||||
} as PromptReleaseData)
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
refresh()
|
||||
handleClose()
|
||||
message.success(t('common.saveSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('prompt.saveTitle')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<FormItem
|
||||
name="title"
|
||||
noStyle
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default PromptSaveModal;
|
||||
104
web/src/views/Prompt/components/PromptVariableModal.tsx
Normal file
104
web/src/views/Prompt/components/PromptVariableModal.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Form, AutoComplete, type AutoCompleteProps } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { PromptVariableModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface PromptVariableModalProps {
|
||||
refresh: (value: string) => void;
|
||||
variables: string[];
|
||||
}
|
||||
|
||||
const PromptVariableModal = forwardRef<PromptVariableModalRef, PromptVariableModalProps>(({
|
||||
refresh,
|
||||
variables
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [options, setOptions] = useState<AutoCompleteProps['options']>([])
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(variables.map(key => ({
|
||||
value: key,
|
||||
label: `{{${key}}}`
|
||||
})))
|
||||
}, [variables])
|
||||
const handleSearch = (value: string) => {
|
||||
const filterKeys = variables?.filter(key => key.includes(value))
|
||||
|
||||
if (filterKeys.length) {
|
||||
setOptions(filterKeys.map(key => ({
|
||||
value: key,
|
||||
label: `{{${key}}}`
|
||||
})))
|
||||
} else {
|
||||
setOptions([{
|
||||
value: value,
|
||||
label: `{{${value}}}`
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
form.resetFields();
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
const variableName = form.getFieldValue('variableName')
|
||||
|
||||
if (!variableName) return
|
||||
|
||||
refresh(`{{${variableName}}}`)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.addVariable')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
confirmLoading={loading}
|
||||
onOk={handleSave}
|
||||
okText={t('application.apply')}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||
>
|
||||
<FormItem
|
||||
name="variableName"
|
||||
label={t('application.defineVariableName')}
|
||||
extra={t('application.defineVariableNameExtra')}
|
||||
>
|
||||
<AutoComplete
|
||||
placeholder={t('application.defineVariableNamePlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
options={options}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default PromptVariableModal;
|
||||
Reference in New Issue
Block a user