Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
A
ai-chat-xy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
ai-chat-xy
Commits
50f02701
Commit
50f02701
authored
Aug 06, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 增加审计回答 Word 下载
parent
4657579b
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
200 additions
and
13 deletions
+200
-13
components.jsx
src/features/audit/components.jsx
+39
-1
export.js
src/features/audit/export.js
+103
-9
index.jsx
src/pages/Chat/index.jsx
+58
-3
No files found.
src/features/audit/components.jsx
View file @
50f02701
...
...
@@ -17,7 +17,6 @@ import {
Empty
,
Flex
,
InputNumber
,
message
,
Modal
,
Progress
,
Space
,
...
...
@@ -26,6 +25,7 @@ import {
Tooltip
,
Typography
,
Upload
,
message
,
}
from
'antd'
;
import
{
createStyles
}
from
'antd-style'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
...
...
@@ -36,6 +36,7 @@ import {
exportAuditExcel
,
exportAuditMarkdown
,
exportAuditWord
,
exportGroundedAuditWord
,
filterAuditRecords
,
}
from
'./export'
;
...
...
@@ -401,6 +402,43 @@ export function AuditChecklistResult({ result, aiContent = '' }) {
);
}
export
function
AuditWordDownload
({
records
=
[],
aiContent
=
''
,
question
=
''
,
title
=
'审计知识库问答'
,
})
{
const
{
styles
}
=
useAuditStyles
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
handleDownload
=
async
()
=>
{
setLoading
(
true
);
try
{
await
exportGroundedAuditWord
({
records
,
aiContent
,
question
,
title
});
}
catch
(
error
)
{
message
.
error
(
`Word 导出失败:
${
error
.
message
||
'未知错误'
}
`
);
}
finally
{
setLoading
(
false
);
}
};
return
(
<
div
className=
{
styles
.
exportBar
}
>
<
Typography
.
Text
type=
"secondary"
>
知识库原文
{
records
.
length
}
条
</
Typography
.
Text
>
<
Button
type=
"primary"
icon=
{
<
FileWordOutlined
/>
}
loading=
{
loading
}
onClick=
{
handleDownload
}
>
下载 Word
</
Button
>
</
div
>
);
}
export
function
AuditKnowledgeImportDialog
({
open
,
existingRecords
,
...
...
src/features/audit/export.js
View file @
50f02701
...
...
@@ -283,6 +283,13 @@ function createWordStylesXml() {
</w:styles>`
;
}
function
wrapWordDocument
(
body
)
{
return
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>
${
body
.
join
(
''
,
)}
<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/><w:cols w:space="720"/><w:docGrid w:linePitch="360"/></w:sectPr></w:body></w:document>`
;
}
function
createWordDocumentXml
(
result
,
aiContent
,
threshold
)
{
const
summary
=
parseAuditSummary
(
aiContent
,
result
.
project
);
const
groups
=
groupAuditRecords
(
filterAuditRecords
(
result
,
threshold
));
...
...
@@ -395,13 +402,10 @@ function createWordDocumentXml(result, aiContent, threshold) {
});
});
return
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>
${
body
.
join
(
''
,
)}
<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/><w:cols w:space="720"/><w:docGrid w:linePitch="360"/></w:sectPr></w:body></w:document>`
;
return
wrapWordDocument
(
body
);
}
export
async
function
createAuditWordBlob
(
result
,
aiContent
,
threshold
)
{
async
function
createWordBlob
(
documentXml
)
{
const
zip
=
new
JSZip
();
zip
.
file
(
'[Content_Types].xml'
,
...
...
@@ -414,10 +418,7 @@ export async function createAuditWordBlob(result, aiContent, threshold) {
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`
,
);
const
word
=
zip
.
folder
(
'word'
);
word
.
file
(
'document.xml'
,
createWordDocumentXml
(
result
,
aiContent
,
threshold
),
);
word
.
file
(
'document.xml'
,
documentXml
);
word
.
file
(
'styles.xml'
,
createWordStylesXml
());
word
.
folder
(
'_rels'
)
...
...
@@ -432,9 +433,102 @@ export async function createAuditWordBlob(result, aiContent, threshold) {
});
}
export
async
function
createAuditWordBlob
(
result
,
aiContent
,
threshold
)
{
return
createWordBlob
(
createWordDocumentXml
(
result
,
aiContent
,
threshold
));
}
export
async
function
exportAuditWord
(
result
,
aiContent
,
threshold
)
{
downloadBlob
(
await
createAuditWordBlob
(
result
,
aiContent
,
threshold
),
`
${
safeFileName
(
result
.
project
)}
.docx`
,
);
}
function
createGroundedAuditWordDocumentXml
({
title
,
question
,
aiContent
,
records
=
[],
})
{
const
body
=
[
wordParagraph
(
wordRun
(
title
||
'审计知识库问答'
),
{
style
:
'Title'
,
alignment
:
'center'
,
}),
wordParagraph
(
wordRun
(
`生成时间:
${
formatWordDate
()}
`
,
{
size
:
'18'
,
color
:
'808080'
,
}),
{
alignment
:
'right'
},
),
wordParagraph
(
wordRun
(
'用户问题'
,
{
bold
:
true
}),
{
style
:
'Heading1'
,
}),
wordParagraph
(
wordRun
(
question
||
'(未记录)'
),
{
firstLine
:
'420'
}),
wordParagraph
(
wordRun
(
'智能体回答'
,
{
bold
:
true
}),
{
style
:
'Heading1'
,
}),
wordParagraph
(
wordRun
(
stripMarkdown
(
aiContent
)
||
'(无回答内容)'
),
{
firstLine
:
'420'
,
}),
wordParagraph
(
wordRun
(
'知识库原文依据'
,
{
bold
:
true
}),
{
style
:
'Heading1'
,
}),
];
if
(
!
records
.
length
)
{
body
.
push
(
wordParagraph
(
wordRun
(
'(当前知识库没有可用风险点)'
)));
return
wrapWordDocument
(
body
);
}
records
.
forEach
((
record
,
index
)
=>
{
const
heading
=
record
.
riskPointCode
||
record
.
level2Project
||
record
.
riskDescription
||
`风险点
${
index
+
1
}
`
;
const
details
=
[
[
'风险点编号'
,
record
.
riskPointCode
],
[
'业务板块'
,
record
.
businessSegment
],
[
'一级项目'
,
record
.
level1Project
],
[
'二级项目'
,
record
.
level2Project
],
[
'具体风险点描述'
,
record
.
riskDescription
],
[
'法规依据'
,
record
.
regulationBasis
],
[
'风险点来源'
,
record
.
riskSource
],
[
'风险等级'
,
record
.
riskLevel
],
[
'来源文件名'
,
record
.
sourceFileName
],
[
'来源行号'
,
record
.
sourceRow
],
[
'文件批次'
,
record
.
sourceBatch
],
[
'备注'
,
record
.
remark
],
];
body
.
push
(
wordParagraph
(
wordRun
(
`
${
index
+
1
}
.
${
heading
}
`
,
{
bold
:
true
}),
{
style
:
'Heading2'
,
}),
...
details
.
filter
(
([,
value
])
=>
value
!==
undefined
&&
value
!==
null
&&
value
!==
''
,
)
.
map
(([
label
,
value
])
=>
wordParagraph
(
[
wordRun
(
`
${
label
}
:`
,
{
bold
:
true
,
size
:
'21'
}),
wordRun
(
String
(
value
),
{
size
:
'21'
}),
],
{
leftIndent
:
'283'
},
),
),
);
});
return
wrapWordDocument
(
body
);
}
export
async
function
exportGroundedAuditWord
(
options
)
{
downloadBlob
(
await
createWordBlob
(
createGroundedAuditWordDocumentXml
(
options
)),
`
${
safeFileName
(
options
.
title
||
'审计知识库问答'
)}
.docx`
,
);
}
src/pages/Chat/index.jsx
View file @
50f02701
...
...
@@ -48,6 +48,7 @@ import {
AuditChecklistResult
,
AuditKnowledgeBar
,
AuditKnowledgeImportDialog
,
AuditWordDownload
,
}
from
'../../features/audit/components'
;
import
{
buildGroundedAuditKnowledgePrompt
,
...
...
@@ -681,6 +682,7 @@ export default forwardRef((props, ref) => {
const
abortController
=
useRef
(
null
);
const
auditRecordsRef
=
useRef
([]);
const
pendingAuditResultRef
=
useRef
(
null
);
const
pendingAuditExportRef
=
useRef
(
null
);
const
promptGroups
=
useMemo
(()
=>
{
return
[
buildTopicPromptGroup
(
configuredTopics
),
...
...
@@ -740,6 +742,10 @@ export default forwardRef((props, ref) => {
originMessage
?.
auditResult
||
pendingAuditResultRef
.
current
||
undefined
,
auditExport
:
originMessage
?.
auditExport
||
pendingAuditExportRef
.
current
||
undefined
,
tool_calls
:
undefined
,
};
}
...
...
@@ -793,6 +799,10 @@ export default forwardRef((props, ref) => {
originMessage
?.
auditResult
||
pendingAuditResultRef
.
current
||
undefined
,
auditExport
:
originMessage
?.
auditExport
||
pendingAuditExportRef
.
current
||
undefined
,
tool_calls
:
toolCalls
,
tools
:
toolCalls
,
};
...
...
@@ -987,6 +997,14 @@ export default forwardRef((props, ref) => {
if
(
usesStrictAuditKnowledge
)
{
const
records
=
await
loadAuditRecords
();
pendingAuditExportRef
.
current
=
{
question
:
message
.
content
,
title
:
conversationAgent
?.
key
===
'audit-checklist'
||
topic
?.
name
===
AUDIT_TOPIC
.
name
?
'审计检查清单'
:
'审计知识库问答'
,
};
auditKnowledgePrompt
=
buildGroundedAuditKnowledgePrompt
(
records
,
message
.
content
,
...
...
@@ -1006,6 +1024,7 @@ export default forwardRef((props, ref) => {
const
controller
=
new
AbortController
();
onStream
(
controller
);
pendingAuditResultRef
.
current
=
null
;
pendingAuditExportRef
.
current
=
null
;
onUpdate
({
data
:
'LOADING'
,
...
...
@@ -1021,6 +1040,7 @@ export default forwardRef((props, ref) => {
:
await
getTools
();
const
sanitizeLlmMessage
=
({
auditResult
,
auditExport
,
files
,
loading
,
settings
,
...
...
@@ -1287,6 +1307,33 @@ export default forwardRef((props, ref) => {
}
if
(
item
.
message
.
role
===
'assistant'
)
{
let
previousQuestion
=
''
;
for
(
let
messageIndex
=
index
-
1
;
messageIndex
>=
0
;
messageIndex
-=
1
)
{
if
(
messages
[
messageIndex
]?.
message
?.
role
===
'user'
)
{
previousQuestion
=
messages
[
messageIndex
].
message
.
content
||
''
;
break
;
}
}
const
usesAuditWordExport
=
conversationAgent
?.
key
===
'knowledge-copilot'
||
conversationAgent
?.
key
===
'audit-checklist'
||
conversationTopic
?.
name
===
AUDIT_TOPIC
.
name
;
const
auditExport
=
item
.
message
.
auditExport
||
(
usesAuditWordExport
&&
previousQuestion
?
{
question
:
previousQuestion
,
title
:
conversationAgent
?.
key
===
'audit-checklist'
||
conversationTopic
?.
name
===
AUDIT_TOPIC
.
name
?
'审计检查清单'
:
'审计知识库问答'
,
}
:
null
);
messageItem
.
classNames
=
{
content
:
`
${
item
.
status
===
'loading'
?
styles
.
loadingMessage
:
''
}
${
styles
.
assistantBubble
...
...
@@ -1417,6 +1464,14 @@ export default forwardRef((props, ref) => {
aiContent=
{
item
.
message
.
content
}
/>
)
}
{
item
.
status
===
'success'
&&
auditExport
&&
(
<
AuditWordDownload
records=
{
auditRecords
}
aiContent=
{
item
.
message
.
content
}
question=
{
auditExport
.
question
}
title=
{
auditExport
.
title
}
/>
)
}
</
Flex
>
);
};
...
...
@@ -1456,7 +1511,7 @@ export default forwardRef((props, ref) => {
return
messageItem
;
});
},
[
messages
]);
},
[
auditRecords
,
conversationAgent
,
conversationTopic
,
messages
]);
useEffect
(()
=>
{
abortController
?.
current
?.
abort
(
'cancelled'
);
...
...
@@ -1472,10 +1527,10 @@ export default forwardRef((props, ref) => {
},
[
conversation
]);
useEffect
(()
=>
{
if
(
!
conversation
&&
userInfo
)
{
if
(
userInfo
)
{
loadAuditRecords
().
catch
(()
=>
{});
}
},
[
conversation
,
loadAuditRecords
,
userInfo
]);
},
[
loadAuditRecords
,
userInfo
]);
useEffect
(()
=>
{
if
(
conversation
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment