feat(web): add prompt menu

This commit is contained in:
zhaoying
2026-01-28 17:50:09 +08:00
parent 66bc2fb41f
commit f3da8956d9
17 changed files with 798 additions and 8 deletions

View 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;

View 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;

View 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;