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
1488c6e6
Commit
1488c6e6
authored
Aug 27, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: refine ai chat assistant cards and model controls
parent
aa343ada
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
344 additions
and
39 deletions
+344
-39
ChatWorkspace.jsx
src/components/ChatWorkspace.jsx
+133
-3
index.jsx
src/pages/Chat/index.jsx
+90
-31
DataService.js
src/services/DataService.js
+61
-0
tailwind.css
src/tailwind.css
+60
-5
No files found.
src/components/ChatWorkspace.jsx
View file @
1488c6e6
import
{
import
{
BulbOutlined
,
CloseOutlined
,
CopyOutlined
,
CopyOutlined
,
DownOutlined
,
PaperClipOutlined
,
PaperClipOutlined
,
ReloadOutlined
,
ReloadOutlined
,
SearchOutlined
,
SettingOutlined
,
SettingOutlined
,
}
from
'@ant-design/icons'
;
}
from
'@ant-design/icons'
;
import
{
import
{
...
@@ -14,7 +18,28 @@ import { ChatMessage } from '@heroui-pro/react/chat-message';
...
@@ -14,7 +18,28 @@ import { ChatMessage } from '@heroui-pro/react/chat-message';
import
{
Markdown
,
StreamMarkdown
}
from
'@heroui-pro/react/markdown'
;
import
{
Markdown
,
StreamMarkdown
}
from
'@heroui-pro/react/markdown'
;
import
{
PromptInput
}
from
'@heroui-pro/react/prompt-input'
;
import
{
PromptInput
}
from
'@heroui-pro/react/prompt-input'
;
import
{
PromptSuggestion
}
from
'@heroui-pro/react/prompt-suggestion'
;
import
{
PromptSuggestion
}
from
'@heroui-pro/react/prompt-suggestion'
;
import
{
Disclosure
,
Spinner
,
Surface
,
toast
}
from
'@heroui/react'
;
import
{
Autocomplete
,
Chip
,
Disclosure
,
ListBox
,
SearchField
,
Spinner
,
Surface
,
toast
,
}
from
'@heroui/react'
;
function
filterModel
(
textValue
,
inputValue
)
{
const
query
=
String
(
inputValue
||
''
)
.
trim
()
.
toLocaleLowerCase
();
return
(
!
query
||
String
(
textValue
||
''
)
.
toLocaleLowerCase
()
.
includes
(
query
)
);
}
function
MessageAttachments
({
files
=
[]
})
{
function
MessageAttachments
({
files
=
[]
})
{
if
(
!
files
.
length
)
return
null
;
if
(
!
files
.
length
)
return
null
;
...
@@ -162,7 +187,7 @@ function PromptGroups({ groups = [], onSelect }) {
...
@@ -162,7 +187,7 @@ function PromptGroups({ groups = [], onSelect }) {
return
(
return
(
<
PromptSuggestion
className=
"rc-chat-suggestions"
variant=
"card"
>
<
PromptSuggestion
className=
"rc-chat-suggestions"
variant=
"card"
>
{
groups
.
map
((
group
)
=>
(
{
groups
.
map
((
group
)
=>
(
<
PromptSuggestion
.
Group
key=
{
group
.
key
}
label=
{
group
.
label
}
>
<
PromptSuggestion
.
Group
key=
{
group
.
key
}
>
<
PromptSuggestion
.
Items
<
PromptSuggestion
.
Items
className=
{
`rc-chat-suggestion-items${
className=
{
`rc-chat-suggestion-items${
group.children?.length === 1
group.children?.length === 1
...
@@ -183,6 +208,14 @@ function PromptGroups({ groups = [], onSelect }) {
...
@@ -183,6 +208,14 @@ function PromptGroups({ groups = [], onSelect }) {
}
}
}
}
}
}
>
>
{
item
.
icon
?
(
<
Surface
className=
"rc-chat-agent-icon grid size-14 place-items-center rounded-xl border border-border"
variant=
"default"
>
{
item
.
icon
}
</
Surface
>
)
:
null
}
<
PromptSuggestion
.
ItemTitle
>
<
PromptSuggestion
.
ItemTitle
>
{
item
.
label
||
item
.
description
||
item
.
title
}
{
item
.
label
||
item
.
description
||
item
.
title
}
</
PromptSuggestion
.
ItemTitle
>
</
PromptSuggestion
.
ItemTitle
>
...
@@ -201,6 +234,7 @@ function PromptGroups({ groups = [], onSelect }) {
...
@@ -201,6 +234,7 @@ function PromptGroups({ groups = [], onSelect }) {
}
}
export
default
function
ChatWorkspace
({
export
default
function
ChatWorkspace
({
activeMode
,
canConfigure
,
canConfigure
,
children
,
children
,
description
,
description
,
...
@@ -209,9 +243,12 @@ export default function ChatWorkspace({
...
@@ -209,9 +243,12 @@ export default function ChatWorkspace({
isLoading
,
isLoading
,
knowledgeBases
=
[],
knowledgeBases
=
[],
messages
=
[],
messages
=
[],
modelListLoading
=
false
,
models
=
[],
onConfigure
,
onConfigure
,
onFilesSelected
,
onFilesSelected
,
onInputChange
,
onInputChange
,
onModelChange
,
onQuestion
,
onQuestion
,
onRegenerate
,
onRegenerate
,
onRemoveFile
,
onRemoveFile
,
...
@@ -221,6 +258,7 @@ export default function ChatWorkspace({
...
@@ -221,6 +258,7 @@ export default function ChatWorkspace({
questions
=
[],
questions
=
[],
renderAssistantExtras
,
renderAssistantExtras
,
renderAssistantContent
,
renderAssistantContent
,
selectedModel
,
suggestionGroups
=
[],
suggestionGroups
=
[],
title
,
title
,
userInfo
,
userInfo
,
...
@@ -228,6 +266,10 @@ export default function ChatWorkspace({
...
@@ -228,6 +266,10 @@ export default function ChatWorkspace({
const
visibleMessages
=
messages
.
filter
(
const
visibleMessages
=
messages
.
filter
(
(
item
)
=>
!
item
.
message
?.
metadata
?.
hidden
,
(
item
)
=>
!
item
.
message
?.
metadata
?.
hidden
,
);
);
const
selectedModelLabel
=
models
.
find
((
model
)
=>
String
(
model
.
id
)
===
String
(
selectedModel
))?.
label
||
selectedModel
||
(
modelListLoading
?
'正在加载模型'
:
'选择模型'
);
return
(
return
(
<
div
className=
"rc-chat-workspace"
>
<
div
className=
"rc-chat-workspace"
>
...
@@ -273,7 +315,24 @@ export default function ChatWorkspace({
...
@@ -273,7 +315,24 @@ export default function ChatWorkspace({
);
);
})
}
})
}
{
!
visibleMessages
.
length
?
(
{
!
visibleMessages
.
length
?
(
<>
{
!
title
?
(
<
section
aria
-
labelledby=
"chat-welcome-title"
className=
"rc-chat-empty-state flex flex-col items-center text-center"
>
<
Surface
className=
"grid size-16 place-items-center rounded-full text-accent"
variant=
"tertiary"
>
<
BulbOutlined
className=
"text-2xl"
/>
</
Surface
>
<
h1
id=
"chat-welcome-title"
>
今天想处理什么?
</
h1
>
<
p
>
选择一个助手,或直接在下方输入问题。
</
p
>
</
section
>
)
:
null
}
<
PromptGroups
groups=
{
suggestionGroups
}
onSelect=
{
onSuggestion
}
/>
<
PromptGroups
groups=
{
suggestionGroups
}
onSelect=
{
onSuggestion
}
/>
</>
)
:
null
}
)
:
null
}
{
children
}
{
children
}
<
ChatConversation
.
ScrollAnchor
/>
<
ChatConversation
.
ScrollAnchor
/>
...
@@ -329,7 +388,7 @@ export default function ChatWorkspace({
...
@@ -329,7 +388,7 @@ export default function ChatWorkspace({
</
PromptInput
.
Attachments
>
</
PromptInput
.
Attachments
>
)
:
null
}
)
:
null
}
<
PromptInput
.
Content
>
<
PromptInput
.
Content
>
<
PromptInput
.
TextArea
placeholder=
"
请输入消息内容
"
/>
<
PromptInput
.
TextArea
placeholder=
"
你想了解什么?
"
/>
</
PromptInput
.
Content
>
</
PromptInput
.
Content
>
<
PromptInput
.
Toolbar
>
<
PromptInput
.
Toolbar
>
<
PromptInput
.
ToolbarStart
>
<
PromptInput
.
ToolbarStart
>
...
@@ -354,6 +413,77 @@ export default function ChatWorkspace({
...
@@ -354,6 +413,77 @@ export default function ChatWorkspace({
)
}
)
}
/>
/>
</
ChatAttachmentInput
>
</
ChatAttachmentInput
>
<
Chip
className=
"rc-chat-agent-chip"
size=
"md"
variant=
"secondary"
>
{
activeMode
||
'通用问答'
}
</
Chip
>
<
Autocomplete
aria
-
label=
"选择模型"
className=
"rc-chat-model-select"
isDisabled=
{
isLoading
||
modelListLoading
||
models
.
length
===
0
}
selectedKey=
{
selectedModel
||
null
}
variant=
"secondary"
onSelectionChange=
{
onModelChange
}
>
<
Autocomplete
.
Trigger
title=
{
selectedModelLabel
}
>
<
Autocomplete
.
Value
>
<
span
className=
"block min-w-0 truncate"
>
{
selectedModelLabel
}
</
span
>
</
Autocomplete
.
Value
>
<
Autocomplete
.
Indicator
>
<
DownOutlined
/>
</
Autocomplete
.
Indicator
>
</
Autocomplete
.
Trigger
>
<
Autocomplete
.
Popover
placement=
"top start"
>
<
Autocomplete
.
Filter
filter=
{
filterModel
}
>
<
SearchField
aria
-
label=
"搜索模型"
fullWidth
variant=
"secondary"
>
<
SearchField
.
Group
>
<
SearchField
.
SearchIcon
>
<
SearchOutlined
/>
</
SearchField
.
SearchIcon
>
<
SearchField
.
Input
autoFocus
placeholder=
"输入模型名称搜索"
/>
<
SearchField
.
ClearButton
aria
-
label=
"清空搜索"
>
<
CloseOutlined
/>
</
SearchField
.
ClearButton
>
</
SearchField
.
Group
>
</
SearchField
>
<
ListBox
aria
-
label=
"可用模型"
items=
{
models
}
renderEmptyState=
{
()
=>
(
<
div
className=
"grid min-h-20 place-items-center px-3 py-4 text-sm text-muted"
>
暂无可用模型
</
div
>
)
}
>
{
(
model
)
=>
(
<
ListBox
.
Item
id=
{
model
.
id
}
textValue=
{
model
.
label
}
>
<
span
className=
"min-w-0 flex-1 truncate"
>
{
model
.
label
}
</
span
>
{
model
.
isDefault
?
(
<
span
className=
"text-xs text-muted"
>
默认
</
span
>
)
:
null
}
<
ListBox
.
ItemIndicator
/>
</
ListBox
.
Item
>
)
}
</
ListBox
>
</
Autocomplete
.
Filter
>
</
Autocomplete
.
Popover
>
</
Autocomplete
>
</
PromptInput
.
ToolbarStart
>
</
PromptInput
.
ToolbarStart
>
<
PromptInput
.
ToolbarEnd
>
<
PromptInput
.
ToolbarEnd
>
{
files
.
some
((
file
)
=>
file
.
status
===
'uploading'
)
?
(
{
files
.
some
((
file
)
=>
file
.
status
===
'uploading'
)
?
(
...
...
src/pages/Chat/index.jsx
View file @
1488c6e6
...
@@ -2,9 +2,9 @@ import { useNavbarActions } from '@/components/AppShell';
...
@@ -2,9 +2,9 @@ import { useNavbarActions } from '@/components/AppShell';
import
ChatWorkspace
from
'@/components/ChatWorkspace'
;
import
ChatWorkspace
from
'@/components/ChatWorkspace'
;
import
{
createSseStream
,
useChat
,
useChatAgent
}
from
'@/utils/chatRuntime'
;
import
{
createSseStream
,
useChat
,
useChatAgent
}
from
'@/utils/chatRuntime'
;
import
{
import
{
CommentOutlined
,
DatabaseOutlined
,
DatabaseOutlined
,
FileDoneOutlined
,
FileDoneOutlined
,
FileSearchOutlined
,
}
from
'@ant-design/icons'
;
}
from
'@ant-design/icons'
;
import
{
toast
}
from
'@heroui/react'
;
import
{
toast
}
from
'@heroui/react'
;
import
{
useQuery
}
from
'@umijs/max'
;
import
{
useQuery
}
from
'@umijs/max'
;
...
@@ -47,6 +47,7 @@ import {
...
@@ -47,6 +47,7 @@ import {
getApplicationRuntimeConfig
,
getApplicationRuntimeConfig
,
imageAnalyze
,
imageAnalyze
,
llmChat
,
llmChat
,
llmList
,
queryMcps
,
queryMcps
,
queryRecords
,
queryRecords
,
queryTopics
,
queryTopics
,
...
@@ -63,7 +64,17 @@ import InvoiceAssistant from './invoice';
...
@@ -63,7 +64,17 @@ import InvoiceAssistant from './invoice';
const
SYSTEM_PROMPT
=
`你是一个有用的的智能助手。
const
SYSTEM_PROMPT
=
`你是一个有用的的智能助手。
如果用户提供了上下文,请根据上下文回答问题,不要试图创造一个答案。如果你不知道答案,请回答你只能根据上下文的内容回答问题。`
;
如果用户提供了上下文,请根据上下文回答问题,不要试图创造一个答案。如果你不知道答案,请回答你只能根据上下文的内容回答问题。`
;
const
SELECTED_MODEL_STORAGE_KEY
=
'ai_chat.selected_model'
;
const
AGENT_DEFINITIONS
=
[
const
AGENT_DEFINITIONS
=
[
{
key
:
'general-chat'
,
icon
:
<
CommentOutlined
/>,
label
:
'通用问答'
,
description
:
'自由交流、分析问题或协助写作,不读取业务知识库。'
,
prompt
:
'你是通用 AI 助手。根据用户的问题直接回答,不引用或假设任何未提供的业务知识库内容。'
,
},
{
{
key
:
'knowledge-copilot'
,
key
:
'knowledge-copilot'
,
icon
:
<
DatabaseOutlined
/>,
icon
:
<
DatabaseOutlined
/>,
...
@@ -75,7 +86,7 @@ const AGENT_DEFINITIONS = [
...
@@ -75,7 +86,7 @@ const AGENT_DEFINITIONS = [
{
{
key
:
'audit-checklist'
,
key
:
'audit-checklist'
,
icon
:
<
FileDoneOutlined
/>,
icon
:
<
FileDoneOutlined
/>,
label
:
'审计检查清单
生成
'
,
label
:
'审计检查清单'
,
description
:
'基于审计风险点知识库,生成可执行的审计检查清单。'
,
description
:
'基于审计风险点知识库,生成可执行的审计检查清单。'
,
prompt
:
prompt
:
'你是审计检查清单生成智能体。只能依据系统提供的审计风险点数据库原文组织检查清单,不得添加原文没有的风险、法规、事实或建议;知识库依据不足时必须明确说明。'
,
'你是审计检查清单生成智能体。只能依据系统提供的审计风险点数据库原文组织检查清单,不得添加原文没有的风险、法规、事实或建议;知识库依据不足时必须明确说明。'
,
...
@@ -120,7 +131,7 @@ function normalizeTopic(topic) {
...
@@ -120,7 +131,7 @@ function normalizeTopic(topic) {
function
buildAgentPromptGroup
(
topics
=
[])
{
function
buildAgentPromptGroup
(
topics
=
[])
{
return
{
return
{
key
:
'agents'
,
key
:
'agents'
,
label
:
'
智能体
'
,
label
:
'
选择功能
'
,
children
:
AGENT_DEFINITIONS
.
map
((
agent
)
=>
{
children
:
AGENT_DEFINITIONS
.
map
((
agent
)
=>
{
const
topic
=
const
topic
=
topics
.
find
((
item
)
=>
item
.
name
===
agent
.
topicName
)
||
topics
.
find
((
item
)
=>
item
.
name
===
agent
.
topicName
)
||
...
@@ -128,34 +139,12 @@ function buildAgentPromptGroup(topics = []) {
...
@@ -128,34 +139,12 @@ function buildAgentPromptGroup(topics = []) {
return
{
return
{
...
agent
,
...
agent
,
initialContent
:
agent
.
topicName
===
AUDIT_TOPIC
.
name
?
undefined
:
agent
.
description
,
topic
,
topic
,
};
};
}),
}),
};
};
}
}
function
buildTopicPromptGroup
(
topics
=
[])
{
const
normalizedTopics
=
topics
.
length
>
0
?
topics
:
FALLBACK_TOPICS
;
const
firstTopic
=
normalizedTopics
[
0
];
return
{
key
:
'topics'
,
label
:
'业务话题'
,
children
:
firstTopic
?
[
{
key
:
firstTopic
.
name
,
description
:
firstTopic
.
title
,
icon
:
<
FileSearchOutlined
/>,
topic
:
firstTopic
,
},
]
:
[],
};
}
function
getKnowledgeBaseId
(
kb
)
{
function
getKnowledgeBaseId
(
kb
)
{
return
kb
?.
_id
||
kb
?.
id
||
kb
?.
name
;
return
kb
?.
_id
||
kb
?.
id
||
kb
?.
name
;
}
}
...
@@ -225,6 +214,14 @@ function getConversationTopic(metadata = {}) {
...
@@ -225,6 +214,14 @@ function getConversationTopic(metadata = {}) {
return
null
;
return
null
;
}
}
function
getStoredLlmModel
()
{
try
{
return
localStorage
.
getItem
(
SELECTED_MODEL_STORAGE_KEY
)
||
''
;
}
catch
{
return
''
;
}
}
export
default
forwardRef
((
props
,
ref
)
=>
{
export
default
forwardRef
((
props
,
ref
)
=>
{
const
{
userInfo
,
conversation
,
onAddConversation
}
=
props
;
const
{
userInfo
,
conversation
,
onAddConversation
}
=
props
;
const
conversationAgent
=
useMemo
(
const
conversationAgent
=
useMemo
(
...
@@ -261,6 +258,7 @@ export default forwardRef((props, ref) => {
...
@@ -261,6 +258,7 @@ export default forwardRef((props, ref) => {
const
[
attachedFiles
,
setAttachedFiles
]
=
useState
([]);
const
[
attachedFiles
,
setAttachedFiles
]
=
useState
([]);
const
[
inputValue
,
setInputValue
]
=
useState
(
''
);
const
[
inputValue
,
setInputValue
]
=
useState
(
''
);
const
[
selectedModel
,
setSelectedModel
]
=
useState
(
getStoredLlmModel
);
const
[
auditKnowledgeOpen
,
setAuditKnowledgeOpen
]
=
useState
(
false
);
const
[
auditKnowledgeOpen
,
setAuditKnowledgeOpen
]
=
useState
(
false
);
const
[
auditPromptOpen
,
setAuditPromptOpen
]
=
useState
(
false
);
const
[
auditPromptOpen
,
setAuditPromptOpen
]
=
useState
(
false
);
const
[
auditKnowledgeLoading
,
setAuditKnowledgeLoading
]
=
useState
(
false
);
const
[
auditKnowledgeLoading
,
setAuditKnowledgeLoading
]
=
useState
(
false
);
...
@@ -280,11 +278,54 @@ export default forwardRef((props, ref) => {
...
@@ -280,11 +278,54 @@ export default forwardRef((props, ref) => {
[
configuredTopics
],
[
configuredTopics
],
);
);
const
promptGroups
=
useMemo
(()
=>
{
const
promptGroups
=
useMemo
(()
=>
{
return
[
buildAgentPromptGroup
(
configuredTopics
)];
},
[
configuredTopics
]);
const
{
data
:
availableModels
=
[],
isLoading
:
modelListLoading
}
=
useQuery
({
queryKey
:
[
'llm-models'
],
enabled
:
Boolean
(
userInfo
),
staleTime
:
5
*
60
*
1000
,
queryFn
:
async
()
=>
{
try
{
return
await
llmList
();
}
catch
(
error
)
{
const
fallbackModel
=
await
resolveLlmModel
({
thinking
:
true
});
if
(
!
fallbackModel
)
throw
error
;
return
[
return
[
buildTopicPromptGroup
(
configuredTopics
),
{
buildAgentPromptGroup
(
configuredTopics
),
id
:
fallbackModel
,
label
:
fallbackModel
,
isDefault
:
true
,
unavailable
:
true
,
},
];
];
},
[
configuredTopics
]);
}
},
});
useEffect
(()
=>
{
if
(
!
availableModels
.
length
)
return
;
setSelectedModel
((
current
)
=>
{
const
next
=
availableModels
.
some
((
model
)
=>
model
.
id
===
current
)
?
current
:
availableModels
.
find
((
model
)
=>
model
.
isDefault
)?.
id
||
availableModels
[
0
].
id
;
try
{
localStorage
.
setItem
(
SELECTED_MODEL_STORAGE_KEY
,
next
);
}
catch
{}
return
next
;
});
},
[
availableModels
]);
const
selectModel
=
(
key
)
=>
{
if
(
key
===
null
||
key
===
undefined
)
return
;
const
next
=
String
(
key
);
setSelectedModel
(
next
);
try
{
localStorage
.
setItem
(
SELECTED_MODEL_STORAGE_KEY
,
next
);
}
catch
{}
};
const
loadAuditRecords
=
useCallback
(
const
loadAuditRecords
=
useCallback
(
async
(
force
=
false
)
=>
{
async
(
force
=
false
)
=>
{
...
@@ -787,10 +828,12 @@ export default forwardRef((props, ref) => {
...
@@ -787,10 +828,12 @@ 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
=
await
resolveLlmModel
({
const
model
=
selectedModel
||
(
await
resolveLlmModel
({
long
,
long
,
thinking
:
true
,
thinking
:
true
,
}
);
})
);
const
enable_search
=
conversationTopic
?.
metadata
?.
enable_search
;
const
enable_search
=
conversationTopic
?.
metadata
?.
enable_search
;
const
enable_thinking
=
true
;
const
enable_thinking
=
true
;
const
requestContext
=
{
const
requestContext
=
{
...
@@ -1030,9 +1073,20 @@ export default forwardRef((props, ref) => {
...
@@ -1030,9 +1073,20 @@ export default forwardRef((props, ref) => {
console
.
error
(
error
);
console
.
error
(
error
);
toast
.
danger
(
error
.
message
||
'应用模型配置读取失败'
);
toast
.
danger
(
error
.
message
||
'应用模型配置读取失败'
);
});
});
const
usesAuditKnowledge
=
conversationAgent
?.
key
===
'knowledge-copilot'
||
conversationAgent
?.
key
===
'audit-checklist'
||
conversationTopic
?.
name
===
AUDIT_TOPIC
.
name
;
if
(
usesAuditKnowledge
)
{
loadAuditRecords
().
catch
(()
=>
{});
loadAuditRecords
().
catch
(()
=>
{});
}
}
},
[
loadAuditRecords
,
userInfo
]);
}
},
[
conversationAgent
?.
key
,
conversationTopic
?.
name
,
loadAuditRecords
,
userInfo
,
]);
const
navbarActions
=
useMemo
(
const
navbarActions
=
useMemo
(
()
=>
()
=>
...
@@ -1241,6 +1295,7 @@ export default forwardRef((props, ref) => {
...
@@ -1241,6 +1295,7 @@ export default forwardRef((props, ref) => {
return
(
return
(
<>
<>
<
ChatWorkspace
<
ChatWorkspace
activeMode=
{
conversationAgent
?.
label
||
DEFAULT_AGENT
.
label
}
canConfigure=
{
canConfigure=
{
Boolean
(
conversation
)
&&
Boolean
(
conversation
)
&&
(
conversationAgent
?.
key
===
'audit-checklist'
||
(
conversationAgent
?.
key
===
'audit-checklist'
||
...
@@ -1256,6 +1311,8 @@ export default forwardRef((props, ref) => {
...
@@ -1256,6 +1311,8 @@ export default forwardRef((props, ref) => {
isLoading=
{
loading
}
isLoading=
{
loading
}
knowledgeBases=
{
conversation
?.
metadata
?.
knowledgeBases
||
[]
}
knowledgeBases=
{
conversation
?.
metadata
?.
knowledgeBases
||
[]
}
messages=
{
messages
}
messages=
{
messages
}
modelListLoading=
{
modelListLoading
}
models=
{
availableModels
}
questions=
{
conversationTopic
?.
metadata
?.
questions
||
[]
}
questions=
{
conversationTopic
?.
metadata
?.
questions
||
[]
}
renderAssistantExtras=
{
renderAssistantExtras
}
renderAssistantExtras=
{
renderAssistantExtras
}
suggestionGroups=
{
!
conversation
?
promptGroups
:
[]
}
suggestionGroups=
{
!
conversation
?
promptGroups
:
[]
}
...
@@ -1264,6 +1321,7 @@ export default forwardRef((props, ref) => {
...
@@ -1264,6 +1321,7 @@ export default forwardRef((props, ref) => {
?
conversationAgent
?.
label
||
conversationTopic
?.
title
||
'智能助手'
?
conversationAgent
?.
label
||
conversationTopic
?.
title
||
'智能助手'
:
undefined
:
undefined
}
}
selectedModel=
{
selectedModel
}
userInfo=
{
userInfo
}
userInfo=
{
userInfo
}
onConfigure=
{
()
=>
setAuditPromptOpen
(
true
)
}
onConfigure=
{
()
=>
setAuditPromptOpen
(
true
)
}
onFilesSelected=
{
handleFilesSelected
}
onFilesSelected=
{
handleFilesSelected
}
...
@@ -1275,6 +1333,7 @@ export default forwardRef((props, ref) => {
...
@@ -1275,6 +1333,7 @@ export default forwardRef((props, ref) => {
current
.
filter
((
item
)
=>
item
.
uid
!==
file
.
uid
),
current
.
filter
((
item
)
=>
item
.
uid
!==
file
.
uid
),
)
)
}
}
onModelChange=
{
selectModel
}
onStop=
{
()
=>
abortController
.
current
?.
abort
()
}
onStop=
{
()
=>
abortController
.
current
?.
abort
()
}
onSubmit=
{
()
=>
{
onSubmit=
{
()
=>
{
const
content
=
inputValue
.
trim
();
const
content
=
inputValue
.
trim
();
...
...
src/services/DataService.js
View file @
1488c6e6
...
@@ -340,6 +340,50 @@ export async function getApplicationRuntimeConfig({ force = false } = {}) {
...
@@ -340,6 +340,50 @@ export async function getApplicationRuntimeConfig({ force = false } = {}) {
return
applicationConfigPromise
;
return
applicationConfigPromise
;
}
}
function
normalizeLlmList
(
payload
)
{
const
candidates
=
[
payload
,
payload
?.
data
,
payload
?.
models
,
payload
?.
records
,
payload
?.
result
,
payload
?.
result
?.
data
,
];
const
items
=
candidates
.
find
(
Array
.
isArray
)
||
[];
const
models
=
items
.
map
((
item
)
=>
{
if
(
typeof
item
===
'string'
&&
item
.
trim
())
{
return
{
id
:
item
.
trim
(),
label
:
item
.
trim
()
};
}
if
(
!
item
||
typeof
item
!==
'object'
)
return
null
;
const
id
=
firstNonEmptyString
(
item
.
id
,
item
.
model
,
item
.
name
,
item
.
value
,
item
.
key
,
);
if
(
!
id
)
return
null
;
return
{
...
item
,
id
,
label
:
firstNonEmptyString
(
item
.
display_name
,
item
.
displayName
,
item
.
label
,
item
.
title
,
item
.
name
,
)
||
id
,
};
})
.
filter
(
Boolean
);
return
[...
new
Map
(
models
.
map
((
model
)
=>
[
model
.
id
,
model
])).
values
()];
}
function
resolveLlmUrl
(
llmConfig
=
{})
{
function
resolveLlmUrl
(
llmConfig
=
{})
{
const
envBaseUrl
=
firstNonEmptyString
(
const
envBaseUrl
=
firstNonEmptyString
(
process
.
env
.
UMI_APP_LLM_API_BASE
,
process
.
env
.
UMI_APP_LLM_API_BASE
,
...
@@ -747,6 +791,23 @@ export async function llmChat(data, signal = undefined) {
...
@@ -747,6 +791,23 @@ export async function llmChat(data, signal = undefined) {
return
response
;
return
response
;
}
}
export
async
function
llmList
(
signal
=
undefined
)
{
const
payload
=
await
request
(
'/api/llm/list'
,
{
method
:
'get'
,
signal
,
});
const
models
=
normalizeLlmList
(
payload
);
if
(
!
models
.
length
)
{
throw
new
Error
(
'模型列表为空'
);
}
const
defaultModel
=
await
resolveLlmModel
().
catch
(()
=>
null
);
return
models
.
map
((
model
)
=>
({
...
model
,
isDefault
:
model
.
id
===
defaultModel
,
}));
}
export
async
function
resolveLlmModel
(
options
=
{})
{
export
async
function
resolveLlmModel
(
options
=
{})
{
const
runtimeConfig
=
await
getApplicationRuntimeConfig
();
const
runtimeConfig
=
await
getApplicationRuntimeConfig
();
const
models
=
runtimeConfig
.
llm
?.
models
||
{};
const
models
=
runtimeConfig
.
llm
?.
models
||
{};
...
...
src/tailwind.css
View file @
1488c6e6
...
@@ -522,14 +522,16 @@ body [data-theme='dark'] .login-page {
...
@@ -522,14 +522,16 @@ body [data-theme='dark'] .login-page {
}
}
.rc-chat-conversation-content
{
.rc-chat-conversation-content
{
width
:
min
(
100%
,
56
rem
);
width
:
min
(
100%
,
64
rem
);
margin-inline
:
auto
;
margin-inline
:
auto
;
gap
:
1rem
;
gap
:
1rem
;
padding-block
:
2rem
1rem
;
padding-block
:
2rem
1rem
;
}
}
.rc-chat-conversation-content--empty
{
.rc-chat-conversation-content--empty
{
width
:
min
(
100%
,
48rem
);
width
:
min
(
100%
,
64rem
);
gap
:
2rem
;
padding-block-start
:
0.75rem
;
}
}
.rc-chat-context
{
.rc-chat-context
{
...
@@ -547,12 +549,19 @@ body [data-theme='dark'] .login-page {
...
@@ -547,12 +549,19 @@ body [data-theme='dark'] .login-page {
}
}
.rc-chat-suggestions
{
.rc-chat-suggestions
{
width
:
100%
;
width
:
min
(
100%
,
62rem
);
margin-inline
:
auto
;
}
}
.rc-chat-suggestion-items
{
.rc-chat-suggestion-items
{
width
:
100%
;
width
:
100%
;
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
1.25rem
;
}
.rc-chat-suggestion-items
[
data-slot
=
'prompt-suggestion-item'
]
{
min-height
:
12rem
;
align-content
:
start
;
}
}
.rc-chat-suggestion-items--single
{
.rc-chat-suggestion-items--single
{
...
@@ -561,13 +570,51 @@ body [data-theme='dark'] .login-page {
...
@@ -561,13 +570,51 @@ body [data-theme='dark'] .login-page {
.rc-chat-composer-stack
{
.rc-chat-composer-stack
{
display
:
grid
;
display
:
grid
;
width
:
min
(
100%
,
48
rem
);
width
:
min
(
100%
,
64
rem
);
min-height
:
0
;
min-height
:
0
;
flex
:
none
;
flex
:
none
;
margin-inline
:
auto
;
margin-inline
:
auto
;
gap
:
0.75rem
;
gap
:
0.75rem
;
}
}
.rc-chat-empty-state
{
gap
:
0.75rem
;
}
.rc-chat-empty-state
h1
{
margin
:
0.5rem
0
0
;
font-size
:
1.75rem
;
font-weight
:
600
;
line-height
:
2.25rem
;
}
.rc-chat-empty-state
p
{
margin
:
0
;
color
:
var
(
--muted
);
font-size
:
1rem
;
line-height
:
1.5rem
;
}
.rc-chat-agent-icon
{
color
:
var
(
--accent
);
}
.rc-chat-agent-icon
.anticon
{
display
:
inline-flex
;
font-size
:
1.5rem
;
line-height
:
1
;
}
.rc-chat-model-select
{
width
:
12rem
;
}
.rc-chat-agent-chip
{
height
:
100%
;
min-height
:
2.25rem
;
align-self
:
center
;
}
.rc-invoice-workspace
{
.rc-invoice-workspace
{
display
:
grid
;
display
:
grid
;
height
:
100%
;
height
:
100%
;
...
@@ -602,4 +649,12 @@ body [data-theme='dark'] .login-page {
...
@@ -602,4 +649,12 @@ body [data-theme='dark'] .login-page {
.rc-chat-suggestion-items
{
.rc-chat-suggestion-items
{
grid-template-columns
:
minmax
(
0
,
1
fr
);
grid-template-columns
:
minmax
(
0
,
1
fr
);
}
}
.rc-chat-agent-chip
{
min-height
:
2.5rem
;
}
.rc-chat-conversation-content--empty
{
padding-block-start
:
2rem
;
}
}
}
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