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
671bc70b
Commit
671bc70b
authored
Aug 04, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 接入在线 LLM 聊天能力
parent
c49ab07e
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
544 additions
and
123 deletions
+544
-123
.umirc.ts
.umirc.ts
+5
-0
index.jsx
src/pages/Chat/index.jsx
+365
-108
invoice.jsx
src/pages/Chat/invoice.jsx
+22
-6
index.jsx
src/pages/Home/index.jsx
+12
-2
DataService.js
src/services/DataService.js
+140
-7
No files found.
.umirc.ts
View file @
671bc70b
...
@@ -38,6 +38,11 @@ export default defineConfig({
...
@@ -38,6 +38,11 @@ export default defineConfig({
changeOrigin
:
true
,
changeOrigin
:
true
,
pathRewrite
:
{
'^/api'
:
''
},
pathRewrite
:
{
'^/api'
:
''
},
},
},
'/llm-api'
:
{
target
:
'https://lingsuan.top'
,
changeOrigin
:
true
,
pathRewrite
:
{
'^/llm-api'
:
''
},
},
'/myapi/upload_invoices'
:
{
'/myapi/upload_invoices'
:
{
target
:
'http://1.94.111.115:5173'
,
target
:
'http://1.94.111.115:5173'
,
changeOrigin
:
true
,
changeOrigin
:
true
,
...
...
src/pages/Chat/index.jsx
View file @
671bc70b
...
@@ -17,19 +17,20 @@ import {
...
@@ -17,19 +17,20 @@ import {
Bubble
,
Bubble
,
Prompts
,
Prompts
,
Sender
,
Sender
,
useXAgent
,
useXChat
,
Welcome
,
Welcome
,
XStream
,
XStream
,
useXAgent
,
useXChat
,
}
from
'@ant-design/x'
;
}
from
'@ant-design/x'
;
import
{
useQuery
}
from
'@umijs/max'
;
import
{
import
{
Button
,
Button
,
Collapse
,
Collapse
,
Flex
,
Flex
,
message
,
Space
,
Space
,
theme
,
Typography
,
Typography
,
message
,
theme
,
}
from
'antd'
;
}
from
'antd'
;
import
{
createStyles
}
from
'antd-style'
;
import
{
createStyles
}
from
'antd-style'
;
import
'katex/dist/katex.min.css'
;
import
'katex/dist/katex.min.css'
;
...
@@ -49,9 +50,13 @@ import {
...
@@ -49,9 +50,13 @@ import {
createConversation
,
createConversation
,
documentAnalyze
,
documentAnalyze
,
documentRerank
,
documentRerank
,
extractLlmChunkContent
,
extractLlmChunkReasoning
,
imageAnalyze
,
imageAnalyze
,
llmChat
,
llmChat
,
queryMcps
,
queryMcps
,
queryTopics
,
resolveLlmModel
,
semanticSearch
,
semanticSearch
,
updateConversation
,
updateConversation
,
}
from
'../../services/DataService'
;
}
from
'../../services/DataService'
;
...
@@ -76,41 +81,36 @@ const preprocessLaTeX = (content) => {
...
@@ -76,41 +81,36 @@ const preprocessLaTeX = (content) => {
const
SYSTEM_PROMPT
=
`你是一个有用的的智能助手。
const
SYSTEM_PROMPT
=
`你是一个有用的的智能助手。
如果用户提供了上下文,请根据上下文回答问题,不要试图创造一个答案。如果你不知道答案,请回答你只能根据上下文的内容回答问题。`
;
如果用户提供了上下文,请根据上下文回答问题,不要试图创造一个答案。如果你不知道答案,请回答你只能根据上下文的内容回答问题。`
;
const
AGENTS
=
{
const
AGENT_DEFINITIONS
=
[
key
:
'2'
,
label
:
'智能体'
,
children
:
[
{
{
key
:
'2-5'
,
key
:
'knowledge-copilot'
,
icon
:
<
DatabaseOutlined
/>,
label
:
'知识库问答'
,
description
:
'优先检索已接入知识库,基于命中的资料回答问题并说明依据。'
,
prompt
:
'你是企业知识库问答智能体。回答必须优先依据已检索到的知识库片段和用户上传文件;如果没有命中足够依据,请明确说明知识库中未检索到相关内容,并给出需要补充的资料范围。'
,
},
{
key
:
'audit-checklist'
,
icon
:
<
FileDoneOutlined
/>,
icon
:
<
FileDoneOutlined
/>,
label
:
'审计检查清单生成'
,
label
:
'审计检查清单生成'
,
description
:
description
:
'根据当前审计项目、整改问题和责任单位上下文,生成可执行的审计检查清单。'
,
'根据当前审计项目、整改问题和责任单位上下文,生成可执行的审计检查清单。'
,
disabled
:
false
,
prompt
:
'你是审计检查清单生成智能体。你需要把审计项目、整改问题、责任单位、知识库依据拆解为现场可执行检查点,输出包含检查对象、检查资料、核验方法、风险提示和责任跟进建议。'
,
topicName
:
'mock_audit_rectification_query'
,
},
},
{
{
key
:
'2-4
'
,
key
:
'invoice-assistant
'
,
icon
:
<
PayCircleOutlined
/>,
icon
:
<
PayCircleOutlined
/>,
label
:
'发票助手'
,
label
:
'发票助手'
,
description
:
'支持全票种识别,精准高效助力发票管理'
,
description
:
'支持全票种识别,精准高效助力发票管理。'
,
disabled
:
false
,
prompt
:
'你是发票处理智能体。你需要围绕发票识别、票据归集、核验和台账整理提供帮助。'
,
},
},
// {
];
// key: '2-2',
// icon: <PaperClipOutlined />,
const
DEFAULT_AGENT
=
AGENT_DEFINITIONS
[
0
];
// label: '存单数据提取',
// description: '高效采集存单数据,助力业务精准处理',
// disabled: true,
// },
// {
// key: '2-3',
// icon: <FileDoneOutlined />,
// label: '报告核对',
// description: '高效核对报告,精准揪错,提升文档准确性',
// disabled: true,
// },
],
};
const
AUDIT_TOPIC
=
{
const
AUDIT_TOPIC
=
{
name
:
'mock_audit_rectification_query'
,
name
:
'mock_audit_rectification_query'
,
...
@@ -129,6 +129,135 @@ const AUDIT_TOPIC = {
...
@@ -129,6 +129,135 @@ const AUDIT_TOPIC = {
},
},
};
};
const
FALLBACK_TOPICS
=
[
AUDIT_TOPIC
];
function
normalizeTopic
(
topic
)
{
if
(
!
topic
)
{
return
null
;
}
return
{
_id
:
topic
.
_id
||
topic
.
id
,
name
:
topic
.
name
,
title
:
topic
.
title
||
topic
.
metadata
?.
label
||
topic
.
name
||
'业务话题'
,
metadata
:
topic
.
metadata
||
{},
};
}
function
buildAgentPromptGroup
(
topics
=
[])
{
return
{
key
:
'agents'
,
label
:
'智能体'
,
children
:
AGENT_DEFINITIONS
.
map
((
agent
)
=>
{
const
topic
=
topics
.
find
((
item
)
=>
item
.
name
===
agent
.
topicName
)
||
(
agent
.
topicName
===
AUDIT_TOPIC
.
name
?
AUDIT_TOPIC
:
undefined
);
return
{
...
agent
,
initialContent
:
agent
.
description
,
topic
,
};
}),
};
}
function
buildTopicPromptGroup
(
topics
=
[])
{
const
normalizedTopics
=
topics
.
length
>
0
?
topics
:
FALLBACK_TOPICS
;
return
{
key
:
'topics'
,
label
:
'业务话题'
,
children
:
normalizedTopics
.
flatMap
((
topic
)
=>
{
const
questions
=
topic
.
metadata
?.
questions
||
[];
return
[
{
key
:
topic
.
name
,
description
:
topic
.
title
,
icon
:
<
FileSearchOutlined
/>,
topic
,
},
...
questions
.
map
((
question
)
=>
({
key
:
`
${
topic
.
name
}
_
${
question
}
`
,
description
:
question
,
icon
:
<
AuditOutlined
/>,
topic
,
content
:
question
,
})),
];
}),
};
}
function
getKnowledgeBaseId
(
kb
)
{
return
kb
?.
_id
||
kb
?.
id
||
kb
?.
name
;
}
function
dedupeKnowledgeBases
(
kbs
=
[])
{
return
_
.
uniqBy
(
kbs
.
filter
(
Boolean
),
getKnowledgeBaseId
);
}
function
serializeAgent
(
agent
)
{
if
(
!
agent
)
{
return
null
;
}
return
_
.
pick
(
agent
,
[
'_id'
,
'key'
,
'label'
,
'description'
,
'prompt'
,
'topicName'
,
]);
}
function
serializeTopic
(
topic
)
{
const
normalizedTopic
=
normalizeTopic
(
topic
);
if
(
!
normalizedTopic
)
{
return
null
;
}
return
_
.
pick
(
normalizedTopic
,
[
'_id'
,
'name'
,
'title'
,
'metadata'
]);
}
function
serializeKnowledgeBase
(
kb
)
{
if
(
!
kb
)
{
return
null
;
}
return
_
.
pick
(
kb
,
[
'_id'
,
'name'
,
'title'
,
'metadata'
]);
}
function
isObjectIdLike
(
value
)
{
return
typeof
value
===
'string'
&&
/^
[
a-f
\d]{24}
$/i
.
test
(
value
);
}
function
getReferenceId
(
record
)
{
const
id
=
record
?.
_id
||
record
?.
id
;
return
isObjectIdLike
(
id
)
?
id
:
undefined
;
}
function
getConversationAgent
(
metadata
=
{})
{
const
agent
=
metadata
.
agentConfig
||
metadata
.
agent
;
if
(
agent
&&
typeof
agent
===
'object'
&&
agent
.
prompt
)
{
return
serializeAgent
(
agent
);
}
return
serializeAgent
(
DEFAULT_AGENT
);
}
function
getConversationTopic
(
metadata
=
{})
{
const
topic
=
metadata
.
topicConfig
||
metadata
.
topic
;
if
(
topic
&&
typeof
topic
===
'object'
)
{
return
serializeTopic
(
topic
);
}
return
null
;
}
const
AUDIT_OBJECT_GROUPS
=
[
const
AUDIT_OBJECT_GROUPS
=
[
{
{
key
:
'project'
,
key
:
'project'
,
...
@@ -532,9 +661,32 @@ const useStyle = createStyles(({ token, css, responsive }) => {
...
@@ -532,9 +661,32 @@ const useStyle = createStyles(({ token, css, responsive }) => {
line-height: 1.7;
line-height: 1.7;
}
}
`
,
`
,
knowledgeContextCard
:
css
`
margin-top: 16px;
padding: 14px 16px;
border-radius: 18px;
background: #ffffff;
border: 1px solid rgba(200, 30, 58, 0.16);
color: #0f172a;
display: grid;
gap: 8px;
box-shadow: 0 12px 26px rgba(159, 18, 57, 0.06);
strong {
color: #9f1239;
font-size: 14px;
}
p {
margin: 0;
color: #475569;
font-size: 13px;
line-height: 1.7;
}
`
,
promptRow
:
css
`
promptRow
:
css
`
display: grid;
display: grid;
grid-template-columns: repeat(
2, minmax(0
, 1fr));
grid-template-columns: repeat(
auto-fit, minmax(260px
, 1fr));
gap: 16px;
gap: 16px;
width: 100%;
width: 100%;
min-width: 0;
min-width: 0;
...
@@ -648,9 +800,37 @@ export default forwardRef((props, ref) => {
...
@@ -648,9 +800,37 @@ export default forwardRef((props, ref) => {
const
{
userInfo
,
conversation
,
onAddConversation
}
=
props
;
const
{
userInfo
,
conversation
,
onAddConversation
}
=
props
;
const
{
styles
}
=
useStyle
();
const
{
styles
}
=
useStyle
();
const
{
token
}
=
theme
.
useToken
();
const
{
token
}
=
theme
.
useToken
();
const
conversationAgent
=
useMemo
(
()
=>
getConversationAgent
(
conversation
?.
metadata
),
[
conversation
],
);
const
conversationTopic
=
useMemo
(
()
=>
getConversationTopic
(
conversation
?.
metadata
),
[
conversation
],
);
// 检测是否是发票助手(根据conversation的label判断)
// 检测是否是发票助手(根据conversation的label判断)
const
isInvoiceAssistant
=
conversation
?.
metadata
?.
label
===
'发票助手'
;
const
isInvoiceAssistant
=
conversationAgent
?.
key
===
'invoice-assistant'
||
conversation
?.
metadata
?.
label
===
'发票助手'
;
const
{
data
:
configuredTopics
=
[]
}
=
useQuery
({
queryKey
:
[
'ai-topics'
,
userInfo
?.
_id
],
enabled
:
!!
userInfo
,
queryFn
:
async
()
=>
{
try
{
const
res
=
await
queryTopics
({
page_size
:
30
});
const
topics
=
(
res
.
records
||
[]).
map
(
normalizeTopic
).
filter
(
Boolean
);
const
hasAuditTopic
=
topics
.
some
(
(
topic
)
=>
topic
.
name
===
AUDIT_TOPIC
.
name
,
);
return
hasAuditTopic
?
topics
:
[...
topics
,
AUDIT_TOPIC
];
}
catch
(
error
)
{
console
.
error
(
error
);
return
FALLBACK_TOPICS
;
}
},
});
const
[
attachmentsOpen
,
setAttachmentsOpen
]
=
useState
(
false
);
const
[
attachmentsOpen
,
setAttachmentsOpen
]
=
useState
(
false
);
const
[
attachedFiles
,
setAttachedFiles
]
=
useState
([]);
const
[
attachedFiles
,
setAttachedFiles
]
=
useState
([]);
...
@@ -668,6 +848,12 @@ export default forwardRef((props, ref) => {
...
@@ -668,6 +848,12 @@ export default forwardRef((props, ref) => {
}),
}),
[
auditContext
],
[
auditContext
],
);
);
const
promptGroups
=
useMemo
(()
=>
{
return
[
buildTopicPromptGroup
(
configuredTopics
),
buildAgentPromptGroup
(
configuredTopics
),
];
},
[
configuredTopics
]);
useImperativeHandle
(
ref
,
()
=>
{
useImperativeHandle
(
ref
,
()
=>
{
return
{
return
{
...
@@ -721,8 +907,8 @@ export default forwardRef((props, ref) => {
...
@@ -721,8 +907,8 @@ export default forwardRef((props, ref) => {
return
originMessage
;
return
originMessage
;
}
}
currentThink
=
messageData
?.
choices
?.[
0
]?.
delta
?.
reasoning_content
||
''
;
currentThink
=
extractLlmChunkReasoning
(
messageData
)
;
currentContent
=
messageData
?.
choices
?.[
0
]?.
delta
?.
content
||
''
;
currentContent
=
extractLlmChunkContent
(
messageData
)
;
messageData
?.
choices
?.[
0
]?.
delta
?.
tool_calls
?.
forEach
((
call
)
=>
{
messageData
?.
choices
?.[
0
]?.
delta
?.
tool_calls
?.
forEach
((
call
)
=>
{
const
index
=
call
.
index
;
const
index
=
call
.
index
;
const
toolCall
=
(
toolCalls
[
index
]
=
toolCalls
[
index
]
||
{
const
toolCall
=
(
toolCalls
[
index
]
=
toolCalls
[
index
]
||
{
...
@@ -772,29 +958,51 @@ export default forwardRef((props, ref) => {
...
@@ -772,29 +958,51 @@ export default forwardRef((props, ref) => {
);
);
};
};
const
getActiveKnowledgeBases
=
(
message
)
=>
{
const
topicKbs
=
conversationTopic
?.
metadata
?.
kbs
||
[];
const
conversationKbs
=
conversation
?.
metadata
?.
knowledgeBases
||
[];
const
messageKbs
=
message
?.
knowledgeBases
||
message
?.
settings
?.
knowledgeBases
||
[];
const
selectedKbs
=
dedupeKnowledgeBases
([
...
messageKbs
,
...
topicKbs
,
...
conversationKbs
,
]);
if
(
selectedKbs
.
length
>
0
)
{
return
selectedKbs
;
}
return
[];
};
const
getSystemPrompt
=
async
()
=>
{
const
getSystemPrompt
=
async
()
=>
{
const
topic
=
conversation
.
metadata
.
topic
;
const
topic
=
conversationTopic
;
const
agent
=
conversationAgent
;
const
auditContextText
=
formatAuditContext
(
const
auditContextText
=
formatAuditContext
(
conversation
.
metadata
.
auditContext
||
[],
conversation
.
metadata
.
auditContext
||
[],
);
);
const
auditPrompt
=
auditContextText
const
auditPrompt
=
auditContextText
?
`\n\n当前审计对象上下文:\n
${
auditContextText
}
`
?
`\n\n当前审计对象上下文:\n
${
auditContextText
}
`
:
''
;
:
''
;
const
agentPrompt
=
agent
?.
prompt
?
`\n\n当前智能体:
${
agent
.
label
}
\n
${
agent
.
prompt
}
`
:
''
;
if
(
!
topic
)
{
if
(
!
topic
)
{
return
`
${
SYSTEM_PROMPT
}${
auditPrompt
}
`
;
return
`
${
SYSTEM_PROMPT
}${
a
gentPrompt
}${
a
uditPrompt
}
`
;
}
}
return
`
${
topic
.
metadata
.
prompt
||
''
}
\n
${
return
`
${
topic
.
metadata
.
prompt
||
''
}
\n
${
topic
.
metadata
.
context
||
''
topic
.
metadata
.
context
||
''
}${
auditPrompt
}
`
;
}${
a
gentPrompt
}${
a
uditPrompt
}
`
;
};
};
const
getMessagePrompt
=
async
(
message
)
=>
{
const
getMessagePrompt
=
async
(
message
)
=>
{
const
topic
=
conversation
.
metadata
.
t
opic
;
const
topic
=
conversation
T
opic
;
const
files
=
message
.
files
||
[];
const
files
=
message
.
files
||
[];
const
searchSubjects
=
topic
?.
metadata
?.
search_subjects
||
[];
const
searchSubjects
=
topic
?.
metadata
?.
search_subjects
||
[];
const
kbs
=
topic
?.
metadata
?.
kbs
||
[]
;
const
kbs
=
getActiveKnowledgeBases
(
message
)
;
const
searchResults
=
[];
const
searchResults
=
[];
const
docs
=
[];
const
docs
=
[];
let
fileAnalyzeResults
=
[];
let
fileAnalyzeResults
=
[];
...
@@ -817,34 +1025,48 @@ export default forwardRef((props, ref) => {
...
@@ -817,34 +1025,48 @@ export default forwardRef((props, ref) => {
});
});
docs
.
push
(
docs
.
push
(
res
.
hits
?.
hits
?.
flatMap
((
doc
)
=>
...(
res
.
hits
?.
hits
?.
flatMap
((
doc
)
=>
doc
.
_source
.
embeddings
.
map
(
(
doc
.
_source
.
embeddings
||
[])
.
map
(
(
chunk
)
=>
(
chunk
)
=>
`#
${
doc
.
_source
.
title
}
\n
${
chunk
.
content
||
chunk
.
text
||
''
}
`
,
`#
${
doc
.
_source
.
title
}
\n
${
chunk
.
content
||
chunk
.
text
||
''
}
`
,
),
),
)
||
[],
)
||
[]
)
,
);
);
}
}
if
(
kbs
.
length
>
0
)
{
if
(
kbs
.
length
>
0
)
{
for
(
const
kb
of
kbs
)
{
const
kbId
=
getKnowledgeBaseId
(
kb
);
if
(
!
kbId
)
{
continue
;
}
const
res
=
await
semanticSearch
(
'ai_knowledge_base_document'
,
{
const
res
=
await
semanticSearch
(
'ai_knowledge_base_document'
,
{
text
:
message
.
content
,
text
:
message
.
content
,
size
:
5
,
size
:
5
,
source
:
[
'title'
,
'embeddings.content'
,
'embeddings.text'
],
source
:
[
'title'
,
'knowledge_base'
,
'embeddings.content'
,
'embeddings.text'
,
],
terms
:
{
terms
:
{
knowledge_base
:
kbs
[
0
].
_i
d
,
knowledge_base
:
kbI
d
,
},
},
});
});
docs
.
push
(
docs
.
push
(
...(
res
.
hits
?.
hits
?.
flatMap
((
doc
)
=>
...(
res
.
hits
?.
hits
?.
flatMap
((
doc
)
=>
doc
.
_source
.
embeddings
.
map
(
(
doc
.
_source
.
embeddings
||
[])
.
map
(
(
chunk
)
=>
(
chunk
)
=>
`#
${
doc
.
_source
.
title
}
\n
${
chunk
.
content
||
chunk
.
text
||
''
}
`
,
`#
${
doc
.
_source
.
title
||
kb
.
title
}
\n知识库:
${
kb
.
title
}
\n
${
chunk
.
content
||
chunk
.
text
||
''
}
`
,
),
),
)
||
[]),
)
||
[]),
);
);
}
}
}
if
(
docs
.
length
>
0
)
{
if
(
docs
.
length
>
0
)
{
const
batchSize
=
50
;
const
batchSize
=
50
;
...
@@ -914,6 +1136,10 @@ export default forwardRef((props, ref) => {
...
@@ -914,6 +1136,10 @@ export default forwardRef((props, ref) => {
if
(
searchResults
.
length
>
0
)
{
if
(
searchResults
.
length
>
0
)
{
searchPrompt
=
`知识库搜索结果:\n
${
searchResults
.
join
(
'
\
n'
)}
\n\n`
;
searchPrompt
=
`知识库搜索结果:\n
${
searchResults
.
join
(
'
\
n'
)}
\n\n`
;
}
else
if
(
kbs
.
length
>
0
)
{
searchPrompt
=
`知识库搜索结果:本次未在已选知识库中检索到足够相关内容。\n已选知识库:
${
kbs
.
map
((
kb
)
=>
kb
.
title
||
kb
.
name
)
.
join
(
'、'
)}
\n请不要编造知识库中不存在的结论。\n\n`
;
}
}
if
(
fileAnalyzeResults
.
length
>
0
)
{
if
(
fileAnalyzeResults
.
length
>
0
)
{
...
@@ -927,6 +1153,12 @@ export default forwardRef((props, ref) => {
...
@@ -927,6 +1153,12 @@ export default forwardRef((props, ref) => {
filePrompt
=
`用户上传的文件内容:\n
${
fileContent
}
\n\n`
;
filePrompt
=
`用户上传的文件内容:\n
${
fileContent
}
\n\n`
;
}
}
if
(
kbs
.
length
>
0
)
{
kbPrompt
=
`回答范围:优先基于已选知识库(
${
kbs
.
map
((
kb
)
=>
kb
.
title
||
kb
.
name
)
.
join
(
'、'
)}
)和用户上传文件回答;如果依据不足,请说明缺口。\n\n`
;
}
return
`
${
auditPrompt
}${
searchPrompt
}${
filePrompt
}${
kbPrompt
}
用户提问:
${
message
.
content
}
`
;
return
`
${
auditPrompt
}${
searchPrompt
}${
filePrompt
}${
kbPrompt
}
用户提问:
${
message
.
content
}
`
;
};
};
...
@@ -960,19 +1192,32 @@ export default forwardRef((props, ref) => {
...
@@ -960,19 +1192,32 @@ export default forwardRef((props, ref) => {
const
content
=
JSON
.
stringify
(
requestMessages
);
const
content
=
JSON
.
stringify
(
requestMessages
);
const
long
=
content
.
length
>
900000
;
const
long
=
content
.
length
>
900000
;
const
model
=
long
?
'qwen-long'
:
'qwen-plus'
;
const
model
=
await
resolveLlmModel
({
const
enable_search
=
long
,
conversation
.
metadata
.
topic
?.
metadata
?.
enable_search
;
thinking
:
true
,
});
const
enable_search
=
conversationTopic
?.
metadata
?.
enable_search
;
const
enable_thinking
=
true
;
const
enable_thinking
=
true
;
const
requestContext
=
{
agent
:
conversationAgent
,
topic
:
conversationTopic
?
{
name
:
conversationTopic
.
name
,
title
:
conversationTopic
.
title
,
}
:
undefined
,
knowledgeBases
:
getActiveKnowledgeBases
(
message
).
map
(
serializeKnowledgeBase
,
),
auditContext
:
conversation
.
metadata
.
auditContext
||
[],
};
const
data
=
{
const
data
=
{
model
,
model
,
stream
:
true
,
stream
:
true
,
messages
:
requestMessages
,
messages
:
requestMessages
,
tools
:
context
:
requestContext
,
!
conversation
.
metadata
.
topic
&&
tools
.
length
>
0
tools
:
!
conversationTopic
&&
tools
.
length
>
0
?
tools
:
undefined
,
?
tools
:
undefined
,
enable_search
,
enable_search
,
enable_thinking
,
enable_thinking
,
};
};
...
@@ -1109,7 +1354,7 @@ export default forwardRef((props, ref) => {
...
@@ -1109,7 +1354,7 @@ export default forwardRef((props, ref) => {
messages
:
[
messages
:
[
{
{
role
:
'system'
,
role
:
'system'
,
content
:
SYSTEM_PROMPT
,
content
:
await
getSystemPrompt
()
,
},
},
...
data
.
messages
,
...
data
.
messages
,
currentMessage
,
currentMessage
,
...
@@ -1403,10 +1648,18 @@ export default forwardRef((props, ref) => {
...
@@ -1403,10 +1648,18 @@ export default forwardRef((props, ref) => {
topic
,
topic
,
files
=
null
,
files
=
null
,
settings
=
null
,
settings
=
null
,
agentLabel
=
null
,
agent
=
null
,
knowledgeBases
=
null
,
})
=>
{
})
=>
{
const
selectedAgent
=
serializeAgent
(
agent
||
DEFAULT_AGENT
);
const
selectedTopic
=
serializeTopic
(
topic
);
const
selectedKnowledgeBases
=
knowledgeBases
||
null
;
const
serializedKnowledgeBases
=
selectedKnowledgeBases
?.
map
(
serializeKnowledgeBase
).
filter
(
Boolean
)
||
null
;
const
nextSettings
=
{
const
nextSettings
=
{
...
auditSettings
,
...
auditSettings
,
knowledgeBases
:
serializedKnowledgeBases
,
...
settings
,
...
settings
,
};
};
const
userMessage
=
content
&&
{
const
userMessage
=
content
&&
{
...
@@ -1421,14 +1674,31 @@ export default forwardRef((props, ref) => {
...
@@ -1421,14 +1674,31 @@ export default forwardRef((props, ref) => {
};
};
if
(
!
conversation
)
{
if
(
!
conversation
)
{
await
addConversation
(
const
conversationData
=
{
{
label
:
agent
label
:
agentLabel
||
content
||
topic
?.
title
||
AUDIT_TOPIC
.
title
,
?
selectedAgent
?.
label
topic
:
topic
,
:
content
||
selectedTopic
?.
title
||
selectedAgent
?.
label
||
AUDIT_TOPIC
.
title
,
agentConfig
:
selectedAgent
||
serializeAgent
(
DEFAULT_AGENT
),
knowledgeBases
:
serializedKnowledgeBases
||
[],
auditContext
:
nextSettings
.
auditContext
,
auditContext
:
nextSettings
.
auditContext
,
},
};
userMessage
,
const
topicId
=
getReferenceId
(
selectedTopic
);
);
const
agentId
=
getReferenceId
(
selectedAgent
);
if
(
selectedTopic
)
{
conversationData
.
topicConfig
=
selectedTopic
;
}
if
(
topicId
)
{
conversationData
.
topic
=
topicId
;
}
if
(
agentId
)
{
conversationData
.
agent
=
agentId
;
}
await
addConversation
(
conversationData
,
userMessage
);
return
;
return
;
}
}
...
@@ -1465,9 +1735,18 @@ export default forwardRef((props, ref) => {
...
@@ -1465,9 +1735,18 @@ export default forwardRef((props, ref) => {
<
div
className=
{
styles
.
chatList
}
>
<
div
className=
{
styles
.
chatList
}
>
{
conversation
?
(
{
conversation
?
(
<
div
className=
{
styles
.
chatListInner
}
>
<
div
className=
{
styles
.
chatListInner
}
>
{
conversation
.
metadata
.
topic
&&
(
{
(
conversationTopic
||
conversationAgent
)
&&
(
<
Welcome
<
Welcome
title=
{
conversation
.
metadata
.
topic
.
title
}
title=
{
conversationAgent
?.
label
||
conversationTopic
?.
title
||
'智能助手'
}
description=
{
conversationTopic
?.
title
?
`当前话题:${conversationTopic.title}`
:
conversationAgent
?.
description
}
style=
{
{
style=
{
{
background
:
background
:
'linear-gradient(135deg, rgba(0, 156, 217, 0.12) 0%, rgba(186, 230, 253, 0.36) 100%)'
,
'linear-gradient(135deg, rgba(0, 156, 217, 0.12) 0%, rgba(186, 230, 253, 0.36) 100%)'
,
...
@@ -1476,6 +1755,16 @@ export default forwardRef((props, ref) => {
...
@@ -1476,6 +1755,16 @@ export default forwardRef((props, ref) => {
}
}
}
}
/>
/>
)
}
)
}
{
conversation
.
metadata
.
knowledgeBases
?.
length
>
0
&&
(
<
div
className=
{
styles
.
knowledgeContextCard
}
>
<
strong
>
已连接知识库
</
strong
>
<
p
>
{
conversation
.
metadata
.
knowledgeBases
.
map
((
kb
)
=>
kb
.
title
||
kb
.
name
)
.
join
(
' / '
)
}
</
p
>
</
div
>
)
}
{
conversation
.
metadata
.
auditContext
?.
length
>
0
&&
(
{
conversation
.
metadata
.
auditContext
?.
length
>
0
&&
(
<
div
className=
{
styles
.
auditContextCard
}
>
<
div
className=
{
styles
.
auditContextCard
}
>
<
strong
>
已读取审计对象上下文
</
strong
>
<
strong
>
已读取审计对象上下文
</
strong
>
...
@@ -1525,28 +1814,10 @@ export default forwardRef((props, ref) => {
...
@@ -1525,28 +1814,10 @@ export default forwardRef((props, ref) => {
)
:
(
)
:
(
<
div
className=
{
`${styles.chatListInner} ${styles.placeholder}`
}
>
<
div
className=
{
`${styles.chatListInner} ${styles.placeholder}`
}
>
<
div
className=
{
styles
.
promptRow
}
>
<
div
className=
{
styles
.
promptRow
}
>
{
promptGroups
.
map
((
group
)
=>
(
<
Prompts
<
Prompts
items=
{
[
key=
{
group
.
key
}
{
items=
{
[
group
]
}
key
:
AUDIT_TOPIC
.
name
,
label
:
'话题'
,
children
:
[
{
key
:
AUDIT_TOPIC
.
name
,
description
:
AUDIT_TOPIC
.
title
,
icon
:
<
FileSearchOutlined
/>,
topic
:
AUDIT_TOPIC
,
},
...
AUDIT_TOPIC
.
metadata
.
questions
.
map
((
question
)
=>
({
key
:
question
,
description
:
question
,
icon
:
<
AuditOutlined
/>,
topic
:
AUDIT_TOPIC
,
content
:
question
,
})),
],
},
]
}
style=
{
{
style=
{
{
flex
:
1
,
flex
:
1
,
}
}
}
}
...
@@ -1555,34 +1826,20 @@ export default forwardRef((props, ref) => {
...
@@ -1555,34 +1826,20 @@ export default forwardRef((props, ref) => {
subItem
:
{
padding
:
0
,
background
:
'#ffffff'
},
subItem
:
{
padding
:
0
,
background
:
'#ffffff'
},
}
}
}
}
onItemClick=
{
async
(
info
)
=>
{
onItemClick=
{
async
(
info
)
=>
{
const
selectedAgent
=
info
.
data
.
prompt
?
info
.
data
:
info
.
data
.
agent
;
onSubmit
({
onSubmit
({
content
:
info
.
data
.
c
ontent
,
content
:
info
.
data
.
content
||
info
.
data
.
initialC
ontent
,
topic
:
info
.
data
.
topic
,
topic
:
info
.
data
.
topic
,
agent
:
selectedAgent
,
knowledgeBases
:
info
.
data
.
knowledgeBases
,
});
});
}
}
}
}
className=
{
styles
.
chatPrompt
}
className=
{
styles
.
chatPrompt
}
/>
/>
<
Prompts
))
}
items=
{
[
AGENTS
]
}
style=
{
{
flex
:
1
,
}
}
styles=
{
{
list
:
{
height
:
'100%'
},
subItem
:
{
background
:
'#ffffff'
},
}
}
onItemClick=
{
(
info
)
=>
{
onSubmit
({
content
:
info
.
data
.
description
,
topic
:
info
.
data
.
label
===
AGENTS
.
children
[
0
].
label
?
AUDIT_TOPIC
:
undefined
,
agentLabel
:
info
.
data
.
label
,
});
}
}
className=
{
styles
.
chatPrompt
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
)
}
...
@@ -1620,7 +1877,7 @@ export default forwardRef((props, ref) => {
...
@@ -1620,7 +1877,7 @@ export default forwardRef((props, ref) => {
const
chatSender
=
(
const
chatSender
=
(
<
div
className=
{
styles
.
senderShell
}
>
<
div
className=
{
styles
.
senderShell
}
>
{
messages
.
length
===
0
&&
conversation
?.
metadata
?.
t
opic
&&
(
{
messages
.
length
===
0
&&
conversation
T
opic
&&
(
<
Prompts
<
Prompts
vertical
vertical
className=
{
styles
.
senderPrompt
}
className=
{
styles
.
senderPrompt
}
...
@@ -1629,7 +1886,7 @@ export default forwardRef((props, ref) => {
...
@@ -1629,7 +1886,7 @@ export default forwardRef((props, ref) => {
padding
:
'6px 12px'
,
padding
:
'6px 12px'
,
},
},
}
}
}
}
items=
{
conversation
.
metadata
.
t
opic
.
metadata
.
questions
?.
map
((
q
)
=>
({
items=
{
conversation
T
opic
.
metadata
.
questions
?.
map
((
q
)
=>
({
key
:
q
,
key
:
q
,
description
:
q
,
description
:
q
,
}))
}
}))
}
...
...
src/pages/Chat/invoice.jsx
View file @
671bc70b
...
@@ -13,9 +13,9 @@ import {
...
@@ -13,9 +13,9 @@ import {
Bubble
,
Bubble
,
Prompts
,
Prompts
,
Sender
,
Sender
,
XStream
,
useXAgent
,
useXAgent
,
useXChat
,
useXChat
,
XStream
,
}
from
'@ant-design/x'
;
}
from
'@ant-design/x'
;
import
{
App
,
Button
,
Flex
,
Space
,
Spin
,
Steps
,
Tag
,
Typography
}
from
'antd'
;
import
{
App
,
Button
,
Flex
,
Space
,
Spin
,
Steps
,
Tag
,
Typography
}
from
'antd'
;
import
{
createStyles
}
from
'antd-style'
;
import
{
createStyles
}
from
'antd-style'
;
...
@@ -34,8 +34,11 @@ import {
...
@@ -34,8 +34,11 @@ import {
import
{
import
{
callMcpTool
,
callMcpTool
,
createConversation
,
createConversation
,
extractLlmChunkContent
,
extractLlmChunkReasoning
,
llmChat
,
llmChat
,
queryMcps
,
queryMcps
,
resolveLlmModel
,
updateConversation
,
updateConversation
,
}
from
'../../services/DataService'
;
}
from
'../../services/DataService'
;
...
@@ -71,6 +74,14 @@ const SYSTEM_PROMPT = `你叫Mia,是一个有用的的发票智能助手。如
...
@@ -71,6 +74,14 @@ const SYSTEM_PROMPT = `你叫Mia,是一个有用的的发票智能助手。如
如果你不知道答案,请回答你只能根据上下文的内容回答问题。在识别完文件之后,需要调用mcp工具生成用户可能需要的问题选项。用户确认数量没问题之后,需要生成一个表单供用户填写。接收到的是html的时候,assistent角色回复的消息需要是html格式,只要用户发送了表单信息,那么无论表单信息内容是否为空,都需要调用mcp工具对表单信息进行标准化处理
如果你不知道答案,请回答你只能根据上下文的内容回答问题。在识别完文件之后,需要调用mcp工具生成用户可能需要的问题选项。用户确认数量没问题之后,需要生成一个表单供用户填写。接收到的是html的时候,assistent角色回复的消息需要是html格式,只要用户发送了表单信息,那么无论表单信息内容是否为空,都需要调用mcp工具对表单信息进行标准化处理
`
;
`
;
const
INVOICE_TOOL_API_KEY
=
process
.
env
.
UMI_APP_INVOICE_TOOL_API_KEY
||
process
.
env
.
VITE_INVOICE_TOOL_API_KEY
;
const
INVOICE_TOOL_BASE_URL
=
process
.
env
.
UMI_APP_INVOICE_TOOL_BASE_URL
||
process
.
env
.
VITE_INVOICE_TOOL_BASE_URL
||
'https://api.deepseek.com'
;
const
useStyle
=
createStyles
(({
token
,
css
,
responsive
})
=>
{
const
useStyle
=
createStyles
(({
token
,
css
,
responsive
})
=>
{
return
{
return
{
chat
:
css
`
chat
:
css
`
...
@@ -437,8 +448,8 @@ const InvoiceAssistant = forwardRef((props, ref) => {
...
@@ -437,8 +448,8 @@ const InvoiceAssistant = forwardRef((props, ref) => {
return
originMessage
;
return
originMessage
;
}
}
currentThink
=
messageData
?.
choices
?.[
0
]?.
delta
?.
reasoning_content
||
''
;
currentThink
=
extractLlmChunkReasoning
(
messageData
)
;
currentContent
=
messageData
?.
choices
?.[
0
]?.
delta
?.
content
||
''
;
currentContent
=
extractLlmChunkContent
(
messageData
)
;
messageData
?.
choices
?.[
0
]?.
delta
?.
tool_calls
?.
forEach
((
call
)
=>
{
messageData
?.
choices
?.[
0
]?.
delta
?.
tool_calls
?.
forEach
((
call
)
=>
{
const
index
=
call
.
index
;
const
index
=
call
.
index
;
const
toolCall
=
(
toolCalls
[
index
]
=
toolCalls
[
index
]
||
{
const
toolCall
=
(
toolCalls
[
index
]
=
toolCalls
[
index
]
||
{
...
@@ -729,7 +740,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
...
@@ -729,7 +740,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
}
}
const
content
=
JSON
.
stringify
(
requestMessages
);
const
content
=
JSON
.
stringify
(
requestMessages
);
const
long
=
content
.
length
>
900000
;
const
long
=
content
.
length
>
900000
;
const
model
=
long
?
'qwen-long'
:
'qwen-plus'
;
const
model
=
await
resolveLlmModel
({
long
,
thinking
:
false
,
});
const
enable_search
=
false
;
const
enable_search
=
false
;
const
enable_thinking
=
false
;
const
enable_thinking
=
false
;
...
@@ -800,8 +814,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
...
@@ -800,8 +814,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
)
{
)
{
mcpArguments
=
{
mcpArguments
=
{
...
mcpArguments
,
...
mcpArguments
,
apikey
:
'sk-bf3698390f3b40dd8435cb73169a7437'
,
...(
INVOICE_TOOL_API_KEY
baseurl
:
'https://api.deepseek.com'
,
?
{
apikey
:
INVOICE_TOOL_API_KEY
}
:
{}),
baseurl
:
INVOICE_TOOL_BASE_URL
,
};
};
}
}
...
...
src/pages/Home/index.jsx
View file @
671bc70b
...
@@ -535,7 +535,12 @@ function buildConversationItems(conversations = []) {
...
@@ -535,7 +535,12 @@ function buildConversationItems(conversations = []) {
return
conversations
.
map
((
c
)
=>
({
return
conversations
.
map
((
c
)
=>
({
key
:
c
.
name
,
key
:
c
.
name
,
label
:
c
.
metadata
.
label
||
'新会话'
,
label
:
c
.
metadata
.
label
||
'新会话'
,
icon
:
c
.
metadata
.
topic
?
<
BorderlessTableOutlined
/>
:
<
CommentOutlined
/>,
icon
:
c
.
metadata
.
topic
||
c
.
metadata
.
topicConfig
?
(
<
BorderlessTableOutlined
/>
)
:
(
<
CommentOutlined
/>
),
pinned
:
c
.
metadata
.
pinned
,
pinned
:
c
.
metadata
.
pinned
,
group
:
'历史会话'
,
group
:
'历史会话'
,
}));
}));
...
@@ -545,7 +550,12 @@ function buildCompactConversationItems(conversations = []) {
...
@@ -545,7 +550,12 @@ function buildCompactConversationItems(conversations = []) {
return
conversations
.
slice
(
0
,
6
).
map
((
c
)
=>
({
return
conversations
.
slice
(
0
,
6
).
map
((
c
)
=>
({
key
:
c
.
name
,
key
:
c
.
name
,
label
:
c
.
metadata
.
label
||
'新会话'
,
label
:
c
.
metadata
.
label
||
'新会话'
,
icon
:
c
.
metadata
.
topic
?
<
BorderlessTableOutlined
/>
:
<
CommentOutlined
/>,
icon
:
c
.
metadata
.
topic
||
c
.
metadata
.
topicConfig
?
(
<
BorderlessTableOutlined
/>
)
:
(
<
CommentOutlined
/>
),
pinned
:
c
.
metadata
.
pinned
,
pinned
:
c
.
metadata
.
pinned
,
}));
}));
}
}
...
...
src/services/DataService.js
View file @
671bc70b
...
@@ -4,7 +4,117 @@ import { request } from '@umijs/max';
...
@@ -4,7 +4,117 @@ import { request } from '@umijs/max';
const
SUBJECT_CONVERSATION
=
'ai_conversation'
;
const
SUBJECT_CONVERSATION
=
'ai_conversation'
;
const
SUBJECT_TOPIC
=
'ai_topic'
;
const
SUBJECT_TOPIC
=
'ai_topic'
;
const
SUBJECT_KNOWLEDGEBASE
=
'ai_knowledgebase'
;
const
LLM_API_BASE
=
process
.
env
.
UMI_APP_LLM_API_BASE
||
process
.
env
.
VITE_LLM_API_BASE
||
'/llm-api'
;
const
LLM_CHAT_PATH
=
process
.
env
.
UMI_APP_LLM_CHAT_PATH
||
process
.
env
.
VITE_LLM_CHAT_PATH
||
'/v1/chat/completions'
;
const
LLM_API_KEY
=
process
.
env
.
UMI_APP_LLM_API_KEY
||
process
.
env
.
VITE_LLM_API_KEY
;
const
LLM_MODEL
=
process
.
env
.
UMI_APP_LLM_MODEL
||
process
.
env
.
VITE_LLM_MODEL
||
'gpt-5.5'
;
function
joinUrl
(
baseUrl
,
path
)
{
return
`
${
baseUrl
.
replace
(
/
\/
+$/
,
''
)}
/
${
path
.
replace
(
/^
\/
+/
,
''
)}
`
;
}
export
function
extractLlmChunkContent
(
value
)
{
if
(
typeof
value
===
'string'
)
{
return
value
;
}
if
(
!
value
||
typeof
value
!==
'object'
)
{
return
''
;
}
if
(
typeof
value
.
content
===
'string'
)
{
return
value
.
content
;
}
if
(
typeof
value
.
text
===
'string'
)
{
return
value
.
text
;
}
const
delta
=
value
.
delta
;
if
(
delta
&&
typeof
delta
===
'object'
)
{
const
deltaContent
=
extractLlmChunkContent
(
delta
);
if
(
deltaContent
)
{
return
deltaContent
;
}
}
const
message
=
value
.
message
;
if
(
message
&&
typeof
message
===
'object'
)
{
const
messageContent
=
extractLlmChunkContent
(
message
);
if
(
messageContent
)
{
return
messageContent
;
}
}
const
data
=
value
.
data
;
if
(
data
&&
typeof
data
===
'object'
)
{
const
dataContent
=
extractLlmChunkContent
(
data
);
if
(
dataContent
)
{
return
dataContent
;
}
}
const
choices
=
value
.
choices
;
if
(
Array
.
isArray
(
choices
))
{
return
choices
.
map
((
choice
)
=>
extractLlmChunkContent
(
choice
)).
join
(
''
);
}
return
''
;
}
export
function
extractLlmChunkReasoning
(
value
)
{
if
(
!
value
||
typeof
value
!==
'object'
)
{
return
''
;
}
const
reasoning
=
value
.
reasoning_content
||
value
.
reasoning
||
value
.
thinking_content
||
value
.
thought
||
value
.
thoughts
;
if
(
typeof
reasoning
===
'string'
)
{
return
reasoning
;
}
const
delta
=
value
.
delta
;
if
(
delta
&&
typeof
delta
===
'object'
)
{
const
deltaReasoning
=
extractLlmChunkReasoning
(
delta
);
if
(
deltaReasoning
)
{
return
deltaReasoning
;
}
}
const
message
=
value
.
message
;
if
(
message
&&
typeof
message
===
'object'
)
{
const
messageReasoning
=
extractLlmChunkReasoning
(
message
);
if
(
messageReasoning
)
{
return
messageReasoning
;
}
}
const
data
=
value
.
data
;
if
(
data
&&
typeof
data
===
'object'
)
{
const
dataReasoning
=
extractLlmChunkReasoning
(
data
);
if
(
dataReasoning
)
{
return
dataReasoning
;
}
}
const
choices
=
value
.
choices
;
if
(
Array
.
isArray
(
choices
))
{
return
choices
.
map
((
choice
)
=>
extractLlmChunkReasoning
(
choice
)).
join
(
''
);
}
return
''
;
}
export
function
getApplication
(
name
)
{
export
function
getApplication
(
name
)
{
return
request
(
`/api/application/
${
name
}
`
,
{
return
request
(
`/api/application/
${
name
}
`
,
{
...
@@ -158,14 +268,37 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
...
@@ -158,14 +268,37 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
}
}
export
async
function
llmChat
(
data
,
signal
=
undefined
)
{
export
async
function
llmChat
(
data
,
signal
=
undefined
)
{
return
fetch
(
`/api/llm/chat`
,
{
if
(
!
LLM_API_KEY
)
{
method
:
'post'
,
throw
new
Error
(
'缺少 UMI_APP_LLM_API_KEY,请配置线上模型服务 API Key。'
);
headers
:
{
}
const
headers
=
new
Headers
({
Accept
:
'text/event-stream'
,
Authorization
:
`Bearer
${
LLM_API_KEY
}
`
,
'Content-Type'
:
'application/json'
,
'Content-Type'
:
'application/json'
,
},
});
const
response
=
await
fetch
(
joinUrl
(
LLM_API_BASE
,
LLM_CHAT_PATH
),
{
method
:
'POST'
,
headers
,
signal
,
signal
,
body
:
JSON
.
stringify
(
data
),
body
:
JSON
.
stringify
({
...
data
,
model
:
LLM_MODEL
,
stream
:
true
,
}),
});
});
if
(
!
response
.
ok
)
{
const
message
=
await
response
.
text
();
throw
new
Error
(
message
||
`HTTP
${
response
.
status
}
`
);
}
return
response
;
}
export
async
function
resolveLlmModel
(
options
=
{})
{
return
LLM_MODEL
;
}
}
export
async
function
queryConversations
(
query
)
{
export
async
function
queryConversations
(
query
)
{
...
@@ -179,7 +312,7 @@ export async function queryConversations(query) {
...
@@ -179,7 +312,7 @@ export async function queryConversations(query) {
page_size
:
20
,
page_size
:
20
,
mode
:
'basic'
,
mode
:
'basic'
,
select
:
select
:
'metadata.label metadata.pinned metadata.agent metadata.a
vatar metadata.topic
'
,
'metadata.label metadata.pinned metadata.agent metadata.a
gentConfig metadata.avatar metadata.topic metadata.topicConfig metadata.knowledgeBases
'
,
sort
:
'-metadata.pinned -created_at'
,
sort
:
'-metadata.pinned -created_at'
,
filter
:
{
filter
:
{
'metadata.user'
:
userInfo
.
_id
,
'metadata.user'
:
userInfo
.
_id
,
...
...
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