Commit ba702ba8 authored by David Yang's avatar David Yang

feat: prompt edit

parent 6c9a4573
......@@ -6,6 +6,7 @@ import {
FileWordOutlined,
InboxOutlined,
ReloadOutlined,
SettingOutlined,
UploadOutlined,
} from '@ant-design/icons';
import {
......@@ -15,6 +16,8 @@ import {
Divider,
Empty,
Flex,
Form,
Input,
InputNumber,
Modal,
Space,
......@@ -328,6 +331,7 @@ export function AuditKnowledgeBar({
fileName,
loading,
onOpen,
onOpenPrompt,
onRefresh,
}) {
const { styles } = useAuditStyles();
......@@ -343,6 +347,13 @@ export function AuditKnowledgeBar({
: '尚未配置 Excel'}
</div>
<Space wrap>
<Tooltip title="配置审计提示词">
<Button
aria-label="配置审计提示词"
icon={<SettingOutlined />}
onClick={onOpenPrompt}
/>
</Tooltip>
<Tooltip title="刷新知识库">
<Button
aria-label="刷新知识库"
......@@ -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 }) {
const detail = (label, value) => (
<Typography.Paragraph className={styles.riskLine}>
......@@ -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 [threshold, setThreshold] = useState(
Number(result.threshold || AUDIT_MATCH_THRESHOLD),
......@@ -548,6 +628,7 @@ export function AuditChecklistResult({ result, aiContent = '' }) {
>
Excel
</Button>
{allowWordExport && (
<Button
type="primary"
icon={<FileWordOutlined />}
......@@ -556,6 +637,7 @@ export function AuditChecklistResult({ result, aiContent = '' }) {
>
Word
</Button>
)}
</Space>
</div>
)}
......
......@@ -8,6 +8,7 @@ import {
LoadingOutlined,
PaperClipOutlined,
ReloadOutlined,
SettingOutlined,
ToolOutlined,
} from '@ant-design/icons';
import {
......@@ -26,6 +27,7 @@ import {
Collapse,
Flex,
Space,
Tooltip,
Typography,
message,
theme,
......@@ -48,6 +50,7 @@ import {
AuditChecklistResult,
AuditKnowledgeBar,
AuditKnowledgeImportDialog,
AuditPromptConfigDialog,
AuditWordDownload,
} from '../../features/audit/components';
import { AUDIT_KNOWLEDGE_FILE_SUBJECT } from '../../features/audit/constants';
......@@ -886,7 +889,7 @@ export default forwardRef((props, ref) => {
conversationAgent?.key === 'invoice-assistant' ||
conversation?.metadata?.label === '发票助手';
const { data: configuredTopics = [] } = useQuery({
const { data: configuredTopics = [], refetch: refetchTopics } = useQuery({
queryKey: ['ai-topics', userInfo?._id],
enabled: !!userInfo,
queryFn: async () => {
......@@ -908,6 +911,7 @@ export default forwardRef((props, ref) => {
const [attachedFiles, setAttachedFiles] = useState([]);
const [inputValue, setInputValue] = useState('');
const [auditKnowledgeOpen, setAuditKnowledgeOpen] = useState(false);
const [auditPromptOpen, setAuditPromptOpen] = useState(false);
const [auditKnowledgeLoading, setAuditKnowledgeLoading] = useState(false);
const [auditRecords, setAuditRecords] = useState([]);
const [auditKnowledgeFile, setAuditKnowledgeFile] = useState(null);
......@@ -919,6 +923,12 @@ export default forwardRef((props, ref) => {
const auditLoadPromiseRef = useRef(null);
const pendingAuditResultRef = useRef(null);
const pendingAuditExportRef = useRef(null);
const configuredAuditTopic = useMemo(
() =>
configuredTopics.find((topic) => topic.name === AUDIT_TOPIC.name) ||
AUDIT_TOPIC,
[configuredTopics],
);
const promptGroups = useMemo(() => {
return [
buildTopicPromptGroup(configuredTopics),
......@@ -1028,6 +1038,36 @@ export default forwardRef((props, ref) => {
[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, () => {
return {
submit: async (message) => {
......@@ -1166,8 +1206,14 @@ export default forwardRef((props, ref) => {
};
const getSystemPrompt = async () => {
const topic = conversationTopic;
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
? `\n\n当前智能体:${agent.label}\n${agent.prompt}`
: '';
......@@ -1649,22 +1695,17 @@ export default forwardRef((props, ref) => {
break;
}
}
const usesAuditWordExport =
conversationAgent?.key === 'knowledge-copilot' ||
conversationAgent?.key === 'audit-checklist' ||
conversationTopic?.name === AUDIT_TOPIC.name;
const auditExport =
item.message.auditExport ||
(usesAuditWordExport && previousQuestion
const allowAuditWordExport =
conversationAgent?.key === 'audit-checklist';
const auditExport = allowAuditWordExport
? item.message.auditExport ||
(previousQuestion
? {
question: previousQuestion,
title:
conversationAgent?.key === 'audit-checklist' ||
conversationTopic?.name === AUDIT_TOPIC.name
? '审计检查清单'
: '审计知识库问答',
title: '审计检查清单',
}
: null);
: null)
: null;
messageItem.classNames = {
content: `${item.status === 'loading' ? styles.loadingMessage : ''} ${
styles.assistantBubble
......@@ -1795,6 +1836,7 @@ export default forwardRef((props, ref) => {
<AuditChecklistResult
result={item.message.auditResult}
aiContent={item.message.content}
allowWordExport={allowAuditWordExport}
/>
)}
{item.status === 'success' && auditExport && (
......@@ -2072,6 +2114,7 @@ export default forwardRef((props, ref) => {
fileName={auditKnowledgeFile?.file?.name}
loading={auditKnowledgeLoading}
onOpen={() => setAuditKnowledgeOpen(true)}
onOpenPrompt={() => setAuditPromptOpen(true)}
onRefresh={() => loadAuditRecords(true)}
/>
<div className={styles.promptRow}>
......@@ -2171,11 +2214,25 @@ export default forwardRef((props, ref) => {
onChange={setInputValue}
onCancel={() => abortController.current?.abort()}
prefix={
<Space size={2}>
{(conversationAgent?.key === 'audit-checklist' ||
conversationTopic?.name === AUDIT_TOPIC.name) && (
<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}
className={styles.sender}
......@@ -2208,6 +2265,12 @@ export default forwardRef((props, ref) => {
onCancel={() => setAuditKnowledgeOpen(false)}
onImported={() => loadAuditRecords(true)}
/>
<AuditPromptConfigDialog
open={auditPromptOpen}
value={configuredAuditTopic?.metadata}
onSave={saveAuditPromptConfig}
onCancel={() => setAuditPromptOpen(false)}
/>
</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