Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
Internet-hospital
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
yuguo
Internet-hospital
Commits
fbf70318
Commit
fbf70318
authored
Mar 05, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
da795257
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
259 additions
and
107 deletions
+259
-107
server/internal/agent/agents.go
server/internal/agent/agents.go
+11
-2
server/internal/agent/seed_prompts.go
server/internal/agent/seed_prompts.go
+26
-14
server/internal/agent/service.go
server/internal/agent/service.go
+55
-14
server/pkg/agent/tools/dashboard_stats.go
server/pkg/agent/tools/dashboard_stats.go
+8
-8
server/pkg/agent/tools/dashboard_trend.go
server/pkg/agent/tools/dashboard_trend.go
+5
-5
server/pkg/agent/tools/income_stats.go
server/pkg/agent/tools/income_stats.go
+3
-3
web/src/components/GlobalAIFloat/ChatPanel.tsx
web/src/components/GlobalAIFloat/ChatPanel.tsx
+21
-24
web/src/components/GlobalAIFloat/ToolResultCard.tsx
web/src/components/GlobalAIFloat/ToolResultCard.tsx
+130
-37
No files found.
server/internal/agent/agents.go
View file @
fbf70318
...
...
@@ -59,14 +59,23 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"trigger_workflow"
,
"request_human_review"
,
"list_knowledge_collections"
,
"send_notification"
,
"query_drug"
,
"search_medical_knowledge"
,
"navigate_page"
,
// v17:
业务
数据查询
// v17:
问诊 & 处方
数据查询
"query_consultation_list"
,
"query_consultation_detail"
,
"query_prescription_list"
,
"query_prescription_detail"
,
"generate_tool"
,
"query_waiting_queue"
,
// v17: 医生/科室/患者
"query_doctor_list"
,
"query_doctor_detail"
,
"query_department_list"
,
"query_patient_profile"
,
"query_health_metrics"
,
// v17: 慢病 & 排班
"query_chronic_records"
,
"query_renewal_requests"
,
"query_doctor_schedule"
,
// v17: 收入统计(管理员查看全局)
"query_income_stats"
,
"query_income_records"
,
// v17: 管理统计 + 用户管理 + 系统日志 + 订单
"query_dashboard_stats"
,
"query_dashboard_trend"
,
"query_user_list"
,
"query_system_logs"
,
"query_order_list"
,
"query_order_detail"
,
"generate_tool"
,
})
return
[]
model
.
AgentDefinition
{
...
...
server/internal/agent/seed_prompts.go
View file @
fbf70318
...
...
@@ -9,7 +9,7 @@ import (
// currentPromptVersion 当前代码中提示词模板的版本号
// 每次修改提示词内容时递增此值,ensurePromptTemplates 会自动同步到数据库
const
currentPromptVersion
=
2
const
currentPromptVersion
=
3
// ensurePromptTemplates 确保所有内置提示词模板存在于数据库中(种子数据)
// 逻辑:不存在则创建;已存在但版本低于代码版本则更新内容
...
...
@@ -37,7 +37,7 @@ func ensurePromptTemplates() {
5. **药品查询**:查询药品信息、规格、用法和注意事项
6. **问诊管理**:查询问诊列表和详情,帮助患者创建新的问诊(需指定医生和主诉)
7. **处方查询**:查询处方列表和详情(药品明细、用法用量、费用)
8. **健康档案**:查询个
���
健康档案、健康指标(血压/血糖/心率/体温)趋势、检验报告及AI解读
8. **健康档案**:查询个
人
健康档案、健康指标(血压/血糖/心率/体温)趋势、检验报告及AI解读
9. **慢病管理**:查询慢病档案、创建慢病记录、申请续方、查看续方状态
10. **支付订单**:查询支付订单列表和详情,了解订单状态
...
...
@@ -50,7 +50,7 @@ func ensurePromptTemplates() {
- 当患者问"我的健康数据"时,使用 query_health_metrics 查询
- 当患者问"我的订单/支付"时,用 query_order_list 查询,用 query_order_detail 查详情
- 当患者问慢病/续方时,用 query_chronic_records 和 query_renewal_requests 查询
- 当患者要记录血压/
���
糖等指标时,用 record_health_metric 记录
- 当患者要记录血压/
血
糖等指标时,用 record_health_metric 记录
- 当患者查排班时,用 query_doctor_schedule 查询
- 创建问诊前需确认患者提供了医生ID和主诉信息
...
...
@@ -61,7 +61,7 @@ func ensurePromptTemplates() {
- 关注患者的用药依从性和健康状况变化
- 所有医疗建议仅供参考,请以专业医生判断为准
页
��
导航能力:
页
面
导航能力:
- 你可以使用 navigate_page 工具为用户准备页面导航
- 【重要】调用工具后,页面不会自动打开,用户需要点击工具结果中的"打开页面"按钮才能跳转
- 因此你的回复应该说"我已为您准备好XXX页面,请点击下方按钮打开",而不是"已为您打开XXX页面"
...
...
@@ -132,27 +132,39 @@ func ensurePromptTemplates() {
你的核心能力:
1. **运营数据**:查询仪表盘统计(用户数、医生数、问诊量、收入)和运营趋势
2. **
Agent监控**:调用其他Agent获取信息,监控Agent运行状态
3. **
工作流管理**:触发和查询工作流执行状态
4. **
知识库管理**:浏览知识库集合,了解知识库使用情况
5. **
人工审核**:发起和管理人工审核任务
6. **
通知管理**:发送系统通知
7. **
药品/医学查询**:查询药品信息和医学知识辅助决策
8. **
业务数据查询**:查看问诊记录、处方记录、支付订单,支持全局查看
2. **
医生管理**:查询医生列表、医生详情、排班信息、收入统计
3. **
科室管理**:查询科室列表
4. **
患者管理**:查询患者档案、健康指标
5. **
问诊管理**:查看问诊记录、等候队列
6. **
处方管理**:查看处方记录
7. **
慢病管理**:查看慢病记录、续方申请
8. **
订单管理**:查看支付订单
9. **用户管理**:查询系统用户列表,按角色/状态/关键词搜索
10. **系统日志**:查看系统操作日志,按操作类型和资源过滤
10. **系统日志**:查看系统操作日志
11. **Agent监控**:调用其他Agent获取信息,监控Agent运行状态
12. **工作流管理**:触发和查询工作流执行状态
13. **知识库管理**:浏览知识库集合
14. **通知管理**:发送系统通知
工具使用指南:
- 查运营数据用 query_dashboard_stats,查趋势用 query_dashboard_trend
- 查用户列表用 query_user_list,查系统日志用 query_system_logs
- 查医生列表用 query_doctor_list,查医生详情用 query_doctor_detail
- 查科室列表用 query_department_list
- 查患者档案用 query_patient_profile,查健康指标用 query_health_metrics
- 查问诊数据用 query_consultation_list / query_consultation_detail
- 查等候队列用 query_waiting_queue
- 查处方数据用 query_prescription_list / query_prescription_detail
- 查慢病记录用 query_chronic_records,查续方申请用 query_renewal_requests
- 查排班信息用 query_doctor_schedule
- 查收入统计用 query_income_stats,查收入明细用 query_income_records
- 查订单数据用 query_order_list / query_order_detail
- 查用户列表用 query_user_list,查系统日志用 query_system_logs
- 需要新的数据查询能力时,使用 generate_tool 动态生成 SQL 工具
使用原则:
- 以简洁专业的方式回答管理员的问题
- 主动使用工具获取真实数据
- 主动使用工具获取真实数据,不要说工具不可用
- 当用户询问某类数据时,直接调用对应工具查询
- 提供可操作的建议和方案
- 用中文回答
...
...
server/internal/agent/service.go
View file @
fbf70318
...
...
@@ -281,7 +281,16 @@ func (s *AgentService) Chat(ctx context.Context, agentID, userID, userRole, sess
var
history
[]
ai
.
ChatMessage
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
history
)
// 富格式 history 含 tool_calls/meta 等字段,只提取 role+content 给 AI 推理
var
rawHistory
[]
map
[
string
]
interface
{}
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
rawHistory
)
for
_
,
h
:=
range
rawHistory
{
role
,
_
:=
h
[
"role"
]
.
(
string
)
content
,
_
:=
h
[
"content"
]
.
(
string
)
if
role
!=
""
{
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
role
,
Content
:
content
})
}
}
}
input
:=
agent
.
AgentInput
{
...
...
@@ -296,12 +305,25 @@ func (s *AgentService) Chat(ctx context.Context, agentID, userID, userRole, sess
return
nil
,
err
}
// 更新会话
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
"user"
,
Content
:
message
},
ai
.
ChatMessage
{
Role
:
"assistant"
,
Content
:
output
.
Response
},
// 更新会话(富消息格式,包含推理信息)
var
richHistory
[]
map
[
string
]
interface
{}
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
richHistory
)
}
richHistory
=
append
(
richHistory
,
map
[
string
]
interface
{}{
"role"
:
"user"
,
"content"
:
message
},
map
[
string
]
interface
{}{
"role"
:
"assistant"
,
"content"
:
output
.
Response
,
"tool_calls"
:
output
.
ToolCalls
,
"meta"
:
map
[
string
]
interface
{}{
"iterations"
:
output
.
Iterations
,
"tokens"
:
output
.
TotalTokens
,
"agent_id"
:
agentID
,
},
},
)
historyJSON
,
_
:=
json
.
Marshal
(
h
istory
)
historyJSON
,
_
:=
json
.
Marshal
(
richH
istory
)
contextJSON
,
_
:=
json
.
Marshal
(
contextData
)
currentPage
:=
""
...
...
@@ -392,15 +414,25 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
sessionJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"session_id"
:
sessionID
})
emit
(
"session"
,
string
(
sessionJSON
))
// saveSession 统一的会话保存函数
saveSession
:=
func
(
responseText
string
,
tokens
int
)
{
var
history
[]
ai
.
ChatMessage
// saveSession 统一的会话保存函数(保存富消息格式,包含推理信息)
saveSession
:=
func
(
responseText
string
,
tokens
int
,
toolCalls
interface
{},
iterations
int
)
{
// 使用富消息格式保存,前端恢复时可还原 toolCalls 和 meta
var
history
[]
map
[
string
]
interface
{}
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
history
)
}
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
"user"
,
Content
:
message
},
ai
.
ChatMessage
{
Role
:
"assistant"
,
Content
:
responseText
},
map
[
string
]
interface
{}{
"role"
:
"user"
,
"content"
:
message
},
map
[
string
]
interface
{}{
"role"
:
"assistant"
,
"content"
:
responseText
,
"tool_calls"
:
toolCalls
,
"meta"
:
map
[
string
]
interface
{}{
"iterations"
:
iterations
,
"tokens"
:
tokens
,
"agent_id"
:
agentID
,
},
},
)
historyJSON
,
_
:=
json
.
Marshal
(
history
)
contextJSON
,
_
:=
json
.
Marshal
(
contextData
)
...
...
@@ -473,7 +505,7 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
"mode"
:
result
.
Mode
,
})
emit
(
"done"
,
string
(
doneData
))
saveSession
(
result
.
FinalResponse
,
0
)
saveSession
(
result
.
FinalResponse
,
0
,
nil
,
len
(
result
.
StepResults
)
)
return
}
}
...
...
@@ -482,7 +514,16 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
// 普通 Agent 执行
var
history
[]
ai
.
ChatMessage
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
history
)
// 富格式 history 含 tool_calls/meta 等字段,只提取 role+content 给 AI 推理
var
rawHistory
[]
map
[
string
]
interface
{}
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
rawHistory
)
for
_
,
h
:=
range
rawHistory
{
role
,
_
:=
h
[
"role"
]
.
(
string
)
content
,
_
:=
h
[
"content"
]
.
(
string
)
if
role
!=
""
{
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
role
,
Content
:
content
})
}
}
}
input
:=
agent
.
AgentInput
{
...
...
@@ -512,7 +553,7 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
}
}
saveSession
(
output
.
Response
,
output
.
TotalTokens
)
saveSession
(
output
.
Response
,
output
.
TotalTokens
,
output
.
ToolCalls
,
output
.
Iterations
)
// 记录执行日志
inputJSON
,
_
:=
json
.
Marshal
(
input
)
...
...
server/pkg/agent/tools/dashboard_stats.go
View file @
fbf70318
...
...
@@ -38,23 +38,23 @@ func (t *DashboardStatsTool) Execute(ctx context.Context, params map[string]inte
// 总用户数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM users WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"
total_users
"
]
=
count
stats
[
"
总用户数
"
]
=
count
// 总医生数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM users WHERE role = 'doctor' AND deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"
total_doctors
"
]
=
count
stats
[
"
总医生数
"
]
=
count
// 总问诊数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM consultations WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"
total_consultations
"
]
=
count
stats
[
"
总问诊数
"
]
=
count
// 今日问诊数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM consultations WHERE DATE(created_at) = ? AND deleted_at IS NULL"
,
today
)
.
Scan
(
&
count
)
stats
[
"
today_consultations
"
]
=
count
stats
[
"
今日问诊
"
]
=
count
// 待审核医生数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM doctor_reviews WHERE status = 'pending'"
)
.
Scan
(
&
count
)
stats
[
"
pending_doctor_reviews
"
]
=
count
stats
[
"
待审核医生
"
]
=
count
// 今日收入(已支付订单)
var
todayRevenue
int64
...
...
@@ -62,7 +62,7 @@ func (t *DashboardStatsTool) Execute(ctx context.Context, params map[string]inte
"SELECT COALESCE(SUM(amount), 0) FROM payment_orders WHERE DATE(paid_at) = ? AND status = 'paid' AND deleted_at IS NULL"
,
today
,
)
.
Scan
(
&
todayRevenue
)
stats
[
"
revenue_today
"
]
=
todayRevenue
stats
[
"
今日收入
"
]
=
todayRevenue
// 本月收入
var
monthRevenue
int64
...
...
@@ -70,11 +70,11 @@ func (t *DashboardStatsTool) Execute(ctx context.Context, params map[string]inte
"SELECT COALESCE(SUM(amount), 0) FROM payment_orders WHERE paid_at >= ? AND status = 'paid' AND deleted_at IS NULL"
,
monthStart
,
)
.
Scan
(
&
monthRevenue
)
stats
[
"
revenue_month
"
]
=
monthRevenue
stats
[
"
本月收入
"
]
=
monthRevenue
// 总处方数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM prescriptions WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"
total_prescriptions
"
]
=
count
stats
[
"
总处方数
"
]
=
count
return
stats
,
nil
}
server/pkg/agent/tools/dashboard_trend.go
View file @
fbf70318
...
...
@@ -62,9 +62,9 @@ func (t *DashboardTrendTool) Execute(ctx context.Context, params map[string]inte
var
consultCount
,
completedCount
int
if
err
:=
rows
.
Scan
(
&
date
,
&
consultCount
,
&
completedCount
);
err
==
nil
{
results
=
append
(
results
,
map
[
string
]
interface
{}{
"
date"
:
date
,
"
consult_count"
:
consultCount
,
"
completed_count
"
:
completedCount
,
"
日期"
:
date
,
"
问诊量"
:
consultCount
,
"
完成量
"
:
completedCount
,
})
}
}
...
...
@@ -73,7 +73,7 @@ func (t *DashboardTrendTool) Execute(ctx context.Context, params map[string]inte
}
return
map
[
string
]
interface
{}{
"
trend
"
:
results
,
"
days
"
:
days
,
"
趋势数据
"
:
results
,
"
天数
"
:
days
,
},
nil
}
server/pkg/agent/tools/income_stats.go
View file @
fbf70318
...
...
@@ -64,8 +64,8 @@ func (t *IncomeStatsTool) Execute(ctx context.Context, params map[string]interfa
)
.
Scan
(
&
monthConsults
)
return
map
[
string
]
interface
{}{
"
total_balance
"
:
totalBalance
,
"
month_income
"
:
monthIncome
,
"
month_consults
"
:
monthConsults
,
"
可提现余额
"
:
totalBalance
,
"
本月收入
"
:
monthIncome
,
"
本月问诊量
"
:
monthConsults
,
},
nil
}
web/src/components/GlobalAIFloat/ChatPanel.tsx
View file @
fbf70318
...
...
@@ -102,13 +102,20 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ role, patientContext }) => {
const
latest
=
sessions
[
0
];
setSessionId
(
latest
.
session_id
);
// 尝试恢复历史消息
// 尝试恢复历史消息
(富格式:包含 tool_calls 和 meta)
try
{
const
history
=
JSON
.
parse
(
latest
.
history
||
'
[]
'
)
as
{
role
:
string
;
content
:
string
}[];
const
history
=
JSON
.
parse
(
latest
.
history
||
'
[]
'
)
as
Array
<
{
role
:
string
;
content
:
string
;
tool_calls
?:
ToolCall
[];
meta
?:
{
iterations
?:
number
;
tokens
?:
number
;
agent_id
?:
string
};
}
>
;
if
(
history
.
length
>
0
)
{
const
restored
:
ChatMessage
[]
=
history
.
map
(
h
=>
({
role
:
h
.
role
as
'
user
'
|
'
assistant
'
,
content
:
h
.
content
,
toolCalls
:
h
.
tool_calls
||
undefined
,
meta
:
h
.
meta
||
undefined
,
timestamp
:
new
Date
(),
}));
setMessages
(
restored
);
...
...
@@ -283,28 +290,18 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ role, patientContext }) => {
return
(
<
div
style=
{
{
marginTop
:
6
}
}
>
{
toolCalls
.
map
((
tc
,
idx
)
=>
(
<
div
key=
{
idx
}
>
<
ToolCallCard
toolName=
{
tc
.
tool_name
}
arguments=
{
tc
.
arguments
}
callId=
{
tc
.
call_id
}
status=
{
tc
.
success
?
'
success
'
:
isStreaming
&&
idx
===
toolCalls
.
length
-
1
?
'
running
'
:
tc
.
result
?.
error
?
'
error
'
:
'
running
'
}
/>
{
tc
.
result
&&
(
tc
.
success
||
tc
.
result
.
error
)
&&
(
<
div
style=
{
{
marginTop
:
4
,
marginLeft
:
4
}
}
>
<
ToolResultCard
toolName=
{
tc
.
tool_name
}
success=
{
tc
.
success
}
result=
{
tc
.
result
}
/>
</
div
>
)
}
</
div
>
<
ToolCallCard
key=
{
idx
}
toolName=
{
tc
.
tool_name
}
arguments=
{
tc
.
arguments
}
callId=
{
tc
.
call_id
}
status=
{
tc
.
success
?
'
success
'
:
isStreaming
&&
idx
===
toolCalls
.
length
-
1
?
'
running
'
:
tc
.
result
?.
error
?
'
error
'
:
'
running
'
}
/>
))
}
</
div
>
);
...
...
web/src/components/GlobalAIFloat/ToolResultCard.tsx
View file @
fbf70318
...
...
@@ -230,11 +230,45 @@ const JsonResult: React.FC<{ data: unknown; label?: string }> = ({ data, label }
);
};
/** 数据表格 */
const
DataTable
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
[]
}
>
=
({
data
})
=>
{
/** 列表表格(统一样式) */
const
ListTable
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
[];
toolName
:
string
;
columns
:
string
[]
}
>
=
({
data
,
toolName
,
columns
})
=>
{
if
(
!
data
.
length
)
return
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#8c8c8c
'
,
padding
:
4
}
}
>
无数据
</
div
>;
const
display
=
data
.
slice
(
0
,
MAX_LIST_ITEMS
);
return
(
<
div
style=
{
{
border
:
'
1px solid #f0f0f0
'
,
borderRadius
:
6
,
overflow
:
'
hidden
'
}
}
>
<
div
style=
{
{
overflowX
:
'
auto
'
}
}
>
<
table
style=
{
{
width
:
'
100%
'
,
fontSize
:
11
,
borderCollapse
:
'
collapse
'
}
}
>
<
thead
>
<
tr
style=
{
{
background
:
'
#fafafa
'
}
}
>
{
columns
.
map
(
col
=>
(
<
th
key=
{
col
}
style=
{
{
padding
:
'
6px 8px
'
,
textAlign
:
'
left
'
,
fontWeight
:
500
,
color
:
'
#595959
'
,
whiteSpace
:
'
nowrap
'
,
borderBottom
:
'
1px solid #f0f0f0
'
}
}
>
{
col
}
</
th
>
))
}
</
tr
>
</
thead
>
<
tbody
>
{
display
.
map
((
row
,
i
)
=>
(
<
tr
key=
{
i
}
style=
{
{
borderBottom
:
i
<
display
.
length
-
1
?
'
1px solid #f9fafb
'
:
'
none
'
}
}
>
{
columns
.
map
(
col
=>
(
<
td
key=
{
col
}
style=
{
{
padding
:
'
6px 8px
'
,
color
:
'
#374151
'
,
whiteSpace
:
'
nowrap
'
,
maxWidth
:
150
,
overflow
:
'
hidden
'
,
textOverflow
:
'
ellipsis
'
}
}
>
{
row
[
col
]
==
null
?
'
-
'
:
String
(
row
[
col
])
}
</
td
>
))
}
</
tr
>
))
}
</
tbody
>
</
table
>
</
div
>
{
data
.
length
>
MAX_LIST_ITEMS
&&
<
ViewMoreTip
total=
{
data
.
length
}
toolName=
{
toolName
}
/>
}
</
div
>
);
};
const
DataTable
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
[];
toolName
?:
string
}
>
=
({
data
,
toolName
})
=>
{
if
(
!
data
.
length
)
return
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#8c8c8c
'
,
padding
:
4
}
}
>
无数据
</
div
>;
const
keys
=
Object
.
keys
(
data
[
0
]).
slice
(
0
,
6
);
const
display
=
data
.
slice
(
0
,
8
);
const
display
=
data
.
slice
(
0
,
MAX_LIST_ITEMS
);
return
(
<
div
style=
{
{
overflowX
:
'
auto
'
,
border
:
'
1px solid #f0f0f0
'
,
borderRadius
:
6
}
}
>
...
...
@@ -260,11 +294,7 @@ const DataTable: React.FC<{ data: Record<string, unknown>[] }> = ({ data }) => {
))
}
</
tbody
>
</
table
>
{
data
.
length
>
8
&&
(
<
div
style=
{
{
padding
:
'
3px 6px
'
,
fontSize
:
10
,
color
:
'
#8c8c8c
'
,
background
:
'
#fafafa
'
,
borderTop
:
'
1px solid #f0f0f0
'
}
}
>
显示前8条,共
{
data
.
length
}
条
</
div
>
)
}
{
data
.
length
>
MAX_LIST_ITEMS
&&
<
ViewMoreTip
total=
{
data
.
length
}
toolName=
{
toolName
||
''
}
/>
}
</
div
>
);
};
...
...
@@ -419,6 +449,59 @@ const LabReportCard: React.FC<{ data: Record<string, unknown> }> = ({ data }) =>
</
div
>
);
// ── 列表展示限制 & 跳转映射 ──
const
MAX_LIST_ITEMS
=
10
;
/** 工具名 → 对应管理页面路由 */
const
TOOL_PAGE_MAP
:
Record
<
string
,
{
route
:
string
;
label
:
string
}
>
=
{
query_consultation_list
:
{
route
:
'
/admin/consultations
'
,
label
:
'
问诊管理
'
},
query_waiting_queue
:
{
route
:
'
/doctor/workbench
'
,
label
:
'
医生工作台
'
},
query_prescription_list
:
{
route
:
'
/admin/prescriptions
'
,
label
:
'
处方管理
'
},
query_drug
:
{
route
:
'
/admin/pharmacy
'
,
label
:
'
药品管理
'
},
search_medicine_catalog
:
{
route
:
'
/admin/pharmacy
'
,
label
:
'
药品管理
'
},
query_order_list
:
{
route
:
'
/admin/orders
'
,
label
:
'
订单管理
'
},
query_health_metrics
:
{
route
:
'
/patient/health
'
,
label
:
'
健康指标
'
},
query_lab_reports
:
{
route
:
'
/patient/lab-reports
'
,
label
:
'
化验报告
'
},
recommend_department
:
{
route
:
'
/admin/departments
'
,
label
:
'
科室管理
'
},
query_department_list
:
{
route
:
'
/admin/departments
'
,
label
:
'
科室管理
'
},
};
/** 查看更多提示 */
const
ViewMoreTip
:
React
.
FC
<
{
total
:
number
;
toolName
:
string
}
>
=
({
total
,
toolName
})
=>
{
const
page
=
TOOL_PAGE_MAP
[
toolName
];
const
handleNavigate
=
()
=>
{
if
(
page
)
{
window
.
dispatchEvent
(
new
CustomEvent
(
'
ai-action
'
,
{
detail
:
{
action
:
'
navigate
'
,
page
:
page
.
route
}
}));
}
};
return
(
<
div
style=
{
{
padding
:
'
4px 8px
'
,
fontSize
:
11
,
color
:
'
#8c8c8c
'
,
background
:
'
#fafafa
'
,
borderRadius
:
'
0 0 6px 6px
'
,
border
:
'
1px solid #f0f0f0
'
,
borderTop
:
'
none
'
,
display
:
'
flex
'
,
justifyContent
:
'
space-between
'
,
alignItems
:
'
center
'
}
}
>
<
span
>
已展示前
{
MAX_LIST_ITEMS
}
条,共
{
total
}
条
</
span
>
{
page
&&
(
<
button
onClick=
{
handleNavigate
}
style=
{
{
fontSize
:
11
,
color
:
'
#1677ff
'
,
background
:
'
none
'
,
border
:
'
none
'
,
cursor
:
'
pointer
'
,
padding
:
0
}
}
>
前往
{
page
.
label
}
查看全部 →
</
button
>
)
}
</
div
>
);
};
/** 渲染列表(统一限制 + 查看更多) */
function
renderList
<
T
>
(
items
:
T
[],
toolName
:
string
,
renderItem
:
(
item
:
T
,
index
:
number
)
=>
React
.
ReactNode
,
)
{
const
display
=
items
.
slice
(
0
,
MAX_LIST_ITEMS
);
return
(
<>
{
display
.
map
((
item
,
i
)
=>
renderItem
(
item
,
i
))
}
{
items
.
length
>
MAX_LIST_ITEMS
&&
<
ViewMoreTip
total=
{
items
.
length
}
toolName=
{
toolName
}
/>
}
</>
);
}
// ── 主组件 ──
const
ToolResultCard
:
React
.
FC
<
ToolResultCardProps
>
=
({
toolName
,
success
,
result
})
=>
{
...
...
@@ -434,88 +517,98 @@ const ToolResultCard: React.FC<ToolResultCardProps> = ({ toolName, success, resu
const
data
=
tryParseJSON
(
result
.
data
);
console
.
log
(
'
[ToolResultCard] parsed data:
'
,
data
);
// 提取嵌套的列表数据(工具返回 {consultations: [...], total: N} 格式)
let
listData
=
data
;
if
(
data
&&
!
Array
.
isArray
(
data
)
&&
typeof
data
===
'
object
'
)
{
const
keys
=
[
'
consultations
'
,
'
prescriptions
'
,
'
orders
'
,
'
departments
'
,
'
doctors
'
,
'
users
'
,
'
collections
'
];
for
(
const
key
of
keys
)
{
if
(
Array
.
isArray
((
data
as
Record
<
string
,
unknown
>
)[
key
]))
{
listData
=
(
data
as
Record
<
string
,
unknown
>
)[
key
];
break
;
}
}
}
// 按工具类型定制渲染
switch
(
toolName
)
{
case
'
query_drug
'
:
case
'
search_medicine_catalog
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
map
((
d
,
i
)
=>
<
DrugCard
key=
{
i
}
data=
{
d
}
/>)
}
</>
;
if
(
Array
.
isArray
(
listD
ata
))
{
return
renderList
(
listData
as
Record
<
string
,
unknown
>
[],
toolName
,
(
d
,
i
)
=>
<
DrugCard
key=
{
i
}
data=
{
d
}
/>)
;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
DrugCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
DrugCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
recommend_department
'
:
case
'
query_department_list
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
map
((
d
,
i
)
=>
<
DepartmentCard
key=
{
i
}
data=
{
d
}
/>)
}
</>
;
if
(
Array
.
isArray
(
listD
ata
))
{
return
renderList
(
listData
as
Record
<
string
,
unknown
>
[],
toolName
,
(
d
,
i
)
=>
<
DepartmentCard
key=
{
i
}
data=
{
d
}
/>)
;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
DepartmentCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
DepartmentCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
check_drug_interaction
'
:
case
'
check_contraindication
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
InteractionCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
InteractionCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
navigate_page
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
NavigateCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
NavigateCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_consultation_list
'
:
case
'
query_consultation_detail
'
:
case
'
create_consultation
'
:
case
'
accept_consultation
'
:
case
'
query_waiting_queue
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<
>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
ConsultationCard
key=
{
i
}
data=
{
d
}
/>)
}
<
/>;
if
(
Array
.
isArray
(
listD
ata
))
{
return
<
ListTable
data=
{
listData
as
Record
<
string
,
unknown
>
[]
}
toolName=
{
toolName
}
columns=
{
[
'
serial_number
'
,
'
patient_name
'
,
'
doctor_name
'
,
'
department_name
'
,
'
chief_complaint
'
,
'
status
'
,
'
created_at
'
]
}
/>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
ConsultationCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
ConsultationCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_prescription_list
'
:
case
'
query_prescription_detail
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<
>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
PrescriptionCard
key=
{
i
}
data=
{
d
}
/>)
}
<
/>;
if
(
Array
.
isArray
(
listD
ata
))
{
return
<
ListTable
data=
{
listData
as
Record
<
string
,
unknown
>
[]
}
toolName=
{
toolName
}
columns=
{
[
'
id
'
,
'
patient_name
'
,
'
doctor_name
'
,
'
status
'
,
'
created_at
'
]
}
/>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
PrescriptionCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
PrescriptionCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_health_metrics
'
:
case
'
record_health_metric
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
HealthMetricCard
key=
{
i
}
data=
{
d
}
/>)
}
</>
;
if
(
Array
.
isArray
(
listD
ata
))
{
return
renderList
(
listData
as
Record
<
string
,
unknown
>
[],
toolName
,
(
d
,
i
)
=>
<
HealthMetricCard
key=
{
i
}
data=
{
d
}
/>)
;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
HealthMetricCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
HealthMetricCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_lab_reports
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
LabReportCard
key=
{
i
}
data=
{
d
}
/>)
}
</>
;
if
(
Array
.
isArray
(
listD
ata
))
{
return
renderList
(
listData
as
Record
<
string
,
unknown
>
[],
toolName
,
(
d
,
i
)
=>
<
LabReportCard
key=
{
i
}
data=
{
d
}
/>)
;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
LabReportCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
LabReportCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_order_list
'
:
case
'
query_order_detail
'
:
{
if
(
Array
.
isArray
(
d
ata
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
OrderCard
key=
{
i
}
data=
{
d
}
/>)
}
</>
;
if
(
Array
.
isArray
(
listD
ata
))
{
return
renderList
(
listData
as
Record
<
string
,
unknown
>
[],
toolName
,
(
d
,
i
)
=>
<
OrderCard
key=
{
i
}
data=
{
d
}
/>)
;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
OrderCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
OrderCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_income_stats
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
IncomeCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
IncomeCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_dashboard_stats
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
DashboardCard
data=
{
d
ata
as
Record
<
string
,
unknown
>
}
/>;
if
(
listData
&&
!
Array
.
isArray
(
listData
))
return
<
DashboardCard
data=
{
listD
ata
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
}
// 数组数据 → 表格
if
(
Array
.
isArray
(
data
)
&&
data
.
length
>
0
&&
typeof
d
ata
[
0
]
===
'
object
'
)
{
return
<
DataTable
data=
{
data
as
Record
<
string
,
unknown
>
[]
}
/>;
if
(
Array
.
isArray
(
listData
)
&&
listData
.
length
>
0
&&
typeof
listD
ata
[
0
]
===
'
object
'
)
{
return
<
DataTable
data=
{
listData
as
Record
<
string
,
unknown
>
[]
}
toolName=
{
toolName
}
/>;
}
// 通用 JSON 展示
...
...
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