Commit ba702ba8 authored by David Yang's avatar David Yang

feat: prompt edit

parent 6c9a4573
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
FileWordOutlined, FileWordOutlined,
InboxOutlined, InboxOutlined,
ReloadOutlined, ReloadOutlined,
SettingOutlined,
UploadOutlined, UploadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
...@@ -15,6 +16,8 @@ import { ...@@ -15,6 +16,8 @@ import {
Divider, Divider,
Empty, Empty,
Flex, Flex,
Form,
Input,
InputNumber, InputNumber,
Modal, Modal,
Space, Space,
...@@ -328,6 +331,7 @@ export function AuditKnowledgeBar({ ...@@ -328,6 +331,7 @@ export function AuditKnowledgeBar({
fileName, fileName,
loading, loading,
onOpen, onOpen,
onOpenPrompt,
onRefresh, onRefresh,
}) { }) {
const { styles } = useAuditStyles(); const { styles } = useAuditStyles();
...@@ -343,6 +347,13 @@ export function AuditKnowledgeBar({ ...@@ -343,6 +347,13 @@ export function AuditKnowledgeBar({
: '尚未配置 Excel'} : '尚未配置 Excel'}
</div> </div>
<Space wrap> <Space wrap>
<Tooltip title="配置审计提示词">
<Button
aria-label="配置审计提示词"
icon={<SettingOutlined />}
onClick={onOpenPrompt}
/>
</Tooltip>
<Tooltip title="刷新知识库"> <Tooltip title="刷新知识库">
<Button <Button
aria-label="刷新知识库" aria-label="刷新知识库"
...@@ -359,6 +370,71 @@ export function AuditKnowledgeBar({ ...@@ -359,6 +370,71 @@ export function AuditKnowledgeBar({
); );
} }
export function AuditPromptConfigDialog({ open, value, onSave, onCancel }) {
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
useEffect(() => {
if (open) {
form.setFieldsValue({
prompt: value?.prompt || '',
context: value?.context || '',
});
}
}, [form, open, value]);
const handleSave = async () => {
let values;
try {
values = await form.validateFields();
} catch {
return;
}
setSaving(true);
try {
await onSave?.(values);
message.success('审计提示词已保存');
onCancel?.();
} catch (error) {
message.error(`提示词保存失败:${error.message || '未知错误'}`);
} finally {
setSaving(false);
}
};
return (
<Modal
title="审计提示词配置"
open={open}
width={760}
confirmLoading={saving}
maskClosable={!saving}
closable={!saving}
okText="保存"
cancelText="取消"
onOk={handleSave}
onCancel={saving ? undefined : onCancel}
>
<Form form={form} layout="vertical" requiredMark={false}>
<Form.Item
label="主提示词"
name="prompt"
rules={[{ required: true, message: '请输入主提示词' }]}
>
<Input.TextArea rows={7} showCount maxLength={6000} />
</Form.Item>
<Form.Item
label="业务规则与约束"
name="context"
rules={[{ required: true, message: '请输入业务规则与约束' }]}
>
<Input.TextArea rows={9} showCount maxLength={10000} />
</Form.Item>
</Form>
</Modal>
);
}
function RiskDetail({ risk, index, styles }) { function RiskDetail({ risk, index, styles }) {
const detail = (label, value) => ( const detail = (label, value) => (
<Typography.Paragraph className={styles.riskLine}> <Typography.Paragraph className={styles.riskLine}>
...@@ -386,7 +462,11 @@ function RiskDetail({ risk, index, styles }) { ...@@ -386,7 +462,11 @@ function RiskDetail({ risk, index, styles }) {
); );
} }
export function AuditChecklistResult({ result, aiContent = '' }) { export function AuditChecklistResult({
result,
aiContent = '',
allowWordExport = false,
}) {
const { styles } = useAuditStyles(); const { styles } = useAuditStyles();
const [threshold, setThreshold] = useState( const [threshold, setThreshold] = useState(
Number(result.threshold || AUDIT_MATCH_THRESHOLD), Number(result.threshold || AUDIT_MATCH_THRESHOLD),
...@@ -548,14 +628,16 @@ export function AuditChecklistResult({ result, aiContent = '' }) { ...@@ -548,14 +628,16 @@ export function AuditChecklistResult({ result, aiContent = '' }) {
> >
Excel Excel
</Button> </Button>
<Button {allowWordExport && (
type="primary" <Button
icon={<FileWordOutlined />} type="primary"
loading={wordLoading} icon={<FileWordOutlined />}
onClick={handleWordExport} loading={wordLoading}
> onClick={handleWordExport}
Word >
</Button> Word
</Button>
)}
</Space> </Space>
</div> </div>
)} )}
......
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
LoadingOutlined, LoadingOutlined,
PaperClipOutlined, PaperClipOutlined,
ReloadOutlined, ReloadOutlined,
SettingOutlined,
ToolOutlined, ToolOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
...@@ -26,6 +27,7 @@ import { ...@@ -26,6 +27,7 @@ import {
Collapse, Collapse,
Flex, Flex,
Space, Space,
Tooltip,
Typography, Typography,
message, message,
theme, theme,
...@@ -48,6 +50,7 @@ import { ...@@ -48,6 +50,7 @@ import {
AuditChecklistResult, AuditChecklistResult,
AuditKnowledgeBar, AuditKnowledgeBar,
AuditKnowledgeImportDialog, AuditKnowledgeImportDialog,
AuditPromptConfigDialog,
AuditWordDownload, AuditWordDownload,
} from '../../features/audit/components'; } from '../../features/audit/components';
import { AUDIT_KNOWLEDGE_FILE_SUBJECT } from '../../features/audit/constants'; import { AUDIT_KNOWLEDGE_FILE_SUBJECT } from '../../features/audit/constants';
...@@ -886,7 +889,7 @@ export default forwardRef((props, ref) => { ...@@ -886,7 +889,7 @@ export default forwardRef((props, ref) => {
conversationAgent?.key === 'invoice-assistant' || conversationAgent?.key === 'invoice-assistant' ||
conversation?.metadata?.label === '发票助手'; conversation?.metadata?.label === '发票助手';
const { data: configuredTopics = [] } = useQuery({ const { data: configuredTopics = [], refetch: refetchTopics } = useQuery({
queryKey: ['ai-topics', userInfo?._id], queryKey: ['ai-topics', userInfo?._id],
enabled: !!userInfo, enabled: !!userInfo,
queryFn: async () => { queryFn: async () => {
...@@ -908,6 +911,7 @@ export default forwardRef((props, ref) => { ...@@ -908,6 +911,7 @@ export default forwardRef((props, ref) => {
const [attachedFiles, setAttachedFiles] = useState([]); const [attachedFiles, setAttachedFiles] = useState([]);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [auditKnowledgeOpen, setAuditKnowledgeOpen] = useState(false); const [auditKnowledgeOpen, setAuditKnowledgeOpen] = useState(false);
const [auditPromptOpen, setAuditPromptOpen] = useState(false);
const [auditKnowledgeLoading, setAuditKnowledgeLoading] = useState(false); const [auditKnowledgeLoading, setAuditKnowledgeLoading] = useState(false);
const [auditRecords, setAuditRecords] = useState([]); const [auditRecords, setAuditRecords] = useState([]);
const [auditKnowledgeFile, setAuditKnowledgeFile] = useState(null); const [auditKnowledgeFile, setAuditKnowledgeFile] = useState(null);
...@@ -919,6 +923,12 @@ export default forwardRef((props, ref) => { ...@@ -919,6 +923,12 @@ export default forwardRef((props, ref) => {
const auditLoadPromiseRef = useRef(null); const auditLoadPromiseRef = useRef(null);
const pendingAuditResultRef = useRef(null); const pendingAuditResultRef = useRef(null);
const pendingAuditExportRef = useRef(null); const pendingAuditExportRef = useRef(null);
const configuredAuditTopic = useMemo(
() =>
configuredTopics.find((topic) => topic.name === AUDIT_TOPIC.name) ||
AUDIT_TOPIC,
[configuredTopics],
);
const promptGroups = useMemo(() => { const promptGroups = useMemo(() => {
return [ return [
buildTopicPromptGroup(configuredTopics), buildTopicPromptGroup(configuredTopics),
...@@ -1028,6 +1038,36 @@ export default forwardRef((props, ref) => { ...@@ -1028,6 +1038,36 @@ export default forwardRef((props, ref) => {
[auditKnowledgeFile], [auditKnowledgeFile],
); );
const saveAuditPromptConfig = useCallback(
async ({ prompt, context }) => {
const topic = configuredAuditTopic || AUDIT_TOPIC;
const metadata = {
...AUDIT_TOPIC.metadata,
...(topic.metadata || {}),
prompt: prompt.trim(),
context: context.trim(),
enable_search: false,
};
const recordId =
topic._id || topic.id || (topic !== AUDIT_TOPIC ? topic.name : null);
if (recordId) {
await updateRecord(recordId, {
title: topic.title || AUDIT_TOPIC.title,
metadata,
});
} else {
await createRecord({
subject: 'ai_topic',
name: AUDIT_TOPIC.name,
title: AUDIT_TOPIC.title,
metadata,
});
}
await refetchTopics();
},
[configuredAuditTopic, refetchTopics],
);
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
submit: async (message) => { submit: async (message) => {
...@@ -1166,8 +1206,14 @@ export default forwardRef((props, ref) => { ...@@ -1166,8 +1206,14 @@ export default forwardRef((props, ref) => {
}; };
const getSystemPrompt = async () => { const getSystemPrompt = async () => {
const topic = conversationTopic;
const agent = conversationAgent; const agent = conversationAgent;
const latestConversationTopic = configuredTopics.find(
(item) => item.name === conversationTopic?.name,
);
const topic =
latestConversationTopic ||
conversationTopic ||
(agent?.key === 'audit-checklist' ? configuredAuditTopic : null);
const agentPrompt = agent?.prompt const agentPrompt = agent?.prompt
? `\n\n当前智能体:${agent.label}\n${agent.prompt}` ? `\n\n当前智能体:${agent.label}\n${agent.prompt}`
: ''; : '';
...@@ -1649,22 +1695,17 @@ export default forwardRef((props, ref) => { ...@@ -1649,22 +1695,17 @@ export default forwardRef((props, ref) => {
break; break;
} }
} }
const usesAuditWordExport = const allowAuditWordExport =
conversationAgent?.key === 'knowledge-copilot' || conversationAgent?.key === 'audit-checklist';
conversationAgent?.key === 'audit-checklist' || const auditExport = allowAuditWordExport
conversationTopic?.name === AUDIT_TOPIC.name; ? item.message.auditExport ||
const auditExport = (previousQuestion
item.message.auditExport || ? {
(usesAuditWordExport && previousQuestion question: previousQuestion,
? { title: '审计检查清单',
question: previousQuestion, }
title: : null)
conversationAgent?.key === 'audit-checklist' || : null;
conversationTopic?.name === AUDIT_TOPIC.name
? '审计检查清单'
: '审计知识库问答',
}
: null);
messageItem.classNames = { messageItem.classNames = {
content: `${item.status === 'loading' ? styles.loadingMessage : ''} ${ content: `${item.status === 'loading' ? styles.loadingMessage : ''} ${
styles.assistantBubble styles.assistantBubble
...@@ -1795,6 +1836,7 @@ export default forwardRef((props, ref) => { ...@@ -1795,6 +1836,7 @@ export default forwardRef((props, ref) => {
<AuditChecklistResult <AuditChecklistResult
result={item.message.auditResult} result={item.message.auditResult}
aiContent={item.message.content} aiContent={item.message.content}
allowWordExport={allowAuditWordExport}
/> />
)} )}
{item.status === 'success' && auditExport && ( {item.status === 'success' && auditExport && (
...@@ -2072,6 +2114,7 @@ export default forwardRef((props, ref) => { ...@@ -2072,6 +2114,7 @@ export default forwardRef((props, ref) => {
fileName={auditKnowledgeFile?.file?.name} fileName={auditKnowledgeFile?.file?.name}
loading={auditKnowledgeLoading} loading={auditKnowledgeLoading}
onOpen={() => setAuditKnowledgeOpen(true)} onOpen={() => setAuditKnowledgeOpen(true)}
onOpenPrompt={() => setAuditPromptOpen(true)}
onRefresh={() => loadAuditRecords(true)} onRefresh={() => loadAuditRecords(true)}
/> />
<div className={styles.promptRow}> <div className={styles.promptRow}>
...@@ -2171,11 +2214,25 @@ export default forwardRef((props, ref) => { ...@@ -2171,11 +2214,25 @@ export default forwardRef((props, ref) => {
onChange={setInputValue} onChange={setInputValue}
onCancel={() => abortController.current?.abort()} onCancel={() => abortController.current?.abort()}
prefix={ prefix={
<Button <Space size={2}>
type="text" {(conversationAgent?.key === 'audit-checklist' ||
icon={<PaperClipOutlined style={{ fontSize: 18 }} />} conversationTopic?.name === AUDIT_TOPIC.name) && (
onClick={() => setAttachmentsOpen(!attachmentsOpen)} <Tooltip title="配置审计提示词">
/> <Button
type="text"
aria-label="配置审计提示词"
icon={<SettingOutlined style={{ fontSize: 18 }} />}
onClick={() => setAuditPromptOpen(true)}
/>
</Tooltip>
)}
<Button
type="text"
aria-label="上传附件"
icon={<PaperClipOutlined style={{ fontSize: 18 }} />}
onClick={() => setAttachmentsOpen(!attachmentsOpen)}
/>
</Space>
} }
loading={loading} loading={loading}
className={styles.sender} className={styles.sender}
...@@ -2208,6 +2265,12 @@ export default forwardRef((props, ref) => { ...@@ -2208,6 +2265,12 @@ export default forwardRef((props, ref) => {
onCancel={() => setAuditKnowledgeOpen(false)} onCancel={() => setAuditKnowledgeOpen(false)}
onImported={() => loadAuditRecords(true)} onImported={() => loadAuditRecords(true)}
/> />
<AuditPromptConfigDialog
open={auditPromptOpen}
value={configuredAuditTopic?.metadata}
onSave={saveAuditPromptConfig}
onCancel={() => setAuditPromptOpen(false)}
/>
</div> </div>
); );
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment