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
da795257
Commit
da795257
authored
Mar 05, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
79589e01
Changes
49
Hide whitespace changes
Inline
Side-by-side
Showing
49 changed files
with
5150 additions
and
243 deletions
+5150
-243
.claude/settings.local.json
.claude/settings.local.json
+2
-1
docs/plans/2026-03-05-ai-tools-upgrade-batch1.md
docs/plans/2026-03-05-ai-tools-upgrade-batch1.md
+1531
-0
server/cmd/api/main.go
server/cmd/api/main.go
+66
-0
server/internal/agent/agents.go
server/internal/agent/agents.go
+36
-0
server/internal/agent/handler.go
server/internal/agent/handler.go
+69
-25
server/internal/agent/init.go
server/internal/agent/init.go
+152
-1
server/internal/agent/seed_prompts.go
server/internal/agent/seed_prompts.go
+84
-22
server/internal/agent/service.go
server/internal/agent/service.go
+137
-125
server/internal/model/chronic.go
server/internal/model/chronic.go
+3
-3
server/internal/service/chronic/handler.go
server/internal/service/chronic/handler.go
+2
-2
server/internal/service/chronic/service.go
server/internal/service/chronic/service.go
+5
-1
server/pkg/agent/tools/chronic_record_create.go
server/pkg/agent/tools/chronic_record_create.go
+64
-0
server/pkg/agent/tools/chronic_records.go
server/pkg/agent/tools/chronic_records.go
+74
-0
server/pkg/agent/tools/consultation_accept.go
server/pkg/agent/tools/consultation_accept.go
+52
-0
server/pkg/agent/tools/consultation_create.go
server/pkg/agent/tools/consultation_create.go
+71
-0
server/pkg/agent/tools/consultation_detail.go
server/pkg/agent/tools/consultation_detail.go
+130
-0
server/pkg/agent/tools/consultation_list.go
server/pkg/agent/tools/consultation_list.go
+108
-0
server/pkg/agent/tools/dashboard_stats.go
server/pkg/agent/tools/dashboard_stats.go
+80
-0
server/pkg/agent/tools/dashboard_trend.go
server/pkg/agent/tools/dashboard_trend.go
+79
-0
server/pkg/agent/tools/department_list.go
server/pkg/agent/tools/department_list.go
+56
-0
server/pkg/agent/tools/doctor_detail.go
server/pkg/agent/tools/doctor_detail.go
+95
-0
server/pkg/agent/tools/doctor_list.go
server/pkg/agent/tools/doctor_list.go
+102
-0
server/pkg/agent/tools/doctor_schedule.go
server/pkg/agent/tools/doctor_schedule.go
+88
-0
server/pkg/agent/tools/doctor_schedule_create.go
server/pkg/agent/tools/doctor_schedule_create.go
+76
-0
server/pkg/agent/tools/health_metric_record.go
server/pkg/agent/tools/health_metric_record.go
+90
-0
server/pkg/agent/tools/health_metrics.go
server/pkg/agent/tools/health_metrics.go
+91
-0
server/pkg/agent/tools/income_records.go
server/pkg/agent/tools/income_records.go
+98
-0
server/pkg/agent/tools/income_stats.go
server/pkg/agent/tools/income_stats.go
+71
-0
server/pkg/agent/tools/lab_reports.go
server/pkg/agent/tools/lab_reports.go
+83
-0
server/pkg/agent/tools/medicine_catalog.go
server/pkg/agent/tools/medicine_catalog.go
+90
-0
server/pkg/agent/tools/order_detail.go
server/pkg/agent/tools/order_detail.go
+78
-0
server/pkg/agent/tools/order_list.go
server/pkg/agent/tools/order_list.go
+98
-0
server/pkg/agent/tools/patient_profile.go
server/pkg/agent/tools/patient_profile.go
+99
-0
server/pkg/agent/tools/prescription_detail.go
server/pkg/agent/tools/prescription_detail.go
+113
-0
server/pkg/agent/tools/prescription_list.go
server/pkg/agent/tools/prescription_list.go
+106
-0
server/pkg/agent/tools/renewal_create.go
server/pkg/agent/tools/renewal_create.go
+76
-0
server/pkg/agent/tools/renewal_requests.go
server/pkg/agent/tools/renewal_requests.go
+92
-0
server/pkg/agent/tools/system_logs.go
server/pkg/agent/tools/system_logs.go
+88
-0
server/pkg/agent/tools/user_list.go
server/pkg/agent/tools/user_list.go
+111
-0
server/pkg/agent/tools/waiting_queue.go
server/pkg/agent/tools/waiting_queue.go
+72
-0
server/pkg/rag/embedder.go
server/pkg/rag/embedder.go
+70
-4
web/src/api/agent.ts
web/src/api/agent.ts
+6
-0
web/src/app/(main)/admin/agents/page.tsx
web/src/app/(main)/admin/agents/page.tsx
+34
-2
web/src/app/(main)/doctor/consult/AIPanel.tsx
web/src/app/(main)/doctor/consult/AIPanel.tsx
+69
-8
web/src/app/(main)/patient/chronic/page.tsx
web/src/app/(main)/patient/chronic/page.tsx
+20
-18
web/src/components/GlobalAIFloat/ChatPanel.tsx
web/src/components/GlobalAIFloat/ChatPanel.tsx
+28
-20
web/src/components/GlobalAIFloat/ToolCallCard.tsx
web/src/components/GlobalAIFloat/ToolCallCard.tsx
+75
-3
web/src/components/GlobalAIFloat/ToolResultCard.tsx
web/src/components/GlobalAIFloat/ToolResultCard.tsx
+215
-2
web/src/components/GlobalAIFloat/types.ts
web/src/components/GlobalAIFloat/types.ts
+15
-6
No files found.
.claude/settings.local.json
View file @
da795257
...
...
@@ -56,7 +56,8 @@
"Bash(sed:*)"
,
"Bash(docker-compose ps:*)"
,
"Bash(cat web/src/app/
\\\\\\
(main
\\\\\\
)/admin/agents/page.tsx)"
,
"Bash(awk NR==68,NR==376:*)"
"Bash(awk NR==68,NR==376:*)"
,
"Bash(LANG=en_US.UTF-8 sed:*)"
]
}
}
docs/plans/2026-03-05-ai-tools-upgrade-batch1.md
0 → 100644
View file @
da795257
# AI 智能助手工具化升级 — 第 1 批实施计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:**
新增 11 个业务工具(问诊域 5 + 处方域 3 + 患者信息域 3),使 AI 助手能触达核心诊疗业务数据,与现有 API 端到端一致。
**Architecture:**
所有查询工具直接使用
`DB.WithContext(ctx).Raw()`
查询数据库(与
`query_medical_record`
模式一致);写操作工具通过回调函数注入 Service 层方法(与
`NotifyFn`
模式一致),确保事务、校验、工作流触发与 HTTP API 完全相同。工具内通过
`ctx.Value(ContextKeyUserRole/UserID)`
做角色权限和数据隔离。
**Tech Stack:**
Go 1.22, GORM,
`pkg/agent`
Tool 接口,
`internal/agent/init.go`
注册
---
## Task 1: 问诊列表查询工具 `query_consultation_list`
**Files:**
-
Create:
`server/pkg/agent/tools/consultation_list.go`
-
Modify:
`server/internal/agent/init.go`
(InitTools + syncToolsToDB categoryMap + initToolSelector categoryMap)
-
Modify:
`server/internal/agent/agents.go`
(patientTools + doctorTools + adminTools)
**Step 1: Create `server/pkg/agent/tools/consultation_list.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationListTool 查询问诊列表
type
ConsultationListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
ConsultationListTool
)
Name
()
string
{
return
"query_consultation_list"
}
func
(
t
*
ConsultationListTool
)
Description
()
string
{
return
"查询问诊列表:患者查自己的问诊记录,医生查自己接诊的问诊记录,支持按状态过滤"
}
func
(
t
*
ConsultationListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"问诊状态过滤(可选):pending/in_progress/completed/cancelled"
,
Required
:
false
,
Enum
:
[]
string
{
"pending"
,
"in_progress"
,
"completed"
,
"cancelled"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回记录数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
ConsultationListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
// 根据角色决定查询条件
var
roleField
string
switch
userRole
{
case
"doctor"
:
// 医生需要通过 doctors 表找到 doctor.id
roleField
=
"doctor_id"
var
doctorID
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
.
Error
;
err
!=
nil
||
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"consultations"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
userID
=
doctorID
case
"admin"
:
roleField
=
""
// 管理员查所有
default
:
roleField
=
"patient_id"
}
query
:=
"SELECT c.id, c.serial_number, c.chief_complaint, c.status, c.type, c.created_at, c.started_at, c.ended_at, "
+
"d.name as doctor_name, dep.name as department_name, u.real_name as patient_name "
+
"FROM consultations c "
+
"LEFT JOIN doctors d ON c.doctor_id = d.id "
+
"LEFT JOIN departments dep ON d.department_id = dep.id "
+
"LEFT JOIN users u ON c.patient_id = u.id "
+
"WHERE c.deleted_at IS NULL"
args
:=
[]
interface
{}{}
if
roleField
!=
""
{
query
+=
" AND c."
+
roleField
+
" = ?"
args
=
append
(
args
,
userID
)
}
if
status
!=
""
{
query
+=
" AND c.status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY c.created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"consultations"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"consultations"
:
results
,
"total"
:
len
(
results
),
"role"
:
userRole
,
},
nil
}
```
**Step 2: Register in `server/internal/agent/init.go`**
In
`InitTools()`
, after the
`r.Register(&tools.NavigatePageTool{})`
line, add:
```
go
// 第1批业务工具:问诊管理
r
.
Register
(
&
tools
.
ConsultationListTool
{
DB
:
db
})
```
In
`syncToolsToDB`
categoryMap, add:
```
go
"query_consultation_list"
:
"consult"
,
```
In
`initToolSelector`
categoryMap, add the same entry.
**Step 3: Add to agent tool lists in `server/internal/agent/agents.go`**
Add
`"query_consultation_list"`
to
`patientTools`
,
`doctorTools`
, and
`adminTools`
arrays.
**Step 4: Verify compilation**
Run:
`cd server && go build ./...`
Expected: BUILD SUCCESS
**Step 5: Commit**
```
bash
git add server/pkg/agent/tools/consultation_list.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_consultation_list tool for consult list queries"
```
---
## Task 2: 问诊详情查询工具 `query_consultation_detail`
**Files:**
-
Create:
`server/pkg/agent/tools/consultation_detail.go`
-
Modify:
`server/internal/agent/init.go`
(registration + categoryMap)
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/consultation_detail.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationDetailTool 查询问诊详情
type
ConsultationDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
ConsultationDetailTool
)
Name
()
string
{
return
"query_consultation_detail"
}
func
(
t
*
ConsultationDetailTool
)
Description
()
string
{
return
"查询问诊详情,包括患者信息、医生信息、主诉、诊断、消息记录,支持UUID或流水号(C开头)查询"
}
func
(
t
*
ConsultationDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"consultation_id"
,
Type
:
"string"
,
Description
:
"问诊ID(UUID)或流水号(C开头,如C20260305-0001)"
,
Required
:
true
},
{
Name
:
"include_messages"
,
Type
:
"boolean"
,
Description
:
"是否包含消息记录,默认true"
,
Required
:
false
},
}
}
func
(
t
*
ConsultationDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
consultID
,
ok
:=
params
[
"consultation_id"
]
.
(
string
)
if
!
ok
||
consultID
==
""
{
return
nil
,
fmt
.
Errorf
(
"consultation_id 必填"
)
}
includeMessages
:=
true
if
v
,
ok
:=
params
[
"include_messages"
]
.
(
bool
);
ok
{
includeMessages
=
v
}
// 支持流水号查询
resolvedID
:=
consultID
if
len
(
consultID
)
>
0
&&
consultID
[
0
]
==
'C'
{
var
id
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM consultations WHERE serial_number = ?"
,
consultID
)
.
Scan
(
&
id
)
.
Error
;
err
!=
nil
||
id
==
""
{
return
nil
,
fmt
.
Errorf
(
"流水号 %s 对应的问诊不存在"
,
consultID
)
}
resolvedID
=
id
}
// 查询问诊详情
var
detail
map
[
string
]
interface
{}
row
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT c.id, c.serial_number, c.patient_id, c.doctor_id, c.type, c.status,
c.chief_complaint, c.medical_history, c.diagnosis, c.summary,
c.started_at, c.ended_at, c.created_at, c.satisfaction_score,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name,
u.real_name as patient_name, u.phone as patient_phone
FROM consultations c
LEFT JOIN doctors d ON c.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.id = ? AND c.deleted_at IS NULL`
,
resolvedID
)
.
Row
()
cols
:=
[]
string
{
"id"
,
"serial_number"
,
"patient_id"
,
"doctor_id"
,
"type"
,
"status"
,
"chief_complaint"
,
"medical_history"
,
"diagnosis"
,
"summary"
,
"started_at"
,
"ended_at"
,
"created_at"
,
"satisfaction_score"
,
"doctor_name"
,
"doctor_title"
,
"department_name"
,
"patient_name"
,
"patient_phone"
}
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
row
.
Scan
(
ptrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊不存在: %s"
,
consultID
)
}
detail
=
make
(
map
[
string
]
interface
{})
for
i
,
col
:=
range
cols
{
detail
[
col
]
=
vals
[
i
]
}
// 权限校验:患者只能查自己的,医生只能查自己接诊的
if
userRole
==
"patient"
&&
detail
[
"patient_id"
]
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该问诊记录"
)
}
if
userRole
==
"doctor"
{
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
detail
[
"doctor_id"
]
!=
doctorID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该问诊记录"
)
}
}
// 可选:查询消息记录
if
includeMessages
{
var
messages
[]
map
[
string
]
interface
{}
msgRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, sender_type, content, content_type, created_at
FROM consult_messages
WHERE consult_id = ? AND deleted_at IS NULL
ORDER BY created_at ASC LIMIT 50`
,
resolvedID
)
.
Rows
()
if
err
==
nil
{
defer
msgRows
.
Close
()
msgCols
,
_
:=
msgRows
.
Columns
()
for
msgRows
.
Next
()
{
msg
:=
make
(
map
[
string
]
interface
{})
mVals
:=
make
([]
interface
{},
len
(
msgCols
))
mPtrs
:=
make
([]
interface
{},
len
(
msgCols
))
for
i
:=
range
mVals
{
mPtrs
[
i
]
=
&
mVals
[
i
]
}
if
err
:=
msgRows
.
Scan
(
mPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
msgCols
{
msg
[
col
]
=
mVals
[
i
]
}
messages
=
append
(
messages
,
msg
)
}
}
}
detail
[
"messages"
]
=
messages
detail
[
"message_count"
]
=
len
(
messages
)
}
return
detail
,
nil
}
```
**Step 2: Register in `init.go`, add to categoryMap:**
```
go
r
.
Register
(
&
tools
.
ConsultationDetailTool
{
DB
:
db
})
// categoryMap:
"query_consultation_detail"
:
"consult"
,
```
**Step 3: Add to `agents.go`**
— add
`"query_consultation_detail"`
to patientTools, doctorTools, adminTools.
**Step 4: Verify compilation**
Run:
`cd server && go build ./...`
**Step 5: Commit**
```
bash
git add server/pkg/agent/tools/consultation_detail.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_consultation_detail tool with message history"
```
---
## Task 3: 创建问诊工具 `create_consultation`(写操作 — 回调模式)
**Files:**
-
Create:
`server/pkg/agent/tools/consultation_create.go`
-
Modify:
`server/internal/agent/init.go`
(registration + WireCallbacks)
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/consultation_create.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateConsultFn 创建问诊回调,由 WireCallbacks 注入 consult.Service.CreateConsult
// 参数: ctx, patientID, doctorID, consultType, chiefComplaint, medicalHistory
// 返回: 问诊ID, 流水号, error
var
CreateConsultFn
func
(
ctx
context
.
Context
,
patientID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
string
)
(
string
,
string
,
error
)
// CreateConsultationTool 创建问诊(患者发起)
type
CreateConsultationTool
struct
{}
func
(
t
*
CreateConsultationTool
)
Name
()
string
{
return
"create_consultation"
}
func
(
t
*
CreateConsultationTool
)
Description
()
string
{
return
"患者创建新的问诊,需要指定医生ID、问诊类型和主诉。创建成功后返回问诊ID和流水号"
}
func
(
t
*
CreateConsultationTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"doctor_id"
,
Type
:
"string"
,
Description
:
"接诊医生ID(从 query_doctor_list 获取)"
,
Required
:
true
},
{
Name
:
"type"
,
Type
:
"string"
,
Description
:
"问诊类型"
,
Required
:
true
,
Enum
:
[]
string
{
"text"
,
"video"
}},
{
Name
:
"chief_complaint"
,
Type
:
"string"
,
Description
:
"主诉(患者症状描述)"
,
Required
:
true
},
{
Name
:
"medical_history"
,
Type
:
"string"
,
Description
:
"既往病史(可选)"
,
Required
:
false
},
}
}
func
(
t
*
CreateConsultationTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"patient"
{
return
nil
,
fmt
.
Errorf
(
"仅患者可创建问诊"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
doctorID
,
ok
:=
params
[
"doctor_id"
]
.
(
string
)
if
!
ok
||
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"doctor_id 必填"
)
}
consultType
,
ok
:=
params
[
"type"
]
.
(
string
)
if
!
ok
||
(
consultType
!=
"text"
&&
consultType
!=
"video"
)
{
return
nil
,
fmt
.
Errorf
(
"type 必须为 text 或 video"
)
}
chiefComplaint
,
ok
:=
params
[
"chief_complaint"
]
.
(
string
)
if
!
ok
||
chiefComplaint
==
""
{
return
nil
,
fmt
.
Errorf
(
"chief_complaint 必填"
)
}
medicalHistory
,
_
:=
params
[
"medical_history"
]
.
(
string
)
if
CreateConsultFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊服务未初始化"
)
}
consultID
,
serialNumber
,
err
:=
CreateConsultFn
(
ctx
,
userID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"创建问诊失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"consultation_id"
:
consultID
,
"serial_number"
:
serialNumber
,
"status"
:
"pending"
,
"message"
:
"问诊已创建,等待医生接诊"
,
},
nil
}
```
**Step 2: Wire callback in `init.go` WireCallbacks()**
```
go
// 注入创建问诊回调
tools
.
CreateConsultFn
=
func
(
ctx
context
.
Context
,
patientID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
string
)
(
string
,
string
,
error
)
{
consultSvc
:=
consult
.
NewService
()
resp
,
err
:=
consultSvc
.
CreateConsult
(
ctx
,
patientID
,
&
consult
.
CreateConsultRequest
{
DoctorID
:
doctorID
,
Type
:
consultType
,
ChiefComplaint
:
chiefComplaint
,
MedicalHistory
:
medicalHistory
,
})
if
err
!=
nil
{
return
""
,
""
,
err
}
return
resp
.
ID
,
resp
.
SerialNumber
,
nil
}
```
Add import
`"internet-hospital/internal/service/consult"`
to init.go.
**Step 3: Register tool + categoryMap:**
```
go
r
.
Register
(
&
tools
.
CreateConsultationTool
{})
// categoryMap:
"create_consultation"
:
"consult"
,
```
**Step 4: Add to `agents.go`**
— add
`"create_consultation"`
to patientTools only.
**Step 5: Verify compilation + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/consultation_create.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add create_consultation tool with service callback"
```
---
## Task 4: 医生接诊工具 `accept_consultation`(写操作 — 回调模式)
**Files:**
-
Create:
`server/pkg/agent/tools/consultation_accept.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/consultation_accept.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// AcceptConsultFn 接诊回调,由 WireCallbacks 注入 doctorportal.Service.AcceptConsult
var
AcceptConsultFn
func
(
ctx
context
.
Context
,
consultID
,
doctorUserID
string
)
error
// AcceptConsultationTool 医生接诊
type
AcceptConsultationTool
struct
{}
func
(
t
*
AcceptConsultationTool
)
Name
()
string
{
return
"accept_consultation"
}
func
(
t
*
AcceptConsultationTool
)
Description
()
string
{
return
"医生接受一个等候中的问诊,将问诊状态从pending变为in_progress"
}
func
(
t
*
AcceptConsultationTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"consultation_id"
,
Type
:
"string"
,
Description
:
"要接诊的问诊ID"
,
Required
:
true
},
}
}
func
(
t
*
AcceptConsultationTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可接诊"
)
}
consultID
,
ok
:=
params
[
"consultation_id"
]
.
(
string
)
if
!
ok
||
consultID
==
""
{
return
nil
,
fmt
.
Errorf
(
"consultation_id 必填"
)
}
if
AcceptConsultFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"接诊服务未初始化"
)
}
if
err
:=
AcceptConsultFn
(
ctx
,
consultID
,
userID
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"接诊失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"consultation_id"
:
consultID
,
"status"
:
"in_progress"
,
"message"
:
"接诊成功,问诊已开始"
,
},
nil
}
```
**Step 2: Wire callback in `init.go` WireCallbacks()**
```
go
// 注入接诊回调
tools
.
AcceptConsultFn
=
func
(
ctx
context
.
Context
,
consultID
,
doctorUserID
string
)
error
{
dpSvc
:=
doctorportal
.
NewService
()
_
,
err
:=
dpSvc
.
AcceptConsult
(
ctx
,
consultID
,
doctorUserID
)
return
err
}
```
Add import
`"internet-hospital/internal/service/doctorportal"`
.
**Step 3: Register + categoryMap + agents.go**
— add to doctorTools only.
**Step 4: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/consultation_accept.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add accept_consultation tool for doctor to accept consults"
```
---
## Task 5: 等候队列查询工具 `query_waiting_queue`
**Files:**
-
Create:
`server/pkg/agent/tools/waiting_queue.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/waiting_queue.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// WaitingQueueTool 查询医生的等候队列
type
WaitingQueueTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
WaitingQueueTool
)
Name
()
string
{
return
"query_waiting_queue"
}
func
(
t
*
WaitingQueueTool
)
Description
()
string
{
return
"查询当前医生的等候队列,显示等候中的患者数量和列表"
}
func
(
t
*
WaitingQueueTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{}
}
func
(
t
*
WaitingQueueTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可查看等候队列"
)
}
// 获取 doctor.id
var
doctorID
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
.
Error
;
err
!=
nil
||
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"waiting_count"
:
0
,
"patients"
:
[]
interface
{}{}},
nil
}
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT c.id, c.serial_number, c.chief_complaint, c.type, c.created_at,
u.real_name as patient_name,
EXTRACT(EPOCH FROM (NOW() - c.created_at))::int as wait_seconds
FROM consultations c
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.doctor_id = ? AND c.status = 'pending' AND c.deleted_at IS NULL
ORDER BY c.created_at ASC`
,
doctorID
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"waiting_count"
:
0
,
"patients"
:
[]
interface
{}{}},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"waiting_count"
:
len
(
results
),
"patients"
:
results
,
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to doctorTools only.
```
go
r
.
Register
(
&
tools
.
WaitingQueueTool
{
DB
:
db
})
// categoryMap:
"query_waiting_queue"
:
"consult"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/waiting_queue.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_waiting_queue tool for doctor waiting list"
```
---
## Task 6: 处方列表查询工具 `query_prescription_list`
**Files:**
-
Create:
`server/pkg/agent/tools/prescription_list.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/prescription_list.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionListTool 查询处方列表
type
PrescriptionListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PrescriptionListTool
)
Name
()
string
{
return
"query_prescription_list"
}
func
(
t
*
PrescriptionListTool
)
Description
()
string
{
return
"查询处方列表:患者查自己的处方,医生查自己开的处方,支持按状态过滤"
}
func
(
t
*
PrescriptionListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"处方状态过滤(可选):pending/signed/dispensed/completed/cancelled"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
PrescriptionListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
query
:=
`SELECT p.id, p.prescription_no, p.patient_name, p.diagnosis,
p.total_amount, p.status, p.created_at,
d.name as doctor_name, dep.name as department_name,
(SELECT COUNT(*) FROM prescription_items pi WHERE pi.prescription_id = p.id) as drug_count
FROM prescriptions p
LEFT JOIN doctors d ON p.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE p.deleted_at IS NULL`
args
:=
[]
interface
{}{}
switch
userRole
{
case
"patient"
:
query
+=
" AND p.patient_id = ?"
args
=
append
(
args
,
userID
)
case
"doctor"
:
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"prescriptions"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
query
+=
" AND p.doctor_id = ?"
args
=
append
(
args
,
doctorID
)
case
"admin"
:
// 管理员查所有
default
:
return
nil
,
fmt
.
Errorf
(
"无权限查询处方"
)
}
if
status
!=
""
{
query
+=
" AND p.status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY p.created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"prescriptions"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"prescriptions"
:
results
,
"total"
:
len
(
results
),
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to patientTools, doctorTools, adminTools.
```
go
r
.
Register
(
&
tools
.
PrescriptionListTool
{
DB
:
db
})
// categoryMap:
"query_prescription_list"
:
"prescription"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/prescription_list.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_prescription_list tool for prescription queries"
```
---
## Task 7: 处方详情查询工具 `query_prescription_detail`
**Files:**
-
Create:
`server/pkg/agent/tools/prescription_detail.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/prescription_detail.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionDetailTool 查询处方详情
type
PrescriptionDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PrescriptionDetailTool
)
Name
()
string
{
return
"query_prescription_detail"
}
func
(
t
*
PrescriptionDetailTool
)
Description
()
string
{
return
"查询处方详情,包括药品明细、用法用量、状态和费用,支持处方ID或处方编号(RX开头)查询"
}
func
(
t
*
PrescriptionDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"prescription_id"
,
Type
:
"string"
,
Description
:
"处方ID(UUID)或处方编号(RX开头)"
,
Required
:
true
},
}
}
func
(
t
*
PrescriptionDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
prescriptionID
,
ok
:=
params
[
"prescription_id"
]
.
(
string
)
if
!
ok
||
prescriptionID
==
""
{
return
nil
,
fmt
.
Errorf
(
"prescription_id 必填"
)
}
// 支持处方编号查询
resolvedID
:=
prescriptionID
if
len
(
prescriptionID
)
>=
2
&&
prescriptionID
[
:
2
]
==
"RX"
{
var
id
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM prescriptions WHERE prescription_no = ?"
,
prescriptionID
)
.
Scan
(
&
id
)
.
Error
;
err
!=
nil
||
id
==
""
{
return
nil
,
fmt
.
Errorf
(
"处方编号 %s 不存在"
,
prescriptionID
)
}
resolvedID
=
id
}
// 查询处方主记录
var
detail
map
[
string
]
interface
{}
row
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT p.id, p.prescription_no, p.consult_id, p.patient_id, p.patient_name,
p.patient_gender, p.patient_age, p.diagnosis, p.allergy_history,
p.remark, p.total_amount, p.status, p.created_at,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name
FROM prescriptions p
LEFT JOIN doctors d ON p.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE p.id = ? AND p.deleted_at IS NULL`
,
resolvedID
)
.
Row
()
cols
:=
[]
string
{
"id"
,
"prescription_no"
,
"consult_id"
,
"patient_id"
,
"patient_name"
,
"patient_gender"
,
"patient_age"
,
"diagnosis"
,
"allergy_history"
,
"remark"
,
"total_amount"
,
"status"
,
"created_at"
,
"doctor_name"
,
"doctor_title"
,
"department_name"
}
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
row
.
Scan
(
ptrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"处方不存在: %s"
,
prescriptionID
)
}
detail
=
make
(
map
[
string
]
interface
{})
for
i
,
col
:=
range
cols
{
detail
[
col
]
=
vals
[
i
]
}
// 权限校验
if
userRole
==
"patient"
&&
detail
[
"patient_id"
]
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该处方"
)
}
// 查询药品明细
var
items
[]
map
[
string
]
interface
{}
itemRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT medicine_name, specification, usage, dosage, frequency,
days, quantity, unit, price, note
FROM prescription_items
WHERE prescription_id = ?
ORDER BY created_at ASC`
,
resolvedID
)
.
Rows
()
if
err
==
nil
{
defer
itemRows
.
Close
()
itemCols
,
_
:=
itemRows
.
Columns
()
for
itemRows
.
Next
()
{
item
:=
make
(
map
[
string
]
interface
{})
iVals
:=
make
([]
interface
{},
len
(
itemCols
))
iPtrs
:=
make
([]
interface
{},
len
(
itemCols
))
for
i
:=
range
iVals
{
iPtrs
[
i
]
=
&
iVals
[
i
]
}
if
err
:=
itemRows
.
Scan
(
iPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
itemCols
{
item
[
col
]
=
iVals
[
i
]
}
items
=
append
(
items
,
item
)
}
}
}
detail
[
"items"
]
=
items
detail
[
"drug_count"
]
=
len
(
items
)
return
detail
,
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to patientTools, doctorTools, adminTools.
```
go
r
.
Register
(
&
tools
.
PrescriptionDetailTool
{
DB
:
db
})
// categoryMap:
"query_prescription_detail"
:
"prescription"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/prescription_detail.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_prescription_detail tool with drug items"
```
---
## Task 8: 药品目录搜索工具 `search_medicine_catalog`
**Files:**
-
Create:
`server/pkg/agent/tools/medicine_catalog.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/medicine_catalog.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// MedicineCatalogTool 搜索药品目录
type
MedicineCatalogTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
MedicineCatalogTool
)
Name
()
string
{
return
"search_medicine_catalog"
}
func
(
t
*
MedicineCatalogTool
)
Description
()
string
{
return
"搜索药品目录,查询药品名称、规格、库存、价格等信息,为开方提供数据支持"
}
func
(
t
*
MedicineCatalogTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"keyword"
,
Type
:
"string"
,
Description
:
"药品名称或关键词"
,
Required
:
true
},
{
Name
:
"category"
,
Type
:
"string"
,
Description
:
"药品分类过滤(可选)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10"
,
Required
:
false
},
}
}
func
(
t
*
MedicineCatalogTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"doctor"
&&
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅医生和管理员可搜索药品目录"
)
}
keyword
,
ok
:=
params
[
"keyword"
]
.
(
string
)
if
!
ok
||
keyword
==
""
{
return
nil
,
fmt
.
Errorf
(
"keyword 必填"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
30
{
limit
=
30
}
}
category
,
_
:=
params
[
"category"
]
.
(
string
)
query
:=
`SELECT id, name, generic_name, specification, manufacturer,
unit, price, stock, category, status, usage_method, contraindication
FROM medicines
WHERE (name ILIKE ? OR generic_name ILIKE ?) AND status = 'active'`
searchPattern
:=
"%"
+
keyword
+
"%"
args
:=
[]
interface
{}{
searchPattern
,
searchPattern
}
if
category
!=
""
{
query
+=
" AND category = ?"
args
=
append
(
args
,
category
)
}
query
+=
" ORDER BY name ASC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"medicines"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"medicines"
:
results
,
"total"
:
len
(
results
),
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to doctorTools only.
```
go
r
.
Register
(
&
tools
.
MedicineCatalogTool
{
DB
:
db
})
// categoryMap:
"search_medicine_catalog"
:
"pharmacy"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/medicine_catalog.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add search_medicine_catalog tool for drug catalog search"
```
---
## Task 9: 患者健康档案查询工具 `query_patient_profile`
**Files:**
-
Create:
`server/pkg/agent/tools/patient_profile.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/patient_profile.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PatientProfileTool 查询患者健康档案
type
PatientProfileTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PatientProfileTool
)
Name
()
string
{
return
"query_patient_profile"
}
func
(
t
*
PatientProfileTool
)
Description
()
string
{
return
"查询患者健康档案(基本信息、过敏史、病史、保险信息),患者查自己的,医生按patient_id查"
}
func
(
t
*
PatientProfileTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生/管理员使用,患者无需传入自动查自己的)"
,
Required
:
false
},
}
}
func
(
t
*
PatientProfileTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的健康档案"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
// 查询用户基本信息
var
userInfo
map
[
string
]
interface
{}
uRow
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, real_name, phone, gender, age, avatar, role, status, created_at
FROM users WHERE id = ?`
,
targetID
)
.
Row
()
uCols
:=
[]
string
{
"id"
,
"real_name"
,
"phone"
,
"gender"
,
"age"
,
"avatar"
,
"role"
,
"status"
,
"created_at"
}
uVals
:=
make
([]
interface
{},
len
(
uCols
))
uPtrs
:=
make
([]
interface
{},
len
(
uCols
))
for
i
:=
range
uVals
{
uPtrs
[
i
]
=
&
uVals
[
i
]
}
if
err
:=
uRow
.
Scan
(
uPtrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"用户不存在"
)
}
userInfo
=
make
(
map
[
string
]
interface
{})
for
i
,
col
:=
range
uCols
{
userInfo
[
col
]
=
uVals
[
i
]
}
// 查询健康档案
var
profile
map
[
string
]
interface
{}
pRow
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT gender, birth_date, medical_history, allergy_history,
emergency_contact, insurance_type, insurance_no
FROM patient_profiles WHERE user_id = ?`
,
targetID
)
.
Row
()
pCols
:=
[]
string
{
"gender"
,
"birth_date"
,
"medical_history"
,
"allergy_history"
,
"emergency_contact"
,
"insurance_type"
,
"insurance_no"
}
pVals
:=
make
([]
interface
{},
len
(
pCols
))
pPtrs
:=
make
([]
interface
{},
len
(
pCols
))
for
i
:=
range
pVals
{
pPtrs
[
i
]
=
&
pVals
[
i
]
}
profile
=
make
(
map
[
string
]
interface
{})
if
err
:=
pRow
.
Scan
(
pPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
pCols
{
profile
[
col
]
=
pVals
[
i
]
}
}
// 查询最近问诊数
var
consultCount
int64
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM consultations WHERE patient_id = ? AND deleted_at IS NULL"
,
targetID
)
.
Scan
(
&
consultCount
)
return
map
[
string
]
interface
{}{
"user"
:
userInfo
,
"health_profile"
:
profile
,
"consult_count"
:
consultCount
,
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to patientTools and doctorTools.
```
go
r
.
Register
(
&
tools
.
PatientProfileTool
{
DB
:
db
})
// categoryMap:
"query_patient_profile"
:
"patient"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/patient_profile.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_patient_profile tool for health profile queries"
```
---
## Task 10: 健康指标查询工具 `query_health_metrics`
**Files:**
-
Create:
`server/pkg/agent/tools/health_metrics.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/health_metrics.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// HealthMetricsTool 查询健康指标记录
type
HealthMetricsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
HealthMetricsTool
)
Name
()
string
{
return
"query_health_metrics"
}
func
(
t
*
HealthMetricsTool
)
Description
()
string
{
return
"查询患者健康指标记录(血压、血糖、心率、体温),支持类型过滤,按时间倒序返回"
}
func
(
t
*
HealthMetricsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生使用,患者无需传入)"
,
Required
:
false
},
{
Name
:
"metric_type"
,
Type
:
"string"
,
Description
:
"指标类型过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"blood_pressure"
,
"blood_glucose"
,
"heart_rate"
,
"body_temperature"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认20"
,
Required
:
false
},
}
}
func
(
t
*
HealthMetricsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的健康指标"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
20
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
100
{
limit
=
100
}
}
metricType
,
_
:=
params
[
"metric_type"
]
.
(
string
)
query
:=
`SELECT id, metric_type, value1, value2, unit, notes, recorded_at, created_at
FROM health_metrics WHERE user_id = ?`
args
:=
[]
interface
{}{
targetID
}
if
metricType
!=
""
{
query
+=
" AND metric_type = ?"
args
=
append
(
args
,
metricType
)
}
query
+=
" ORDER BY recorded_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"metrics"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"metrics"
:
results
,
"total"
:
len
(
results
),
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to patientTools and doctorTools.
```
go
r
.
Register
(
&
tools
.
HealthMetricsTool
{
DB
:
db
})
// categoryMap:
"query_health_metrics"
:
"health"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/health_metrics.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_health_metrics tool for health metric queries"
```
---
## Task 11: 检验报告查询工具 `query_lab_reports`
**Files:**
-
Create:
`server/pkg/agent/tools/lab_reports.go`
-
Modify:
`server/internal/agent/init.go`
-
Modify:
`server/internal/agent/agents.go`
**Step 1: Create `server/pkg/agent/tools/lab_reports.go`**
```
go
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// LabReportsTool 查询检验报告
type
LabReportsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
LabReportsTool
)
Name
()
string
{
return
"query_lab_reports"
}
func
(
t
*
LabReportsTool
)
Description
()
string
{
return
"查询患者的检验报告列表,包括AI解读结果"
}
func
(
t
*
LabReportsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生使用,患者无需传入)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10"
,
Required
:
false
},
}
}
func
(
t
*
LabReportsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的检验报告"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
30
{
limit
=
30
}
}
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, title, report_date, file_url, file_type, category,
ai_interpret, created_at
FROM lab_reports
WHERE user_id = ? AND deleted_at IS NULL
ORDER BY report_date DESC, created_at DESC LIMIT ?`
,
targetID
,
limit
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"reports"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"reports"
:
results
,
"total"
:
len
(
results
),
},
nil
}
```
**Step 2: Register + categoryMap + agents.go**
— add to patientTools and doctorTools.
```
go
r
.
Register
(
&
tools
.
LabReportsTool
{
DB
:
db
})
// categoryMap:
"query_lab_reports"
:
"health"
,
```
**Step 3: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/pkg/agent/tools/lab_reports.go server/internal/agent/init.go server/internal/agent/agents.go
git commit
-m
"feat(agent): add query_lab_reports tool for lab report queries"
```
---
## Task 12: 更新系统提示词(告知 AI 新工具的使用场景)
**Files:**
-
Modify:
`server/internal/agent/seed_prompts.go`
**Step 1: Update patient agent system prompt**
在患者智能助手的
`Content`
中,更新核心能力列表,增加:
```
6. **问诊管理**:查询问诊列表和详情,帮助患者创建新的问诊(需指定医生和主诉)
7. **处方查询**:查询处方列表和详情(药品明细、用法用量、费用)
8. **健康档案**:查询个人健康档案、健康指标(血压/血糖/心率/体温)趋势、检验报告及AI解读
```
更新使用原则,增加:
```
- 当患者想看病时,先用 recommend_department 推荐科室,然后建议使用 navigate_page 导航到找医生页面
- 当患者问"我的问诊"时,使用 query_consultation_list 查询
- 当患者问"我的处方/药"时,使用 query_prescription_list 查询
- 当患者问"我的健康数据"时,使用 query_health_metrics 查询
```
**Step 2: Update doctor agent system prompt**
增加核心能力:
```
7. **问诊管理**:查看等候队列、问诊列表和详情,快速接诊
8. **处方管理**:查看处方列表和详情,搜索药品目录
9. **患者档案**:查询患者健康档案、健康指标趋势、检验报告
```
更新使用原则:
```
- 查看等候队列用 query_waiting_queue,接诊用 accept_consultation
- 查患者信息用 query_patient_profile + query_health_metrics + query_lab_reports
- 搜索药品用 search_medicine_catalog,开方通过 navigate_page 导航到开方页面
```
**Step 3: Update admin agent system prompt**
增加核心能力:
```
8. **业务数据查询**:查看问诊记录、处方记录的列表和详情
```
**Step 4: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/internal/agent/seed_prompts.go
git commit
-m
"feat(agent): update system prompts with new tool usage guidance"
```
---
## Task 13: 更新关键词索引(buildToolKeywords 补充新工具关键词)
**Files:**
-
Modify:
`server/internal/agent/init.go`
**Step 1:**
In
`buildToolKeywords`
, add new Chinese keywords to the keyword list:
```
go
"问诊"
,
"就诊"
,
"接诊"
,
"挂号"
,
"预约"
,
"等候"
,
"队列"
,
"排队"
,
"处方"
,
"开方"
,
"用药"
,
"药品"
,
"档案"
,
"信息"
,
"资料"
,
"指标"
,
"血压"
,
"血糖"
,
"心率"
,
"体温"
,
"检验"
,
"化验"
,
```
**Step 2: Verify + Commit**
```
bash
cd
server
&&
go build ./...
git add server/internal/agent/init.go
git commit
-m
"feat(agent): expand keyword index for new business tools"
```
---
## Task 14: 整体编译验证 + 最终提交
**Step 1: Full build**
```
bash
cd
server
&&
go build ./...
```
**Step 2: Run tests**
```
bash
cd
server
&&
go
test
./pkg/agent/...
-v
-count
=
1
```
**Step 3: Verify tool count**
手动检查
`init.go`
中
`InitTools()`
的注册数量:原 19 + 新增 11 = 30 个内置工具。
**Step 4: Final commit if any remaining changes**
```
bash
git add
-A
git commit
-m
"feat(agent): batch 1 complete - 11 new business tools (consult/prescription/patient)"
```
---
## Summary: Batch 1 Deliverables
| # | Tool Name | Type | File | Roles |
|---|-----------|------|------|-------|
| 1 |
`query_consultation_list`
| Query |
`consultation_list.go`
| patient, doctor, admin |
| 2 |
`query_consultation_detail`
| Query |
`consultation_detail.go`
| patient, doctor, admin |
| 3 |
`create_consultation`
| Write(callback) |
`consultation_create.go`
| patient |
| 4 |
`accept_consultation`
| Write(callback) |
`consultation_accept.go`
| doctor |
| 5 |
`query_waiting_queue`
| Query |
`waiting_queue.go`
| doctor |
| 6 |
`query_prescription_list`
| Query |
`prescription_list.go`
| patient, doctor, admin |
| 7 |
`query_prescription_detail`
| Query |
`prescription_detail.go`
| patient, doctor, admin |
| 8 |
`search_medicine_catalog`
| Query |
`medicine_catalog.go`
| doctor |
| 9 |
`query_patient_profile`
| Query |
`patient_profile.go`
| patient, doctor |
| 10 |
`query_health_metrics`
| Query |
`health_metrics.go`
| patient, doctor |
| 11 |
`query_lab_reports`
| Query |
`lab_reports.go`
| patient, doctor |
**Modified files:**
-
`server/internal/agent/init.go`
— Registration + categoryMap + keywords
-
`server/internal/agent/agents.go`
— Agent tool list updates
-
`server/internal/agent/seed_prompts.go`
— System prompt updates
**After Batch 1, tool coverage:**
-
问诊流程: ✅ 查列表/详情 + 创建问诊 + 接诊 + 等候队列
-
处方流程: ✅ 查列表/详情 + 药品目录搜索(开方通过导航)
-
患者信息: ✅ 健康档案 + 健康指标 + 检验报告
server/cmd/api/main.go
View file @
da795257
...
...
@@ -11,6 +11,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"context"
internalagent
"internet-hospital/internal/agent"
"internet-hospital/internal/model"
"internet-hospital/internal/service/knowledgesvc"
...
...
@@ -25,6 +27,7 @@ import (
"internet-hospital/internal/service/chronic"
"internet-hospital/internal/service/health"
"internet-hospital/internal/service/user"
"internet-hospital/pkg/agent/tools"
"internet-hospital/pkg/ai"
"internet-hospital/pkg/config"
"internet-hospital/pkg/database"
...
...
@@ -202,6 +205,69 @@ func main() {
// 注入跨包回调(AgentCallFn / WorkflowTriggerFn)
internalagent
.
WireCallbacks
()
// v17: 注入问诊业务回调(避免 internal/agent ↔ internal/service 循环引用)
tools
.
CreateConsultFn
=
func
(
ctx
context
.
Context
,
patientID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
string
)
(
string
,
string
,
error
)
{
consultSvc
:=
consult
.
NewService
()
resp
,
err
:=
consultSvc
.
CreateConsult
(
ctx
,
patientID
,
&
consult
.
CreateConsultRequest
{
DoctorID
:
doctorID
,
Type
:
consultType
,
ChiefComplaint
:
chiefComplaint
,
MedicalHistory
:
medicalHistory
,
})
if
err
!=
nil
{
return
""
,
""
,
err
}
return
resp
.
ID
,
resp
.
SerialNumber
,
nil
}
tools
.
AcceptConsultFn
=
func
(
ctx
context
.
Context
,
consultID
,
doctorUserID
string
)
error
{
dpSvc
:=
doctorportal
.
NewService
()
_
,
err
:=
dpSvc
.
AcceptConsult
(
ctx
,
consultID
,
doctorUserID
)
return
err
}
log
.
Println
(
"[Main] 问诊业务回调注入完成"
)
// v17 第2批: 注入慢病/续方/健康指标/排班回调
tools
.
CreateChronicRecordFn
=
func
(
ctx
context
.
Context
,
userID
,
diseaseName
,
hospital
,
doctorName
,
currentMeds
,
notes
string
)
(
string
,
error
)
{
chronicSvc
:=
chronic
.
NewService
()
rec
,
err
:=
chronicSvc
.
CreateChronicRecord
(
ctx
,
userID
,
&
chronic
.
ChronicRecordReq
{
DiseaseName
:
diseaseName
,
Hospital
:
hospital
,
DoctorName
:
doctorName
,
CurrentMeds
:
currentMeds
,
Notes
:
notes
,
})
if
err
!=
nil
{
return
""
,
err
}
return
rec
.
ID
,
nil
}
tools
.
CreateRenewalFn
=
func
(
ctx
context
.
Context
,
userID
,
chronicID
,
diseaseName
,
reason
string
,
medicines
[]
string
)
(
string
,
error
)
{
chronicSvc
:=
chronic
.
NewService
()
rec
,
err
:=
chronicSvc
.
CreateRenewal
(
ctx
,
userID
,
&
chronic
.
RenewalReq
{
ChronicID
:
chronicID
,
DiseaseName
:
diseaseName
,
Medicines
:
medicines
,
Reason
:
reason
,
})
if
err
!=
nil
{
return
""
,
err
}
return
rec
.
ID
,
nil
}
tools
.
RecordHealthMetricFn
=
func
(
ctx
context
.
Context
,
userID
,
metricType
string
,
value1
,
value2
float64
,
unit
,
notes
string
)
(
string
,
error
)
{
chronicSvc
:=
chronic
.
NewService
()
rec
,
err
:=
chronicSvc
.
CreateMetric
(
ctx
,
userID
,
&
chronic
.
MetricReq
{
MetricType
:
metricType
,
Value1
:
value1
,
Value2
:
value2
,
Unit
:
unit
,
Notes
:
notes
,
})
if
err
!=
nil
{
return
""
,
err
}
return
rec
.
ID
,
nil
}
tools
.
CreateScheduleFn
=
func
(
ctx
context
.
Context
,
doctorUserID
,
date
,
startTime
,
endTime
string
,
maxCount
int
)
error
{
dpSvc
:=
doctorportal
.
NewService
()
return
dpSvc
.
CreateSchedule
(
ctx
,
doctorUserID
,
[]
doctorportal
.
ScheduleSlotReq
{
{
Date
:
date
,
StartTime
:
startTime
,
EndTime
:
endTime
,
MaxCount
:
maxCount
},
})
}
log
.
Println
(
"[Main] 第2批业务回调注入完成"
)
// 设置 Gin 模式
gin
.
SetMode
(
cfg
.
Server
.
Mode
)
...
...
server/internal/agent/agents.go
View file @
da795257
...
...
@@ -16,6 +16,21 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"search_medical_knowledge"
,
"query_drug"
,
"query_medical_record"
,
"generate_follow_up_plan"
,
"send_notification"
,
"navigate_page"
,
// v17: 问诊管理
"query_consultation_list"
,
"query_consultation_detail"
,
"create_consultation"
,
// v17: 处方查询
"query_prescription_list"
,
"query_prescription_detail"
,
// v17: 患者信息
"query_patient_profile"
,
"query_health_metrics"
,
"query_lab_reports"
,
// v17: 医生/科室
"query_doctor_list"
,
"query_doctor_detail"
,
"query_department_list"
,
// v17: 慢病管理
"query_chronic_records"
,
"create_chronic_record"
,
"query_renewal_requests"
,
"create_renewal_request"
,
// v17: 健康指标写入 + 排班查询
"record_health_metric"
,
"query_doctor_schedule"
,
// v17: 支付订单查询
"query_order_list"
,
"query_order_detail"
,
})
// 医生通用智能体 — 合并 diagnosis + prescription + follow_up 能力
...
...
@@ -23,6 +38,19 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"query_medical_record"
,
"query_symptom_knowledge"
,
"search_medical_knowledge"
,
"query_drug"
,
"check_drug_interaction"
,
"check_contraindication"
,
"calculate_dosage"
,
"generate_follow_up_plan"
,
"send_notification"
,
"navigate_page"
,
// v17: 问诊管理
"query_consultation_list"
,
"query_consultation_detail"
,
"accept_consultation"
,
"query_waiting_queue"
,
// v17: 处方管理
"query_prescription_list"
,
"query_prescription_detail"
,
"search_medicine_catalog"
,
// v17: 患者信息
"query_patient_profile"
,
"query_health_metrics"
,
"query_lab_reports"
,
// v17: 慢病续方审批
"query_renewal_requests"
,
// v17: 排班管理
"query_doctor_schedule"
,
"create_doctor_schedule"
,
// v17: 收入统计
"query_income_stats"
,
"query_income_records"
,
})
// 管理员通用智能体 — 合并 admin_assistant + general 管理能力
...
...
@@ -31,6 +59,14 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"trigger_workflow"
,
"request_human_review"
,
"list_knowledge_collections"
,
"send_notification"
,
"query_drug"
,
"search_medical_knowledge"
,
"navigate_page"
,
// v17: 业务数据查询
"query_consultation_list"
,
"query_consultation_detail"
,
"query_prescription_list"
,
"query_prescription_detail"
,
"generate_tool"
,
// v17: 管理统计 + 用户管理 + 系统日志 + 订单
"query_dashboard_stats"
,
"query_dashboard_trend"
,
"query_user_list"
,
"query_system_logs"
,
"query_order_list"
,
"query_order_detail"
,
})
return
[]
model
.
AgentDefinition
{
...
...
server/internal/agent/handler.go
View file @
da795257
...
...
@@ -6,6 +6,7 @@ import (
"log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"internet-hospital/internal/model"
"internet-hospital/pkg/agent"
...
...
@@ -31,6 +32,7 @@ func NewHandler() *Handler {
// RegisterRoutes 用户侧路由(全角色可用,仅需 JWT)
func
(
h
*
Handler
)
RegisterRoutes
(
r
gin
.
IRouter
)
{
g
:=
r
.
Group
(
"/agent"
)
g
.
POST
(
"/sessions"
,
h
.
CreateSession
)
g
.
POST
(
"/:agent_id/chat"
,
h
.
Chat
)
g
.
POST
(
"/:agent_id/chat/stream"
,
h
.
ChatStream
)
g
.
GET
(
"/sessions"
,
h
.
ListSessions
)
...
...
@@ -139,6 +141,39 @@ func (h *Handler) ListAgents(c *gin.Context) {
response
.
Success
(
c
,
h
.
svc
.
ListAgents
())
}
// CreateSession 创建新会话,返回 session_id
func
(
h
*
Handler
)
CreateSession
(
c
*
gin
.
Context
)
{
var
req
struct
{
AgentID
string
`json:"agent_id" binding:"required"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
err
.
Error
())
return
}
userID
,
_
:=
c
.
Get
(
"user_id"
)
uid
,
_
:=
userID
.
(
string
)
role
,
_
:=
c
.
Get
(
"role"
)
userRole
,
_
:=
role
.
(
string
)
sessionID
:=
uuid
.
New
()
.
String
()
session
:=
model
.
AgentSession
{
SessionID
:
sessionID
,
AgentID
:
req
.
AgentID
,
UserID
:
uid
,
History
:
"[]"
,
Context
:
"null"
,
PageContext
:
"null"
,
EntityContext
:
"null"
,
Status
:
"active"
,
UserRole
:
userRole
,
}
if
err
:=
database
.
GetDB
()
.
Create
(
&
session
)
.
Error
;
err
!=
nil
{
response
.
Error
(
c
,
500
,
"创建会话失败: "
+
err
.
Error
())
return
}
response
.
Success
(
c
,
gin
.
H
{
"session_id"
:
sessionID
})
}
// ListSessions 获取用户的 Agent 会话列表
func
(
h
*
Handler
)
ListSessions
(
c
*
gin
.
Context
)
{
userID
,
_
:=
c
.
Get
(
"user_id"
)
...
...
@@ -303,14 +338,15 @@ func (h *Handler) GetDefinition(c *gin.Context) {
// CreateDefinition 创建新 Agent
func
(
h
*
Handler
)
CreateDefinition
(
c
*
gin
.
Context
)
{
var
req
struct
{
AgentID
string
`json:"agent_id" binding:"required"`
Name
string
`json:"name" binding:"required"`
Description
string
`json:"description"`
Category
string
`json:"category"`
SystemPrompt
string
`json:"system_prompt"`
Tools
[]
string
`json:"tools"`
Skills
[]
string
`json:"skills"`
MaxIterations
int
`json:"max_iterations"`
AgentID
string
`json:"agent_id" binding:"required"`
Name
string
`json:"name" binding:"required"`
Description
string
`json:"description"`
Category
string
`json:"category"`
SystemPrompt
string
`json:"system_prompt"`
PromptTemplateID
*
uint
`json:"prompt_template_id"`
Tools
[]
string
`json:"tools"`
Skills
[]
string
`json:"skills"`
MaxIterations
int
`json:"max_iterations"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
err
.
Error
())
...
...
@@ -322,15 +358,16 @@ func (h *Handler) CreateDefinition(c *gin.Context) {
req
.
MaxIterations
=
10
}
def
:=
model
.
AgentDefinition
{
AgentID
:
req
.
AgentID
,
Name
:
req
.
Name
,
Description
:
req
.
Description
,
Category
:
req
.
Category
,
SystemPrompt
:
req
.
SystemPrompt
,
Tools
:
string
(
toolsJSON
),
Skills
:
string
(
skillsJSON
),
MaxIterations
:
req
.
MaxIterations
,
Status
:
"active"
,
AgentID
:
req
.
AgentID
,
Name
:
req
.
Name
,
Description
:
req
.
Description
,
Category
:
req
.
Category
,
SystemPrompt
:
req
.
SystemPrompt
,
PromptTemplateID
:
req
.
PromptTemplateID
,
Tools
:
string
(
toolsJSON
),
Skills
:
string
(
skillsJSON
),
MaxIterations
:
req
.
MaxIterations
,
Status
:
"active"
,
}
if
err
:=
database
.
GetDB
()
.
Create
(
&
def
)
.
Error
;
err
!=
nil
{
response
.
Error
(
c
,
500
,
err
.
Error
())
...
...
@@ -344,14 +381,16 @@ func (h *Handler) CreateDefinition(c *gin.Context) {
func
(
h
*
Handler
)
UpdateDefinition
(
c
*
gin
.
Context
)
{
agentID
:=
c
.
Param
(
"agent_id"
)
var
req
struct
{
Name
string
`json:"name"`
Description
string
`json:"description"`
Category
string
`json:"category"`
SystemPrompt
string
`json:"system_prompt"`
Tools
[]
string
`json:"tools"`
Skills
[]
string
`json:"skills"`
MaxIterations
int
`json:"max_iterations"`
Status
string
`json:"status"`
Name
string
`json:"name"`
Description
string
`json:"description"`
Category
string
`json:"category"`
SystemPrompt
string
`json:"system_prompt"`
PromptTemplateID
*
uint
`json:"prompt_template_id"`
ClearTemplate
bool
`json:"clear_template"`
Tools
[]
string
`json:"tools"`
Skills
[]
string
`json:"skills"`
MaxIterations
int
`json:"max_iterations"`
Status
string
`json:"status"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
err
.
Error
())
...
...
@@ -378,6 +417,11 @@ func (h *Handler) UpdateDefinition(c *gin.Context) {
if
req
.
SystemPrompt
!=
""
{
updates
[
"system_prompt"
]
=
req
.
SystemPrompt
}
if
req
.
PromptTemplateID
!=
nil
{
updates
[
"prompt_template_id"
]
=
*
req
.
PromptTemplateID
}
else
if
req
.
ClearTemplate
{
updates
[
"prompt_template_id"
]
=
nil
}
if
req
.
Tools
!=
nil
{
toolsJSON
,
_
:=
json
.
Marshal
(
req
.
Tools
)
updates
[
"tools"
]
=
string
(
toolsJSON
)
...
...
server/internal/agent/init.go
View file @
da795257
...
...
@@ -62,6 +62,55 @@ func InitTools() {
// v15: 热代码 Tool 生成器
r
.
Register
(
&
tools
.
CodeGenTool
{})
// v17: 第1批业务工具 — 问诊管理
r
.
Register
(
&
tools
.
ConsultationListTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
ConsultationDetailTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
CreateConsultationTool
{})
r
.
Register
(
&
tools
.
AcceptConsultationTool
{})
r
.
Register
(
&
tools
.
WaitingQueueTool
{
DB
:
db
})
// v17: 第1批业务工具 — 处方管理
r
.
Register
(
&
tools
.
PrescriptionListTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
PrescriptionDetailTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
MedicineCatalogTool
{
DB
:
db
})
// v17: 第1批业务工具 — 患者信息
r
.
Register
(
&
tools
.
PatientProfileTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
HealthMetricsTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
LabReportsTool
{
DB
:
db
})
// v17: 第2批业务工具 — 医生/科室
r
.
Register
(
&
tools
.
DoctorListTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
DoctorDetailTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
DepartmentListTool
{
DB
:
db
})
// v17: 第2批业务工具 — 慢病管理
r
.
Register
(
&
tools
.
ChronicRecordsTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
CreateChronicRecordTool
{})
r
.
Register
(
&
tools
.
RenewalRequestsTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
CreateRenewalTool
{})
// v17: 第2批业务工具 — 健康指标写入
r
.
Register
(
&
tools
.
RecordHealthMetricTool
{})
// v17: 第2批业务工具 — 排班管理
r
.
Register
(
&
tools
.
DoctorScheduleTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
CreateDoctorScheduleTool
{})
// v17: 第3批业务工具 — 支付订单
r
.
Register
(
&
tools
.
OrderListTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
OrderDetailTool
{
DB
:
db
})
// v17: 第3批业务工具 — 医生收入
r
.
Register
(
&
tools
.
IncomeStatsTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
IncomeRecordsTool
{
DB
:
db
})
// v17: 第3批业务工具 — 管理统计
r
.
Register
(
&
tools
.
DashboardStatsTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
DashboardTrendTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
UserListTool
{
DB
:
db
})
r
.
Register
(
&
tools
.
SystemLogsTool
{
DB
:
db
})
// v15: 从数据库加载动态 SQL 工具
loadDynamicSQLTools
(
r
,
db
)
...
...
@@ -75,8 +124,17 @@ func InitTools() {
log
.
Println
(
"[InitTools] ToolMonitor & ToolSelector 初始化完成"
)
// v16: 初始化智能工具推荐器(使用pgvector)
agent
.
InitToolRecommender
(
db
,
embedder
)
recommender
:=
agent
.
InitToolRecommender
(
db
,
embedder
)
log
.
Println
(
"[InitTools] ToolRecommender 初始化完成"
)
// v17: 自动同步工具向量索引(异步,不阻塞启动)
if
recommender
!=
nil
&&
embedder
!=
nil
{
go
func
()
{
if
err
:=
recommender
.
IndexToolEmbeddings
(
context
.
Background
());
err
!=
nil
{
log
.
Printf
(
"[InitTools] 工具向量索引构建失败(embedder可能未配置API key): %v"
,
err
)
}
}()
}
}
// WireCallbacks 注入跨包回调(在 InitTools 和 GetService 初始化完成后调用)
...
...
@@ -155,6 +213,45 @@ func syncToolsToDB(r *agent.ToolRegistry) {
"navigate_page"
:
"navigation"
,
// v15: 热代码生成
"generate_tool"
:
"code_gen"
,
// v17: 问诊管理
"query_consultation_list"
:
"consult"
,
"query_consultation_detail"
:
"consult"
,
"create_consultation"
:
"consult"
,
"accept_consultation"
:
"consult"
,
"query_waiting_queue"
:
"consult"
,
// v17: 处方管理
"query_prescription_list"
:
"prescription"
,
"query_prescription_detail"
:
"prescription"
,
"search_medicine_catalog"
:
"pharmacy"
,
// v17: 患者信息
"query_patient_profile"
:
"patient"
,
"query_health_metrics"
:
"health"
,
"query_lab_reports"
:
"health"
,
// v17: 医生/科室
"query_doctor_list"
:
"doctor"
,
"query_doctor_detail"
:
"doctor"
,
"query_department_list"
:
"department"
,
// v17: 慢病管理
"query_chronic_records"
:
"chronic"
,
"create_chronic_record"
:
"chronic"
,
"query_renewal_requests"
:
"chronic"
,
"create_renewal_request"
:
"chronic"
,
// v17: 健康指标写入
"record_health_metric"
:
"health"
,
// v17: 排班管理
"query_doctor_schedule"
:
"schedule"
,
"create_doctor_schedule"
:
"schedule"
,
// v17: 支付订单
"query_order_list"
:
"payment"
,
"query_order_detail"
:
"payment"
,
// v17: 医生收入
"query_income_stats"
:
"income"
,
"query_income_records"
:
"income"
,
// v17: 管理统计
"query_dashboard_stats"
:
"dashboard"
,
"query_dashboard_trend"
:
"dashboard"
,
"query_user_list"
:
"user_manage"
,
"query_system_logs"
:
"system"
,
}
for
name
,
tool
:=
range
r
.
All
()
{
...
...
@@ -252,6 +349,21 @@ func buildToolKeywords(name, description, category string) string {
"通知"
,
"提醒"
,
"随访"
,
"复诊"
,
"安全"
,
"禁忌"
,
"相互作用"
,
"剂量"
,
"用量"
,
"计算"
,
"表达式"
,
"患者"
,
"医生"
,
"管理"
,
"查询"
,
// v17: 新增业务关键词
"问诊"
,
"就诊"
,
"接诊"
,
"挂号"
,
"预约"
,
"主诉"
,
"等候"
,
"队列"
,
"排队"
,
"档案"
,
"信息"
,
"资料"
,
"健康"
,
"指标"
,
"血压"
,
"血糖"
,
"心率"
,
"体温"
,
"化验"
,
"解读"
,
"目录"
,
"库存"
,
// v17: 第2批业务关键词
"慢病"
,
"慢性"
,
"续方"
,
"续药"
,
"审批"
,
"排班"
,
"时段"
,
"预约"
,
"科室"
,
"专长"
,
"评分"
,
"在线"
,
// v17: 第3批业务关键词
"订单"
,
"支付"
,
"付款"
,
"退款"
,
"收入"
,
"余额"
,
"提现"
,
"账单"
,
"收益"
,
"分成"
,
"仪表盘"
,
"统计"
,
"趋势"
,
"运营"
,
"用户"
,
"日志"
,
"操作"
,
"系统"
,
}
{
if
strings
.
Contains
(
description
,
kw
)
{
descKeywords
[
kw
]
=
true
...
...
@@ -299,6 +411,45 @@ func initToolSelector(r *agent.ToolRegistry) {
"navigate_page"
:
"navigation"
,
// v15: 热代码生成
"generate_tool"
:
"code_gen"
,
// v17: 问诊管理
"query_consultation_list"
:
"consult"
,
"query_consultation_detail"
:
"consult"
,
"create_consultation"
:
"consult"
,
"accept_consultation"
:
"consult"
,
"query_waiting_queue"
:
"consult"
,
// v17: 处方管理
"query_prescription_list"
:
"prescription"
,
"query_prescription_detail"
:
"prescription"
,
"search_medicine_catalog"
:
"pharmacy"
,
// v17: 患者信息
"query_patient_profile"
:
"patient"
,
"query_health_metrics"
:
"health"
,
"query_lab_reports"
:
"health"
,
// v17: 医生/科室
"query_doctor_list"
:
"doctor"
,
"query_doctor_detail"
:
"doctor"
,
"query_department_list"
:
"department"
,
// v17: 慢病管理
"query_chronic_records"
:
"chronic"
,
"create_chronic_record"
:
"chronic"
,
"query_renewal_requests"
:
"chronic"
,
"create_renewal_request"
:
"chronic"
,
// v17: 健康指标写入
"record_health_metric"
:
"health"
,
// v17: 排班管理
"query_doctor_schedule"
:
"schedule"
,
"create_doctor_schedule"
:
"schedule"
,
// v17: 支付订单
"query_order_list"
:
"payment"
,
"query_order_detail"
:
"payment"
,
// v17: 医生收入
"query_income_stats"
:
"income"
,
"query_income_records"
:
"income"
,
// v17: 管理统计
"query_dashboard_stats"
:
"dashboard"
,
"query_dashboard_trend"
:
"dashboard"
,
"query_user_list"
:
"user_manage"
,
"query_system_logs"
:
"system"
,
}
for
name
,
tool
:=
range
r
.
All
()
{
...
...
server/internal/agent/seed_prompts.go
View file @
da795257
...
...
@@ -7,7 +7,12 @@ import (
"internet-hospital/pkg/database"
)
// currentPromptVersion 当前代码中提示词模板的版本号
// 每次修改提示词内容时递增此值,ensurePromptTemplates 会自动同步到数据库
const
currentPromptVersion
=
2
// ensurePromptTemplates 确保所有内置提示词模板存在于数据库中(种子数据)
// 逻辑:不存在则创建;已存在但版本低于代码版本则更新内容
func
ensurePromptTemplates
()
{
db
:=
database
.
GetDB
()
if
db
==
nil
{
...
...
@@ -15,7 +20,7 @@ func ensurePromptTemplates() {
}
templates
:=
[]
model
.
PromptTemplate
{
//
患者智能助手系统提示词
//
==================== 患者智能助手系统提示词 ====================
{
TemplateKey
:
"patient_universal_agent_system"
,
Name
:
"患者智能助手-系统提示词"
,
...
...
@@ -26,10 +31,28 @@ func ensurePromptTemplates() {
你的核心能力:
1. **预问诊**:通过友好对话收集症状信息(持续时间、严重程度、伴随症状),利用知识库分析症状,推荐合适的就诊科室
2. **找医生/挂号**:根据患者症状推荐科室和医生,帮助了解就医流程
2. **找医生/挂号**:根据患者症状推荐科室和医生,
查看医生详情和排班,
帮助了解就医流程
3. **健康咨询**:搜索医学知识提供健康科普,查询药品信息和用药指导
4. **随访管理**:查询处方和用药情况,提醒按时用药,评估病情变化,生成随访计划
5. **药品查询**:查询药品信息、规格、用法和注意事项
6. **问诊管理**:查询问诊列表和详情,帮助患者创建新的问诊(需指定医生和主诉)
7. **处方查询**:查询处方列表和详情(药品明细、用法用量、费用)
8. **健康档案**:查询个���健康档案、健康指标(血压/血糖/心率/体温)趋势、检验报告及AI解读
9. **慢病管理**:查询慢病档案、创建慢病记录、申请续方、查看续方状态
10. **支付订单**:查询支付订单列表和详情,了解订单状态
工具使用指南:
- 当患者想看病时,先用 recommend_department 推荐科室,再用 navigate_page 导航到找医生页面
- 当患者想找医生时,用 query_doctor_list 搜索,用 query_doctor_detail 查详情和排班
- 当患者问科室时,用 query_department_list 查科室列表
- 当患者问"我的问诊"时,使用 query_consultation_list 查询
- 当患者问"我的处方/药"时,使用 query_prescription_list 查询
- 当患者问"我的健康数据"时,使用 query_health_metrics 查询
- 当患者问"我的订单/支付"时,用 query_order_list 查询,用 query_order_detail 查详情
- 当患者问慢病/续方时,用 query_chronic_records 和 query_renewal_requests 查询
- 当患者要记录血压/���糖等指标时,用 record_health_metric 记录
- 当患者查排班时,用 query_doctor_schedule 查询
- 创建问诊前需确认患者提供了医生ID和主诉信息
使用原则:
- 用通俗易懂、温和专业的中文与患者交流
...
...
@@ -38,16 +61,16 @@ func ensurePromptTemplates() {
- 关注患者的用药依从性和健康状况变化
- 所有医疗建议仅供参考,请以专业医生判断为准
页
面
导航能力:
页
��
导航能力:
- 你可以使用 navigate_page 工具为用户准备页面导航
- 【重要】调用工具后,页面不会自动打开,用户需要点击工具结果中的"打开页面"按钮才能跳转
- 因此你的回复应该说"我已为您准备好XXX页面,请点击下方按钮打开",而不是"已为您打开XXX页面"
- 你只能导航到 patient_* 开头的页面,不能访问管理端或医生端页面
- 在回复中,你也可以使用 ACTIONS 标记提供导航按钮,格式:<!--ACTIONS:[{"type":"navigate","label":"页面名称","path":"/路径"}]-->`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
医生智能助手系统提示词
//
==================== 医生智能助手系统提示词 ====================
{
TemplateKey
:
"doctor_universal_agent_system"
,
Name
:
"医生智能助手-系统提示词"
,
...
...
@@ -63,10 +86,26 @@ func ensurePromptTemplates() {
4. **病历生成**:根据对话记录生成标准门诊病历(主诉、现病史、既往史、查体、辅助检查、初步诊断、处置意见)
5. **随访计划**:制定随访方案,包含复诊时间、复查项目、用药提醒、生活方式建议
6. **医嘱生成**:生成结构化医嘱(检查、治疗、护理、饮食、活动)
7. **问诊管理**:查看等候队列、问诊列表和详情,快速接诊
8. **处方管理**:查看处方列表和详情,搜索药品目录(名称、规格、库存、价格)
9. **患者档案**:查询患者健康档案、健康指标趋势、检验报告及AI解读
10. **排班管理**:查看和创建排班时段
11. **续方审批**:查看患者续方申请,通过导航页面进行审批
12. **收入管理**:查看收入统计(余额、本月收入、问诊量)和收入明细
诊断流程:首先查询患者病历了解病史 → 使用知识库检索诊断标准 → 综合分析后给出建议
处方审核流程:检查药物相互作用 → 检查禁忌症 → 验证剂量 → 综合评估
工具使用指南:
- 查看等候队列用 query_waiting_queue,接诊用 accept_consultation
- 查问诊列表用 query_consultation_list,查详情用 query_consultation_detail
- 查患者信息用 query_patient_profile + query_health_metrics + query_lab_reports
- 搜索药品用 search_medicine_catalog,开方通过 navigate_page 导航到开方页面
- 查看处方用 query_prescription_list / query_prescription_detail
- 查看/创建排班用 query_doctor_schedule / create_doctor_schedule
- 查看收入统计用 query_income_stats,查收入明细用 query_income_records
- 查续方申请用 query_renewal_requests,审批通过 navigate_page 导航到审批页面
使用原则:
- 基于循证医学原则提供建议
- 主动使用工具获取真实数据
...
...
@@ -80,9 +119,9 @@ func ensurePromptTemplates() {
- 你只能导航到 doctor_* 开头的页面,不能访问管理端或患者端页面
- 在回复中,你也可以使用 ACTIONS 标记提供导航按钮,格式:<!--ACTIONS:[{"type":"navigate","label":"页面名称","path":"/路径"}]-->`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
管理员智能助手系统提示词
//
==================== 管理员智能助手系统提示词 ====================
{
TemplateKey
:
"admin_universal_agent_system"
,
Name
:
"管理员智能助手-系统提示词"
,
...
...
@@ -92,13 +131,24 @@ func ensurePromptTemplates() {
Content
:
`你是互联网医院管理后台的专属AI智能助手,帮助管理员高效管理平台。
你的核心能力:
1. **运营数据**:查询
和计算运营指标,分析平台运行状况
1. **运营数据**:查询
仪表盘统计(用户数、医生数、问诊量、收入)和运营趋势
2. **Agent监控**:调用其他Agent获取信息,监控Agent运行状态
3. **工作流管理**:触发和查询工作流执行状态
4. **知识库管理**:浏览知识库集合,了解知识库使用情况
5. **人工审核**:发起和管理人工审核任务
6. **通知管理**:发送系统通知
7. **药品/医学查询**:查询药品信息和医学知识辅助决策
8. **业务数据查询**:查看问诊记录、处方记录、支付订单,支持全局查看
9. **用户管理**:查询系统用户列表,按角色/状态/关键词搜索
10. **系统日志**:查看系统操作日志,按操作类型和资源过滤
工具使用指南:
- 查运营数据用 query_dashboard_stats,查趋势用 query_dashboard_trend
- 查用户列表用 query_user_list,查系统日志用 query_system_logs
- 查问诊数据用 query_consultation_list / query_consultation_detail
- 查处方数据用 query_prescription_list / query_prescription_detail
- 查订单数据用 query_order_list / query_order_detail
- 需要新的数据查询能力时,使用 generate_tool 动态生成 SQL 工具
使用原则:
- 以简洁专业的方式回答管理员的问题
...
...
@@ -114,9 +164,9 @@ func ensurePromptTemplates() {
- 支持 open_add 操作准备新增弹窗(如新增医生、新增科室等)
- 在回复中,你也可以使用 ACTIONS 标记提供导航按钮,格式:<!--ACTIONS:[{"type":"navigate","label":"页面名称","path":"/路径"}]-->`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
ACTIONS按钮格式说明(通用附加提示词)
//
==================== ACTIONS按钮格式说明(通用附加提示词) ====================
{
TemplateKey
:
"actions_button_format"
,
Name
:
"建议操作按钮格式说明"
,
...
...
@@ -144,9 +194,9 @@ func ensurePromptTemplates() {
- 导航路径必须是系统中存在的页面
`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
预问诊对话提示词
//
==================== 预问诊对话提示词 ====================
{
TemplateKey
:
"pre_consult_chat"
,
Name
:
"预问诊对话提示词"
,
...
...
@@ -163,9 +213,9 @@ func ensurePromptTemplates() {
6. 不做确定性诊断,用"建议"、"可能"等措辞
7. 如果患者情况紧急,明确建议立即就医`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
预问诊分析提示词
//
==================== 预问诊分析提示词 ====================
{
TemplateKey
:
"pre_consult_analysis"
,
Name
:
"预问诊综合分析提示词"
,
...
...
@@ -194,9 +244,9 @@ func ensurePromptTemplates() {
请确保分析专业准确,建议切实可行。`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
鉴别诊断提示词(直接调用模型,不走智能体)
//
==================== 鉴别诊断提示词 ====================
{
TemplateKey
:
"consult_diagnosis"
,
Name
:
"鉴别诊断分析"
,
...
...
@@ -232,9 +282,9 @@ func ensurePromptTemplates() {
**注意:以上分析仅供临床参考,最终诊断请结合实际检查结果。**`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
用药建议提示词(直接调用模型,不走智能体)
//
==================== 用药建议提示词 ====================
{
TemplateKey
:
"consult_medication"
,
Name
:
"用药建议分析"
,
...
...
@@ -270,9 +320,9 @@ func ensurePromptTemplates() {
**注意:以上用药建议仅供临床参考,请医生根据患者实际情况调整处方。**`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
//
检验报告解读提示词
//
==================== 检验报告解读提示词 ====================
{
TemplateKey
:
"lab_report_interpret"
,
Name
:
"检验报告AI解读提示词"
,
...
...
@@ -280,7 +330,7 @@ func ensurePromptTemplates() {
TemplateType
:
"system"
,
Content
:
`你是一位专业的医学检验报告解读专家。请对检验报告进行通俗易懂的解读,说明各项指标的含义和健康建议。请用中文回答,分条列出关键信息,避免使用过于专业的术语,让普通患者能够理解。`
,
Status
:
"active"
,
Version
:
1
,
Version
:
currentPromptVersion
,
},
}
...
...
@@ -292,7 +342,19 @@ func ensurePromptTemplates() {
if
err
:=
db
.
Create
(
&
tmpl
)
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[PromptTemplates] 创建提示词模板失败 %s: %v"
,
tmpl
.
TemplateKey
,
err
)
}
else
{
log
.
Printf
(
"[PromptTemplates] 已创建提示词模板: %s"
,
tmpl
.
TemplateKey
)
log
.
Printf
(
"[PromptTemplates] 已创建提示词模板: %s (v%d)"
,
tmpl
.
TemplateKey
,
tmpl
.
Version
)
}
}
else
if
existing
.
Version
<
tmpl
.
Version
{
// 已存在但版本低于代码版本 → 更新内容
updates
:=
map
[
string
]
interface
{}{
"content"
:
tmpl
.
Content
,
"version"
:
tmpl
.
Version
,
"name"
:
tmpl
.
Name
,
}
if
err
:=
db
.
Model
(
&
existing
)
.
Updates
(
updates
)
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[PromptTemplates] 更新提示词模板失败 %s: %v"
,
tmpl
.
TemplateKey
,
err
)
}
else
{
log
.
Printf
(
"[PromptTemplates] 已更新提示词模板: %s (v%d → v%d)"
,
tmpl
.
TemplateKey
,
existing
.
Version
,
tmpl
.
Version
)
}
}
}
...
...
server/internal/agent/service.go
View file @
da795257
...
...
@@ -66,7 +66,7 @@ func buildAgentFromDef(def model.AgentDefinition) *agent.ReActAgent {
}
}
// 加载系统提示词:优先从 prompt_template_id 加载,
否则
使用 system_prompt 字段
// 加载系统提示词:优先从 prompt_template_id 加载,
再按 agent_id 查找,最后
使用 system_prompt 字段
systemPrompt
:=
def
.
SystemPrompt
if
def
.
PromptTemplateID
!=
nil
{
db
:=
database
.
GetDB
()
...
...
@@ -78,6 +78,17 @@ func buildAgentFromDef(def model.AgentDefinition) *agent.ReActAgent {
}
}
}
// 如果仍为空,按 agent_id + template_type=system 从 prompt_templates 表加载
if
systemPrompt
==
""
&&
def
.
AgentID
!=
""
{
db
:=
database
.
GetDB
()
if
db
!=
nil
{
var
template
model
.
PromptTemplate
if
err
:=
db
.
Where
(
"agent_id = ? AND template_type = 'system' AND status = 'active'"
,
def
.
AgentID
)
.
First
(
&
template
)
.
Error
;
err
==
nil
{
systemPrompt
=
template
.
Content
}
}
}
// 加载技能包中的工具和提示词
var
skillIDs
[]
string
...
...
@@ -159,6 +170,7 @@ func getOrchestrationSkills(def model.AgentDefinition) []model.AgentSkill {
}
// ensureBuiltinAgents 如果数据库中不存在内置Agent,则写入默认配置
// 如果已存在,同步工具列表(Tools字段)以确保新增工具生效
func
(
s
*
AgentService
)
ensureBuiltinAgents
()
{
db
:=
database
.
GetDB
()
if
db
==
nil
{
...
...
@@ -166,15 +178,6 @@ func (s *AgentService) ensureBuiltinAgents() {
}
defaults
:=
defaultAgentDefinitions
()
for
_
,
def
:=
range
defaults
{
// 如果内存中已有(来自数据库),跳过
s
.
mu
.
RLock
()
_
,
exists
:=
s
.
agents
[
def
.
AgentID
]
s
.
mu
.
RUnlock
()
if
exists
{
continue
}
// 写入数据库
var
existing
model
.
AgentDefinition
if
err
:=
db
.
Where
(
"agent_id = ?"
,
def
.
AgentID
)
.
First
(
&
existing
)
.
Error
;
err
!=
nil
{
// 不存在则创建
...
...
@@ -183,7 +186,18 @@ func (s *AgentService) ensureBuiltinAgents() {
continue
}
existing
=
def
log
.
Printf
(
"[AgentService] 已创建内置Agent: %s"
,
def
.
AgentID
)
}
else
if
existing
.
Tools
!=
def
.
Tools
{
// 已存在但工具列表有变化 → 同步更新
if
err
:=
db
.
Model
(
&
existing
)
.
Update
(
"tools"
,
def
.
Tools
)
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[AgentService] 同步Agent工具列表失败 %s: %v"
,
def
.
AgentID
,
err
)
}
else
{
log
.
Printf
(
"[AgentService] 已同步Agent工具列表: %s"
,
def
.
AgentID
)
existing
.
Tools
=
def
.
Tools
}
}
// 如果内存中已有(来自 loadFromDB),用同步后的定义重建
s
.
mu
.
Lock
()
s
.
agents
[
def
.
AgentID
]
=
buildAgentFromDef
(
existing
)
s
.
mu
.
Unlock
()
...
...
@@ -248,37 +262,41 @@ func (s *AgentService) Chat(ctx context.Context, agentID, userID, userRole, sess
db
:=
database
.
GetDB
()
// 加载或创建会话
if
sessionID
==
""
{
sessionID
=
uuid
.
New
()
.
String
()
}
// 加载会话(前端通过 POST /agent/sessions 预创建)
var
session
model
.
AgentSession
db
.
Where
(
"session_id = ?"
,
sessionID
)
.
First
(
&
session
)
if
sessionID
!=
""
{
db
.
Where
(
"session_id = ?"
,
sessionID
)
.
First
(
&
session
)
}
if
session
.
ID
==
0
{
if
sessionID
==
""
{
sessionID
=
uuid
.
New
()
.
String
()
}
session
=
model
.
AgentSession
{
SessionID
:
sessionID
,
AgentID
:
agentID
,
UserID
:
userID
,
History
:
"[]"
,
Context
:
"null"
,
PageContext
:
"null"
,
EntityContext
:
"null"
,
Status
:
"active"
,
UserRole
:
userRole
,
}
db
.
Create
(
&
session
)
}
// 解析历史消息
var
history
[]
ai
.
ChatMessage
if
session
.
History
!=
""
{
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
history
)
}
input
:=
agent
.
AgentInput
{
SessionID
:
sessionID
,
UserID
:
userID
,
UserRole
:
userRole
,
Message
:
message
,
Context
:
contextData
,
History
:
history
,
SessionID
:
sessionID
,
UserID
:
userID
,
UserRole
:
userRole
,
Message
:
message
,
Context
:
contextData
,
History
:
history
,
}
start
:=
time
.
Now
()
output
,
err
:=
a
.
Run
(
ctx
,
input
)
durationMs
:=
int
(
time
.
Since
(
start
)
.
Milliseconds
())
if
err
!=
nil
{
return
nil
,
err
}
// 更新
历史
// 更新
会话
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
"user"
,
Content
:
message
},
ai
.
ChatMessage
{
Role
:
"assistant"
,
Content
:
output
.
Response
},
...
...
@@ -286,9 +304,8 @@ func (s *AgentService) Chat(ctx context.Context, agentID, userID, userRole, sess
historyJSON
,
_
:=
json
.
Marshal
(
history
)
contextJSON
,
_
:=
json
.
Marshal
(
contextData
)
// v16: 提取页面上下文
currentPage
:=
""
pageContextJSON
:=
""
pageContextJSON
:=
"
null
"
if
contextData
!=
nil
{
if
page
,
ok
:=
contextData
[
"page"
]
.
(
map
[
string
]
interface
{});
ok
{
if
pathname
,
ok
:=
page
[
"pathname"
]
.
(
string
);
ok
{
...
...
@@ -299,31 +316,17 @@ func (s *AgentService) Chat(ctx context.Context, agentID, userID, userRole, sess
}
}
if
session
.
ID
==
0
{
session
=
model
.
AgentSession
{
SessionID
:
sessionID
,
AgentID
:
agentID
,
UserID
:
userID
,
History
:
string
(
historyJSON
),
Context
:
string
(
contextJSON
),
Status
:
"active"
,
UserRole
:
userRole
,
CurrentPage
:
currentPage
,
PageContext
:
pageContextJSON
,
MessageCount
:
2
,
TotalTokens
:
output
.
TotalTokens
,
}
db
.
Create
(
&
session
)
}
else
{
db
.
Model
(
&
session
)
.
Updates
(
map
[
string
]
interface
{}{
"history"
:
string
(
historyJSON
),
"user_role"
:
userRole
,
"current_page"
:
currentPage
,
"page_context"
:
pageContextJSON
,
"message_count"
:
session
.
MessageCount
+
2
,
"total_tokens"
:
session
.
TotalTokens
+
output
.
TotalTokens
,
"updated_at"
:
time
.
Now
(),
})
if
err
:=
db
.
Model
(
&
session
)
.
Updates
(
map
[
string
]
interface
{}{
"history"
:
string
(
historyJSON
),
"context"
:
string
(
contextJSON
),
"user_role"
:
userRole
,
"current_page"
:
currentPage
,
"page_context"
:
pageContextJSON
,
"message_count"
:
session
.
MessageCount
+
2
,
"total_tokens"
:
session
.
TotalTokens
+
output
.
TotalTokens
,
"updated_at"
:
time
.
Now
(),
})
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[Chat] 更新会话失败: %v"
,
err
)
}
// 记录执行日志
...
...
@@ -357,22 +360,87 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
return
}
// v15: 检查是否有多Agent编排的技能
db
:=
database
.
GetDB
()
var
agentDef
model
.
AgentDefinition
if
db
!=
nil
{
db
.
Where
(
"agent_id = ?"
,
agentID
)
.
First
(
&
agentDef
)
// 加载会话(前端通过 POST /agent/sessions 预创建)
var
session
model
.
AgentSession
if
sessionID
!=
""
{
db
.
Where
(
"session_id = ?"
,
sessionID
)
.
First
(
&
session
)
}
if
session
.
ID
==
0
{
// 兼容:前端未预创建时自动创建
if
sessionID
==
""
{
sessionID
=
uuid
.
New
()
.
String
()
}
session
=
model
.
AgentSession
{
SessionID
:
sessionID
,
AgentID
:
agentID
,
UserID
:
userID
,
History
:
"[]"
,
Context
:
"null"
,
PageContext
:
"null"
,
EntityContext
:
"null"
,
Status
:
"active"
,
UserRole
:
userRole
,
}
if
err
:=
db
.
Create
(
&
session
)
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[ChatStream] 自动创建会话失败: %v"
,
err
)
}
}
// 发送 session 事件
sessionJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"session_id"
:
sessionID
})
emit
(
"session"
,
string
(
sessionJSON
))
// saveSession 统一的会话保存函数
saveSession
:=
func
(
responseText
string
,
tokens
int
)
{
var
history
[]
ai
.
ChatMessage
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
},
)
historyJSON
,
_
:=
json
.
Marshal
(
history
)
contextJSON
,
_
:=
json
.
Marshal
(
contextData
)
currentPage
:=
""
pageContextJSON
:=
"null"
if
contextData
!=
nil
{
if
page
,
ok
:=
contextData
[
"page"
]
.
(
map
[
string
]
interface
{});
ok
{
if
pathname
,
ok
:=
page
[
"pathname"
]
.
(
string
);
ok
{
currentPage
=
pathname
}
pageCtxBytes
,
_
:=
json
.
Marshal
(
page
)
pageContextJSON
=
string
(
pageCtxBytes
)
}
}
if
err
:=
db
.
Model
(
&
session
)
.
Updates
(
map
[
string
]
interface
{}{
"history"
:
string
(
historyJSON
),
"context"
:
string
(
contextJSON
),
"user_role"
:
userRole
,
"current_page"
:
currentPage
,
"page_context"
:
pageContextJSON
,
"message_count"
:
session
.
MessageCount
+
2
,
"total_tokens"
:
session
.
TotalTokens
+
tokens
,
"updated_at"
:
time
.
Now
(),
})
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[ChatStream] 更新会话失败: %v"
,
err
)
}
else
{
log
.
Printf
(
"[ChatStream] 会话保存成功: session_id=%s, messages=%d"
,
sessionID
,
len
(
history
))
}
}
// v15: 检查是否有多Agent编排的技能
var
agentDef
model
.
AgentDefinition
db
.
Where
(
"agent_id = ?"
,
agentID
)
.
First
(
&
agentDef
)
if
orchSkills
:=
getOrchestrationSkills
(
agentDef
);
len
(
orchSkills
)
>
0
{
skill
:=
orchSkills
[
0
]
// 取第一个编排技能执行
skill
:=
orchSkills
[
0
]
executor
:=
agent
.
GetSkillExecutor
()
if
executor
!=
nil
{
if
sessionID
==
""
{
sessionID
=
uuid
.
New
()
.
String
()
}
sessionJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"session_id"
:
sessionID
})
emit
(
"session"
,
string
(
sessionJSON
))
thinkJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
interface
{}{
"iteration"
:
1
,
"status"
:
"orchestrating_skill"
,
"skill"
:
skill
.
Name
})
emit
(
"thinking"
,
string
(
thinkJSON
))
...
...
@@ -387,7 +455,6 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
log
.
Printf
(
"[ChatStream] 技能编排失败 %s: %v"
,
skill
.
SkillID
,
result
.
Error
)
// 回退到普通 Agent 执行(不中断)
}
else
{
// 流式分块发送编排结果
chunkSize
:=
3
runes
:=
[]
rune
(
result
.
FinalResponse
)
for
i
:=
0
;
i
<
len
(
runes
);
i
+=
chunkSize
{
...
...
@@ -406,24 +473,15 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
"mode"
:
result
.
Mode
,
})
emit
(
"done"
,
string
(
doneData
))
saveSession
(
result
.
FinalResponse
,
0
)
return
}
}
}
// db already declared above for orchestration check; reuse it below
if
sessionID
==
""
{
sessionID
=
uuid
.
New
()
.
String
()
}
var
session
model
.
AgentSession
db
.
Where
(
"session_id = ?"
,
sessionID
)
.
First
(
&
session
)
// 发送 session 事件
sessionJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"session_id"
:
sessionID
})
emit
(
"session"
,
string
(
sessionJSON
))
// 普通 Agent 执行
var
history
[]
ai
.
ChatMessage
if
session
.
History
!=
""
{
if
session
.
History
!=
""
&&
session
.
History
!=
"[]"
{
json
.
Unmarshal
([]
byte
(
session
.
History
),
&
history
)
}
...
...
@@ -437,8 +495,6 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
}
start
:=
time
.
Now
()
// 将 StreamEvent 转发为 SSE 事件
onEvent
:=
func
(
ev
agent
.
StreamEvent
)
error
{
data
,
_
:=
json
.
Marshal
(
ev
.
Data
)
emit
(
string
(
ev
.
Type
),
string
(
data
))
...
...
@@ -451,56 +507,12 @@ func (s *AgentService) ChatStream(ctx context.Context, agentID, userID, userRole
if
err
!=
nil
{
errJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"error"
:
err
.
Error
()})
emit
(
"error"
,
string
(
errJSON
))
return
}
// 持久化会话
history
=
append
(
history
,
ai
.
ChatMessage
{
Role
:
"user"
,
Content
:
message
},
ai
.
ChatMessage
{
Role
:
"assistant"
,
Content
:
output
.
Response
},
)
historyJSON
,
_
:=
json
.
Marshal
(
history
)
contextJSON
,
_
:=
json
.
Marshal
(
contextData
)
// v16: 提取页面上下文(ChatStream)
currentPageStream
:=
""
pageContextJSONStream
:=
""
if
contextData
!=
nil
{
if
page
,
ok
:=
contextData
[
"page"
]
.
(
map
[
string
]
interface
{});
ok
{
if
pathname
,
ok
:=
page
[
"pathname"
]
.
(
string
);
ok
{
currentPageStream
=
pathname
}
pageCtxBytes
,
_
:=
json
.
Marshal
(
page
)
pageContextJSONStream
=
string
(
pageCtxBytes
)
if
output
==
nil
{
return
}
}
if
session
.
ID
==
0
{
session
=
model
.
AgentSession
{
SessionID
:
sessionID
,
AgentID
:
agentID
,
UserID
:
userID
,
History
:
string
(
historyJSON
),
Context
:
string
(
contextJSON
),
Status
:
"active"
,
UserRole
:
userRole
,
CurrentPage
:
currentPageStream
,
PageContext
:
pageContextJSONStream
,
MessageCount
:
2
,
TotalTokens
:
output
.
TotalTokens
,
}
db
.
Create
(
&
session
)
}
else
{
db
.
Model
(
&
session
)
.
Updates
(
map
[
string
]
interface
{}{
"history"
:
string
(
historyJSON
),
"user_role"
:
userRole
,
"current_page"
:
currentPageStream
,
"page_context"
:
pageContextJSONStream
,
"message_count"
:
session
.
MessageCount
+
2
,
"total_tokens"
:
session
.
TotalTokens
+
output
.
TotalTokens
,
"updated_at"
:
time
.
Now
(),
})
}
saveSession
(
output
.
Response
,
output
.
TotalTokens
)
// 记录执行日志
inputJSON
,
_
:=
json
.
Marshal
(
input
)
...
...
server/internal/model/chronic.go
View file @
da795257
...
...
@@ -28,15 +28,15 @@ func (ChronicRecord) TableName() string { return "chronic_records" }
type
RenewalRequest
struct
{
ID
string
`gorm:"type:uuid;primaryKey" json:"id"`
UserID
string
`gorm:"type:uuid;index;not null" json:"user_id"`
ChronicID
string
`gorm:"type:uuid;index" json:"chronic_id"`
ChronicID
*
string
`gorm:"type:uuid;index" json:"chronic_id"`
DiseaseName
string
`gorm:"type:varchar(100)" json:"disease_name"`
Medicines
string
`gorm:"type:text" json:"medicines"`
// JSON array
Reason
string
`gorm:"type:text" json:"reason"`
Status
string
`gorm:"type:varchar(20);default:'pending'" json:"status"`
// pending|approved|rejected
DoctorID
string
`gorm:"type:uuid" json:"doctor_id"`
DoctorID
*
string
`gorm:"type:uuid" json:"doctor_id"`
DoctorName
string
`gorm:"type:varchar(50)" json:"doctor_name"`
DoctorNote
string
`gorm:"type:text" json:"doctor_note"`
PrescriptionID
string
`gorm:"type:uuid" json:"prescription_id"`
PrescriptionID
*
string
`gorm:"type:uuid" json:"prescription_id"`
AIAdvice
string
`gorm:"type:text" json:"ai_advice"`
CreatedAt
time
.
Time
`json:"created_at"`
UpdatedAt
time
.
Time
`json:"updated_at"`
...
...
server/internal/service/chronic/handler.go
View file @
da795257
...
...
@@ -61,7 +61,7 @@ func (h *Handler) CreateRecord(c *gin.Context) {
}
r
,
err
:=
h
.
service
.
CreateChronicRecord
(
c
.
Request
.
Context
(),
userID
.
(
string
),
&
req
)
if
err
!=
nil
{
response
.
Error
(
c
,
500
,
"创建慢病档案失败
"
)
response
.
Error
(
c
,
500
,
"创建慢病档案失败
: "
+
err
.
Error
()
)
return
}
response
.
Success
(
c
,
r
)
...
...
@@ -110,7 +110,7 @@ func (h *Handler) CreateRenewal(c *gin.Context) {
}
r
,
err
:=
h
.
service
.
CreateRenewal
(
c
.
Request
.
Context
(),
userID
.
(
string
),
&
req
)
if
err
!=
nil
{
response
.
Error
(
c
,
500
,
"创建续方申请失败
"
)
response
.
Error
(
c
,
500
,
"创建续方申请失败
: "
+
err
.
Error
()
)
return
}
response
.
Success
(
c
,
r
)
...
...
server/internal/service/chronic/service.go
View file @
da795257
...
...
@@ -93,9 +93,13 @@ func (s *Service) ListRenewals(ctx context.Context, userID string) ([]model.Rene
func
(
s
*
Service
)
CreateRenewal
(
ctx
context
.
Context
,
userID
string
,
req
*
RenewalReq
)
(
*
model
.
RenewalRequest
,
error
)
{
medsJSON
,
_
:=
json
.
Marshal
(
req
.
Medicines
)
var
chronicID
*
string
if
req
.
ChronicID
!=
""
{
chronicID
=
&
req
.
ChronicID
}
r
:=
&
model
.
RenewalRequest
{
ID
:
uuid
.
New
()
.
String
(),
UserID
:
userID
,
ChronicID
:
req
.
C
hronicID
,
DiseaseName
:
req
.
DiseaseName
,
ChronicID
:
c
hronicID
,
DiseaseName
:
req
.
DiseaseName
,
Medicines
:
string
(
medsJSON
),
Reason
:
req
.
Reason
,
Status
:
"pending"
,
}
if
err
:=
s
.
db
.
WithContext
(
ctx
)
.
Create
(
r
)
.
Error
;
err
!=
nil
{
...
...
server/pkg/agent/tools/chronic_record_create.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateChronicRecordFn 创建慢病记录回调,由 main.go 注入
var
CreateChronicRecordFn
func
(
ctx
context
.
Context
,
userID
,
diseaseName
,
hospital
,
doctorName
,
currentMeds
,
notes
string
)
(
string
,
error
)
// CreateChronicRecordTool 创建慢病档案
type
CreateChronicRecordTool
struct
{}
func
(
t
*
CreateChronicRecordTool
)
Name
()
string
{
return
"create_chronic_record"
}
func
(
t
*
CreateChronicRecordTool
)
Description
()
string
{
return
"患者创建慢病档案记录,记录慢性疾病的诊断信息和当前用药情况"
}
func
(
t
*
CreateChronicRecordTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"disease_name"
,
Type
:
"string"
,
Description
:
"疾病名称(如高血压、糖尿病)"
,
Required
:
true
},
{
Name
:
"hospital"
,
Type
:
"string"
,
Description
:
"确诊医院(可选)"
,
Required
:
false
},
{
Name
:
"doctor_name"
,
Type
:
"string"
,
Description
:
"确诊医生(可选)"
,
Required
:
false
},
{
Name
:
"current_meds"
,
Type
:
"string"
,
Description
:
"当前用药情况(可选)"
,
Required
:
false
},
{
Name
:
"notes"
,
Type
:
"string"
,
Description
:
"备注(可选)"
,
Required
:
false
},
}
}
func
(
t
*
CreateChronicRecordTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"patient"
{
return
nil
,
fmt
.
Errorf
(
"仅患者可创建慢病记录"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
diseaseName
,
ok
:=
params
[
"disease_name"
]
.
(
string
)
if
!
ok
||
diseaseName
==
""
{
return
nil
,
fmt
.
Errorf
(
"disease_name 必填"
)
}
hospital
,
_
:=
params
[
"hospital"
]
.
(
string
)
doctorName
,
_
:=
params
[
"doctor_name"
]
.
(
string
)
currentMeds
,
_
:=
params
[
"current_meds"
]
.
(
string
)
notes
,
_
:=
params
[
"notes"
]
.
(
string
)
if
CreateChronicRecordFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"慢病服务未初始化"
)
}
recordID
,
err
:=
CreateChronicRecordFn
(
ctx
,
userID
,
diseaseName
,
hospital
,
doctorName
,
currentMeds
,
notes
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"创建慢病记录失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"record_id"
:
recordID
,
"disease_name"
:
diseaseName
,
"message"
:
"慢病档案已创建"
,
},
nil
}
server/pkg/agent/tools/chronic_records.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ChronicRecordsTool 查询慢病记录
type
ChronicRecordsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
ChronicRecordsTool
)
Name
()
string
{
return
"query_chronic_records"
}
func
(
t
*
ChronicRecordsTool
)
Description
()
string
{
return
"查询患者的慢病档案记录(疾病名称、确诊日期、当前用药、控制状态)"
}
func
(
t
*
ChronicRecordsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生使用,患者无需传入)"
,
Required
:
false
},
}
}
func
(
t
*
ChronicRecordsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的慢病记录"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, disease_name, diagnosis_date, hospital, doctor_name,
current_meds, control_status, notes, created_at
FROM chronic_records
WHERE user_id = ? AND deleted_at IS NULL
ORDER BY created_at DESC`
,
targetID
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"records"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"records"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/consultation_accept.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// AcceptConsultFn 接诊回调,由 WireCallbacks 注入
var
AcceptConsultFn
func
(
ctx
context
.
Context
,
consultID
,
doctorUserID
string
)
error
// AcceptConsultationTool 医生接诊
type
AcceptConsultationTool
struct
{}
func
(
t
*
AcceptConsultationTool
)
Name
()
string
{
return
"accept_consultation"
}
func
(
t
*
AcceptConsultationTool
)
Description
()
string
{
return
"医生接受一个等候中的问诊,将问诊状态从pending变为in_progress"
}
func
(
t
*
AcceptConsultationTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"consultation_id"
,
Type
:
"string"
,
Description
:
"要接诊的问诊ID"
,
Required
:
true
},
}
}
func
(
t
*
AcceptConsultationTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可接诊"
)
}
consultID
,
ok
:=
params
[
"consultation_id"
]
.
(
string
)
if
!
ok
||
consultID
==
""
{
return
nil
,
fmt
.
Errorf
(
"consultation_id 必填"
)
}
if
AcceptConsultFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"接诊服务未初始化"
)
}
if
err
:=
AcceptConsultFn
(
ctx
,
consultID
,
userID
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"接诊失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"consultation_id"
:
consultID
,
"status"
:
"in_progress"
,
"message"
:
"接诊成功,问诊已开始"
,
},
nil
}
server/pkg/agent/tools/consultation_create.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateConsultFn 创建问诊回调,由 WireCallbacks 注入
// 参数: ctx, patientID, doctorID, consultType, chiefComplaint, medicalHistory
// 返回: 问诊ID, 流水号, error
var
CreateConsultFn
func
(
ctx
context
.
Context
,
patientID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
string
)
(
string
,
string
,
error
)
// CreateConsultationTool 创建问诊(患者发起)
type
CreateConsultationTool
struct
{}
func
(
t
*
CreateConsultationTool
)
Name
()
string
{
return
"create_consultation"
}
func
(
t
*
CreateConsultationTool
)
Description
()
string
{
return
"患者创建新的问诊,需要指定医生ID、问诊类型和主诉。创建成功后返回问诊ID和流水号"
}
func
(
t
*
CreateConsultationTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"doctor_id"
,
Type
:
"string"
,
Description
:
"接诊医生ID(从 query_doctor_list 获取)"
,
Required
:
true
},
{
Name
:
"type"
,
Type
:
"string"
,
Description
:
"问诊类型"
,
Required
:
true
,
Enum
:
[]
string
{
"text"
,
"video"
}},
{
Name
:
"chief_complaint"
,
Type
:
"string"
,
Description
:
"主诉(患者症状描述)"
,
Required
:
true
},
{
Name
:
"medical_history"
,
Type
:
"string"
,
Description
:
"既往病史(可选)"
,
Required
:
false
},
}
}
func
(
t
*
CreateConsultationTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"patient"
{
return
nil
,
fmt
.
Errorf
(
"仅患者可创建问诊"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
doctorID
,
ok
:=
params
[
"doctor_id"
]
.
(
string
)
if
!
ok
||
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"doctor_id 必填"
)
}
consultType
,
ok
:=
params
[
"type"
]
.
(
string
)
if
!
ok
||
(
consultType
!=
"text"
&&
consultType
!=
"video"
)
{
return
nil
,
fmt
.
Errorf
(
"type 必须为 text 或 video"
)
}
chiefComplaint
,
ok
:=
params
[
"chief_complaint"
]
.
(
string
)
if
!
ok
||
chiefComplaint
==
""
{
return
nil
,
fmt
.
Errorf
(
"chief_complaint 必填"
)
}
medicalHistory
,
_
:=
params
[
"medical_history"
]
.
(
string
)
if
CreateConsultFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊服务未初始化"
)
}
consultID
,
serialNumber
,
err
:=
CreateConsultFn
(
ctx
,
userID
,
doctorID
,
consultType
,
chiefComplaint
,
medicalHistory
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"创建问诊失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"consultation_id"
:
consultID
,
"serial_number"
:
serialNumber
,
"status"
:
"pending"
,
"message"
:
"问诊已创建,等待医生接诊"
,
},
nil
}
server/pkg/agent/tools/consultation_detail.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationDetailTool 查询问诊详情
type
ConsultationDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
ConsultationDetailTool
)
Name
()
string
{
return
"query_consultation_detail"
}
func
(
t
*
ConsultationDetailTool
)
Description
()
string
{
return
"查询问诊详情,包括患者信息、医生信息、主诉、诊断、消息记录,支持UUID或流水号(C开头)查询"
}
func
(
t
*
ConsultationDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"consultation_id"
,
Type
:
"string"
,
Description
:
"问诊ID(UUID)或流水号(C开头,如C20260305-0001)"
,
Required
:
true
},
{
Name
:
"include_messages"
,
Type
:
"boolean"
,
Description
:
"是否包含消息记录,默认true"
,
Required
:
false
},
}
}
func
(
t
*
ConsultationDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
consultID
,
ok
:=
params
[
"consultation_id"
]
.
(
string
)
if
!
ok
||
consultID
==
""
{
return
nil
,
fmt
.
Errorf
(
"consultation_id 必填"
)
}
includeMessages
:=
true
if
v
,
ok
:=
params
[
"include_messages"
]
.
(
bool
);
ok
{
includeMessages
=
v
}
// 支持流水号查询
resolvedID
:=
consultID
if
len
(
consultID
)
>
0
&&
consultID
[
0
]
==
'C'
{
var
id
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM consultations WHERE serial_number = ?"
,
consultID
)
.
Scan
(
&
id
)
.
Error
;
err
!=
nil
||
id
==
""
{
return
nil
,
fmt
.
Errorf
(
"流水号 %s 对应的问诊不存在"
,
consultID
)
}
resolvedID
=
id
}
// 查询问诊详情
var
detail
map
[
string
]
interface
{}
detailRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT c.id, c.serial_number, c.patient_id, c.doctor_id, c.type, c.status,
c.chief_complaint, c.medical_history, c.diagnosis, c.summary,
c.started_at, c.ended_at, c.created_at, c.satisfaction_score,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name,
u.real_name as patient_name, u.phone as patient_phone
FROM consultations c
LEFT JOIN doctors d ON c.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.id = ? AND c.deleted_at IS NULL`
,
resolvedID
)
.
Rows
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊不存在: %s"
,
consultID
)
}
defer
detailRows
.
Close
()
cols
,
_
:=
detailRows
.
Columns
()
if
!
detailRows
.
Next
()
{
return
nil
,
fmt
.
Errorf
(
"问诊不存在: %s"
,
consultID
)
}
detail
=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
detailRows
.
Scan
(
ptrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊不存在: %s"
,
consultID
)
}
for
i
,
col
:=
range
cols
{
detail
[
col
]
=
vals
[
i
]
}
// 权限校验:患者只能查自己的,医生只能查自己接诊的
if
userRole
==
"patient"
&&
detail
[
"patient_id"
]
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该问诊记录"
)
}
if
userRole
==
"doctor"
{
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
detail
[
"doctor_id"
]
!=
doctorID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该问诊记录"
)
}
}
// 可选:查询消息记录
if
includeMessages
{
var
messages
[]
map
[
string
]
interface
{}
msgRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, sender_type, content, content_type, created_at
FROM consult_messages
WHERE consult_id = ? AND deleted_at IS NULL
ORDER BY created_at ASC LIMIT 50`
,
resolvedID
)
.
Rows
()
if
err
==
nil
{
defer
msgRows
.
Close
()
msgCols
,
_
:=
msgRows
.
Columns
()
for
msgRows
.
Next
()
{
msg
:=
make
(
map
[
string
]
interface
{})
mVals
:=
make
([]
interface
{},
len
(
msgCols
))
mPtrs
:=
make
([]
interface
{},
len
(
msgCols
))
for
i
:=
range
mVals
{
mPtrs
[
i
]
=
&
mVals
[
i
]
}
if
err
:=
msgRows
.
Scan
(
mPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
msgCols
{
msg
[
col
]
=
mVals
[
i
]
}
messages
=
append
(
messages
,
msg
)
}
}
}
detail
[
"messages"
]
=
messages
detail
[
"message_count"
]
=
len
(
messages
)
}
return
detail
,
nil
}
server/pkg/agent/tools/consultation_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationListTool 查询问诊列表
type
ConsultationListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
ConsultationListTool
)
Name
()
string
{
return
"query_consultation_list"
}
func
(
t
*
ConsultationListTool
)
Description
()
string
{
return
"查询问诊列表:患者查自己的问诊记录,医生查自己接诊的问诊记录,支持按状态过滤"
}
func
(
t
*
ConsultationListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"问诊状态过滤(可选):pending/in_progress/completed/cancelled"
,
Required
:
false
,
Enum
:
[]
string
{
"pending"
,
"in_progress"
,
"completed"
,
"cancelled"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回记录数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
ConsultationListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
// 根据角色决定查询条件
var
roleField
string
switch
userRole
{
case
"doctor"
:
roleField
=
"doctor_id"
var
doctorID
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
.
Error
;
err
!=
nil
||
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"consultations"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
userID
=
doctorID
case
"admin"
:
roleField
=
""
default
:
roleField
=
"patient_id"
}
query
:=
"SELECT c.id, c.serial_number, c.chief_complaint, c.status, c.type, c.created_at, c.started_at, c.ended_at, "
+
"d.name as doctor_name, dep.name as department_name, u.real_name as patient_name "
+
"FROM consultations c "
+
"LEFT JOIN doctors d ON c.doctor_id = d.id "
+
"LEFT JOIN departments dep ON d.department_id = dep.id "
+
"LEFT JOIN users u ON c.patient_id = u.id "
+
"WHERE c.deleted_at IS NULL"
args
:=
[]
interface
{}{}
if
roleField
!=
""
{
query
+=
" AND c."
+
roleField
+
" = ?"
args
=
append
(
args
,
userID
)
}
if
status
!=
""
{
query
+=
" AND c.status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY c.created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"consultations"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"consultations"
:
results
,
"total"
:
len
(
results
),
"role"
:
userRole
,
},
nil
}
server/pkg/agent/tools/dashboard_stats.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DashboardStatsTool 查询管理端仪表盘统计数据
type
DashboardStatsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DashboardStatsTool
)
Name
()
string
{
return
"query_dashboard_stats"
}
func
(
t
*
DashboardStatsTool
)
Description
()
string
{
return
"查询管理端仪表盘统计:总用户数、总医生数、总问诊数、今日问诊、待审核医生、今日/本月收入,仅管理员可用"
}
func
(
t
*
DashboardStatsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{}
}
func
(
t
*
DashboardStatsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅管理员可查看仪表盘数据"
)
}
today
:=
time
.
Now
()
.
Format
(
"2006-01-02"
)
monthStart
:=
time
.
Now
()
.
Format
(
"2006-01"
)
+
"-01"
stats
:=
map
[
string
]
interface
{}{}
var
count
int64
// 总用户数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM users WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"total_users"
]
=
count
// 总医生数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM users WHERE role = 'doctor' AND deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"total_doctors"
]
=
count
// 总问诊数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM consultations WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"total_consultations"
]
=
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
// 待审核医生数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM doctor_reviews WHERE status = 'pending'"
)
.
Scan
(
&
count
)
stats
[
"pending_doctor_reviews"
]
=
count
// 今日收入(已支付订单)
var
todayRevenue
int64
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"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
// 本月收入
var
monthRevenue
int64
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"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
// 总处方数
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM prescriptions WHERE deleted_at IS NULL"
)
.
Scan
(
&
count
)
stats
[
"total_prescriptions"
]
=
count
return
stats
,
nil
}
server/pkg/agent/tools/dashboard_trend.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DashboardTrendTool 查询管理端运营趋势数据
type
DashboardTrendTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DashboardTrendTool
)
Name
()
string
{
return
"query_dashboard_trend"
}
func
(
t
*
DashboardTrendTool
)
Description
()
string
{
return
"查询最近7天运营趋势:每日问诊量、完成量、收入,仅管理员可用"
}
func
(
t
*
DashboardTrendTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"days"
,
Type
:
"number"
,
Description
:
"查询天数,默认7,最大30"
,
Required
:
false
},
}
}
func
(
t
*
DashboardTrendTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅管理员可查看运营趋势"
)
}
days
:=
7
if
v
,
ok
:=
params
[
"days"
]
.
(
float64
);
ok
&&
v
>
0
{
days
=
int
(
v
)
if
days
>
30
{
days
=
30
}
}
startDate
:=
time
.
Now
()
.
AddDate
(
0
,
0
,
-
(
days
-
1
))
.
Format
(
"2006-01-02"
)
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT
DATE(created_at)::text AS date,
COUNT(*) AS consult_count,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count
FROM consultations
WHERE DATE(created_at) >= ? AND deleted_at IS NULL
GROUP BY DATE(created_at)
ORDER BY date
`
,
startDate
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"trend"
:
[]
interface
{}{}},
nil
}
defer
rows
.
Close
()
var
results
[]
map
[
string
]
interface
{}
for
rows
.
Next
()
{
var
date
string
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
,
})
}
}
if
results
==
nil
{
results
=
[]
map
[
string
]
interface
{}{}
}
return
map
[
string
]
interface
{}{
"trend"
:
results
,
"days"
:
days
,
},
nil
}
server/pkg/agent/tools/department_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DepartmentListTool 查询科室列表
type
DepartmentListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DepartmentListTool
)
Name
()
string
{
return
"query_department_list"
}
func
(
t
*
DepartmentListTool
)
Description
()
string
{
return
"查询所有科室列表(含层级关系),为推荐科室后查询具体医生提供数据支持"
}
func
(
t
*
DepartmentListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{}
}
func
(
t
*
DepartmentListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, name, icon, parent_id, sort_order
FROM departments
WHERE deleted_at IS NULL
ORDER BY sort_order ASC, name ASC`
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"departments"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"departments"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/doctor_detail.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorDetailTool 查询医生详情+排班
type
DoctorDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DoctorDetailTool
)
Name
()
string
{
return
"query_doctor_detail"
}
func
(
t
*
DoctorDetailTool
)
Description
()
string
{
return
"查询医生详情信息和近7天排班,包括简介、专长、评分、价格、可预约时段"
}
func
(
t
*
DoctorDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"doctor_id"
,
Type
:
"string"
,
Description
:
"医生ID"
,
Required
:
true
},
}
}
func
(
t
*
DoctorDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
doctorID
,
ok
:=
params
[
"doctor_id"
]
.
(
string
)
if
!
ok
||
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"doctor_id 必填"
)
}
// 查询医生详情
detailRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT d.id, d.name, d.title, d.hospital, d.specialties, d.introduction,
d.rating, d.consult_count, d.price, d.is_online, d.avatar,
dep.name as department_name
FROM doctors d
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE d.id = ? AND d.deleted_at IS NULL`
,
doctorID
)
.
Rows
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"医生不存在: %s"
,
doctorID
)
}
defer
detailRows
.
Close
()
cols
,
_
:=
detailRows
.
Columns
()
if
!
detailRows
.
Next
()
{
return
nil
,
fmt
.
Errorf
(
"医生不存在: %s"
,
doctorID
)
}
detail
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
detailRows
.
Scan
(
ptrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"医生不存在: %s"
,
doctorID
)
}
for
i
,
col
:=
range
cols
{
detail
[
col
]
=
vals
[
i
]
}
// 查询近7天排班
today
:=
time
.
Now
()
.
Format
(
"2006-01-02"
)
endDate
:=
time
.
Now
()
.
AddDate
(
0
,
0
,
7
)
.
Format
(
"2006-01-02"
)
var
schedules
[]
map
[
string
]
interface
{}
schedRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, date, start_time, end_time, max_count, remaining
FROM doctor_schedules
WHERE doctor_id = ? AND date >= ? AND date <= ?
ORDER BY date ASC, start_time ASC`
,
doctorID
,
today
,
endDate
)
.
Rows
()
if
err
==
nil
{
defer
schedRows
.
Close
()
sCols
,
_
:=
schedRows
.
Columns
()
for
schedRows
.
Next
()
{
sched
:=
make
(
map
[
string
]
interface
{})
sVals
:=
make
([]
interface
{},
len
(
sCols
))
sPtrs
:=
make
([]
interface
{},
len
(
sCols
))
for
i
:=
range
sVals
{
sPtrs
[
i
]
=
&
sVals
[
i
]
}
if
err
:=
schedRows
.
Scan
(
sPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
sCols
{
sched
[
col
]
=
sVals
[
i
]
}
schedules
=
append
(
schedules
,
sched
)
}
}
}
detail
[
"schedules"
]
=
schedules
return
detail
,
nil
}
server/pkg/agent/tools/doctor_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorListTool 搜索医生列表
type
DoctorListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DoctorListTool
)
Name
()
string
{
return
"query_doctor_list"
}
func
(
t
*
DoctorListTool
)
Description
()
string
{
return
"搜索医生列表,支持按科室、关键词筛选,按评分/问诊量/价格排序,返回医生基本信息和在线状态"
}
func
(
t
*
DoctorListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"department_id"
,
Type
:
"string"
,
Description
:
"科室ID过滤(可选)"
,
Required
:
false
},
{
Name
:
"keyword"
,
Type
:
"string"
,
Description
:
"医生姓名关键词(可选)"
,
Required
:
false
},
{
Name
:
"sort_by"
,
Type
:
"string"
,
Description
:
"排序方式,默认按评分"
,
Required
:
false
,
Enum
:
[]
string
{
"rating"
,
"consult_count"
,
"price"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10"
,
Required
:
false
},
}
}
func
(
t
*
DoctorListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"patient"
&&
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅患者和管理员可搜索医生"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
30
{
limit
=
30
}
}
departmentID
,
_
:=
params
[
"department_id"
]
.
(
string
)
keyword
,
_
:=
params
[
"keyword"
]
.
(
string
)
sortBy
,
_
:=
params
[
"sort_by"
]
.
(
string
)
query
:=
"SELECT d.id, d.name, d.title, d.hospital, d.specialties, d.rating, "
+
"d.consult_count, d.price, d.is_online, d.avatar, "
+
"dep.name as department_name "
+
"FROM doctors d "
+
"LEFT JOIN departments dep ON d.department_id = dep.id "
+
"WHERE d.status = 'approved' AND d.deleted_at IS NULL"
args
:=
[]
interface
{}{}
if
departmentID
!=
""
{
query
+=
" AND d.department_id = ?"
args
=
append
(
args
,
departmentID
)
}
if
keyword
!=
""
{
query
+=
" AND d.name ILIKE ?"
args
=
append
(
args
,
"%"
+
keyword
+
"%"
)
}
switch
sortBy
{
case
"consult_count"
:
query
+=
" ORDER BY d.consult_count DESC"
case
"price"
:
query
+=
" ORDER BY d.price ASC"
default
:
query
+=
" ORDER BY d.rating DESC"
}
query
+=
" LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"doctors"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"doctors"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/doctor_schedule.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorScheduleTool 查询医生排班
type
DoctorScheduleTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
DoctorScheduleTool
)
Name
()
string
{
return
"query_doctor_schedule"
}
func
(
t
*
DoctorScheduleTool
)
Description
()
string
{
return
"查询医生排班信息,患者用于预约,医生用于查看自己的排班"
}
func
(
t
*
DoctorScheduleTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"doctor_id"
,
Type
:
"string"
,
Description
:
"医生ID(患者必填,医生可不填查自己的)"
,
Required
:
false
},
{
Name
:
"days"
,
Type
:
"number"
,
Description
:
"查询未来天数,默认7天"
,
Required
:
false
},
}
}
func
(
t
*
DoctorScheduleTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
doctorID
,
_
:=
params
[
"doctor_id"
]
.
(
string
)
// 医生查自己排班时无需传 doctor_id
if
doctorID
==
""
&&
userRole
==
"doctor"
{
var
id
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
id
)
doctorID
=
id
}
if
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"doctor_id 必填"
)
}
days
:=
7
if
v
,
ok
:=
params
[
"days"
]
.
(
float64
);
ok
&&
v
>
0
{
days
=
int
(
v
)
if
days
>
30
{
days
=
30
}
}
today
:=
time
.
Now
()
.
Format
(
"2006-01-02"
)
endDate
:=
time
.
Now
()
.
AddDate
(
0
,
0
,
days
)
.
Format
(
"2006-01-02"
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, date, start_time, end_time, max_count, remaining
FROM doctor_schedules
WHERE doctor_id = ? AND date >= ? AND date <= ?
ORDER BY date ASC, start_time ASC`
,
doctorID
,
today
,
endDate
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"schedules"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"schedules"
:
results
,
"total"
:
len
(
results
),
"doctor_id"
:
doctorID
,
},
nil
}
server/pkg/agent/tools/doctor_schedule_create.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateScheduleFn 创建排班回调,由 main.go 注入
// 参数: ctx, doctorUserID, date, startTime, endTime, maxCount
var
CreateScheduleFn
func
(
ctx
context
.
Context
,
doctorUserID
,
date
,
startTime
,
endTime
string
,
maxCount
int
)
error
// CreateDoctorScheduleTool 医生创建排班
type
CreateDoctorScheduleTool
struct
{}
func
(
t
*
CreateDoctorScheduleTool
)
Name
()
string
{
return
"create_doctor_schedule"
}
func
(
t
*
CreateDoctorScheduleTool
)
Description
()
string
{
return
"医生创建排班时段,设置日期、时间段和最大接诊人数"
}
func
(
t
*
CreateDoctorScheduleTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"date"
,
Type
:
"string"
,
Description
:
"排班日期(格式:2006-01-02)"
,
Required
:
true
},
{
Name
:
"start_time"
,
Type
:
"string"
,
Description
:
"开始时间(格式:09:00)"
,
Required
:
true
},
{
Name
:
"end_time"
,
Type
:
"string"
,
Description
:
"结束时间(格式:12:00)"
,
Required
:
true
},
{
Name
:
"max_count"
,
Type
:
"number"
,
Description
:
"最大接诊人数"
,
Required
:
true
},
}
}
func
(
t
*
CreateDoctorScheduleTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可创建排班"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
date
,
ok
:=
params
[
"date"
]
.
(
string
)
if
!
ok
||
date
==
""
{
return
nil
,
fmt
.
Errorf
(
"date 必填"
)
}
startTime
,
ok
:=
params
[
"start_time"
]
.
(
string
)
if
!
ok
||
startTime
==
""
{
return
nil
,
fmt
.
Errorf
(
"start_time 必填"
)
}
endTime
,
ok
:=
params
[
"end_time"
]
.
(
string
)
if
!
ok
||
endTime
==
""
{
return
nil
,
fmt
.
Errorf
(
"end_time 必填"
)
}
maxCount
:=
0
if
v
,
ok
:=
params
[
"max_count"
]
.
(
float64
);
ok
{
maxCount
=
int
(
v
)
}
if
maxCount
<=
0
{
return
nil
,
fmt
.
Errorf
(
"max_count 必须大于0"
)
}
if
CreateScheduleFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"排班服务未初始化"
)
}
if
err
:=
CreateScheduleFn
(
ctx
,
userID
,
date
,
startTime
,
endTime
,
maxCount
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"创建排班失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"date"
:
date
,
"start_time"
:
startTime
,
"end_time"
:
endTime
,
"max_count"
:
maxCount
,
"message"
:
"排班已创建"
,
},
nil
}
server/pkg/agent/tools/health_metric_record.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// RecordHealthMetricFn 记录健康指标回调,由 main.go 注入
var
RecordHealthMetricFn
func
(
ctx
context
.
Context
,
userID
,
metricType
string
,
value1
,
value2
float64
,
unit
,
notes
string
)
(
string
,
error
)
// RecordHealthMetricTool 记录健康指标
type
RecordHealthMetricTool
struct
{}
func
(
t
*
RecordHealthMetricTool
)
Name
()
string
{
return
"record_health_metric"
}
func
(
t
*
RecordHealthMetricTool
)
Description
()
string
{
return
"患者记录健康指标(血压、血糖、心率、体温),异常值会自动触发健康告警"
}
func
(
t
*
RecordHealthMetricTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"metric_type"
,
Type
:
"string"
,
Description
:
"指标类型"
,
Required
:
true
,
Enum
:
[]
string
{
"blood_pressure"
,
"blood_glucose"
,
"heart_rate"
,
"body_temperature"
}},
{
Name
:
"value1"
,
Type
:
"number"
,
Description
:
"主值(血压为收缩压,血糖/心率/体温为测量值)"
,
Required
:
true
},
{
Name
:
"value2"
,
Type
:
"number"
,
Description
:
"副值(血压为舒张压,其他类型可不填)"
,
Required
:
false
},
{
Name
:
"unit"
,
Type
:
"string"
,
Description
:
"单位(可选,如mmHg、mmol/L、bpm、℃)"
,
Required
:
false
},
{
Name
:
"notes"
,
Type
:
"string"
,
Description
:
"备注(可选,如饭前/饭后、运动后等)"
,
Required
:
false
},
}
}
func
(
t
*
RecordHealthMetricTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"patient"
{
return
nil
,
fmt
.
Errorf
(
"仅患者可记录健康指标"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
metricType
,
ok
:=
params
[
"metric_type"
]
.
(
string
)
if
!
ok
||
metricType
==
""
{
return
nil
,
fmt
.
Errorf
(
"metric_type 必填"
)
}
value1
,
ok
:=
params
[
"value1"
]
.
(
float64
)
if
!
ok
{
return
nil
,
fmt
.
Errorf
(
"value1 必填"
)
}
var
value2
float64
if
v
,
ok
:=
params
[
"value2"
]
.
(
float64
);
ok
{
value2
=
v
}
unit
,
_
:=
params
[
"unit"
]
.
(
string
)
notes
,
_
:=
params
[
"notes"
]
.
(
string
)
// 默认单位
if
unit
==
""
{
switch
metricType
{
case
"blood_pressure"
:
unit
=
"mmHg"
case
"blood_glucose"
:
unit
=
"mmol/L"
case
"heart_rate"
:
unit
=
"bpm"
case
"body_temperature"
:
unit
=
"℃"
}
}
if
RecordHealthMetricFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"健康指标服务未初始化"
)
}
metricID
,
err
:=
RecordHealthMetricFn
(
ctx
,
userID
,
metricType
,
value1
,
value2
,
unit
,
notes
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"记录健康指标失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"metric_id"
:
metricID
,
"metric_type"
:
metricType
,
"value1"
:
value1
,
"value2"
:
value2
,
"unit"
:
unit
,
"message"
:
"健康指标已记录"
,
},
nil
}
server/pkg/agent/tools/health_metrics.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// HealthMetricsTool 查询健康指标记录
type
HealthMetricsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
HealthMetricsTool
)
Name
()
string
{
return
"query_health_metrics"
}
func
(
t
*
HealthMetricsTool
)
Description
()
string
{
return
"查询患者健康指标记录(血压、血糖、心率、体温),支持类型过滤,按时间倒序返回"
}
func
(
t
*
HealthMetricsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生使用,患者无需传入)"
,
Required
:
false
},
{
Name
:
"metric_type"
,
Type
:
"string"
,
Description
:
"指标类型过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"blood_pressure"
,
"blood_glucose"
,
"heart_rate"
,
"body_temperature"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认20"
,
Required
:
false
},
}
}
func
(
t
*
HealthMetricsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的健康指标"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
20
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
100
{
limit
=
100
}
}
metricType
,
_
:=
params
[
"metric_type"
]
.
(
string
)
query
:=
"SELECT id, metric_type, value1, value2, unit, notes, recorded_at, created_at "
+
"FROM health_metrics WHERE user_id = ?"
args
:=
[]
interface
{}{
targetID
}
if
metricType
!=
""
{
query
+=
" AND metric_type = ?"
args
=
append
(
args
,
metricType
)
}
query
+=
" ORDER BY recorded_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"metrics"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"metrics"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/income_records.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// IncomeRecordsTool 查询医生收入明细
type
IncomeRecordsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
IncomeRecordsTool
)
Name
()
string
{
return
"query_income_records"
}
func
(
t
*
IncomeRecordsTool
)
Description
()
string
{
return
"查询医生收入明细记录,包含收入类型、金额、患者姓名、状态,支持日期过滤"
}
func
(
t
*
IncomeRecordsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"start_date"
,
Type
:
"string"
,
Description
:
"开始日期(可选,格式YYYY-MM-DD)"
,
Required
:
false
},
{
Name
:
"end_date"
,
Type
:
"string"
,
Description
:
"结束日期(可选,格式YYYY-MM-DD)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认20,最大100"
,
Required
:
false
},
}
}
func
(
t
*
IncomeRecordsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可查看收入明细"
)
}
// 查询医生ID
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"医生信息不存在"
)
}
limit
:=
20
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
100
{
limit
=
100
}
}
startDate
,
_
:=
params
[
"start_date"
]
.
(
string
)
endDate
,
_
:=
params
[
"end_date"
]
.
(
string
)
query
:=
"SELECT id, consult_id, income_type, amount, status, patient_name, settled_at, created_at "
+
"FROM doctor_incomes WHERE doctor_id = ?"
args
:=
[]
interface
{}{
doctorID
}
if
startDate
!=
""
{
query
+=
" AND created_at >= ?"
args
=
append
(
args
,
startDate
)
}
if
endDate
!=
""
{
query
+=
" AND created_at <= ?"
args
=
append
(
args
,
endDate
+
" 23:59:59"
)
}
query
+=
" ORDER BY created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"records"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"records"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/income_stats.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// IncomeStatsTool 查询医生收入统计
type
IncomeStatsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
IncomeStatsTool
)
Name
()
string
{
return
"query_income_stats"
}
func
(
t
*
IncomeStatsTool
)
Description
()
string
{
return
"查询医生收入统计:可提现余额、本月收入、本月问诊量,仅医生可用"
}
func
(
t
*
IncomeStatsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{}
}
func
(
t
*
IncomeStatsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可查看收入统计"
)
}
// 查询医生ID
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
doctorID
==
""
{
return
nil
,
fmt
.
Errorf
(
"医生信息不存在"
)
}
var
totalBalance
int64
var
monthIncome
int64
var
monthConsults
int64
// 可提现余额(已结算未提现)
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COALESCE(SUM(amount), 0) FROM doctor_incomes WHERE doctor_id = ? AND status = 'settled'"
,
doctorID
,
)
.
Scan
(
&
totalBalance
)
// 本月收入
startOfMonth
:=
time
.
Now
()
.
Format
(
"2006-01"
)
+
"-01"
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COALESCE(SUM(amount), 0) FROM doctor_incomes WHERE doctor_id = ? AND created_at >= ?"
,
doctorID
,
startOfMonth
,
)
.
Scan
(
&
monthIncome
)
// 本月问诊量
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM doctor_incomes WHERE doctor_id = ? AND created_at >= ?"
,
doctorID
,
startOfMonth
,
)
.
Scan
(
&
monthConsults
)
return
map
[
string
]
interface
{}{
"total_balance"
:
totalBalance
,
"month_income"
:
monthIncome
,
"month_consults"
:
monthConsults
,
},
nil
}
server/pkg/agent/tools/lab_reports.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// LabReportsTool 查询检验报告
type
LabReportsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
LabReportsTool
)
Name
()
string
{
return
"query_lab_reports"
}
func
(
t
*
LabReportsTool
)
Description
()
string
{
return
"查询患者的检验报告列表,包括AI解读结果"
}
func
(
t
*
LabReportsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生使用,患者无需传入)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10"
,
Required
:
false
},
}
}
func
(
t
*
LabReportsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的检验报告"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
30
{
limit
=
30
}
}
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, title, report_date, file_url, file_type, category,
ai_interpret, created_at
FROM lab_reports
WHERE user_id = ? AND deleted_at IS NULL
ORDER BY report_date DESC, created_at DESC LIMIT ?`
,
targetID
,
limit
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"reports"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"reports"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/medicine_catalog.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// MedicineCatalogTool 搜索药品目录
type
MedicineCatalogTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
MedicineCatalogTool
)
Name
()
string
{
return
"search_medicine_catalog"
}
func
(
t
*
MedicineCatalogTool
)
Description
()
string
{
return
"搜索药品目录,查询药品名称、规格、库存、价格等信息,为开方提供数据支持"
}
func
(
t
*
MedicineCatalogTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"keyword"
,
Type
:
"string"
,
Description
:
"药品名称或关键词"
,
Required
:
true
},
{
Name
:
"category"
,
Type
:
"string"
,
Description
:
"药品分类过滤(可选)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10"
,
Required
:
false
},
}
}
func
(
t
*
MedicineCatalogTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"doctor"
&&
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅医生和管理员可搜索药品目录"
)
}
keyword
,
ok
:=
params
[
"keyword"
]
.
(
string
)
if
!
ok
||
keyword
==
""
{
return
nil
,
fmt
.
Errorf
(
"keyword 必填"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
30
{
limit
=
30
}
}
category
,
_
:=
params
[
"category"
]
.
(
string
)
query
:=
"SELECT id, name, generic_name, specification, manufacturer, "
+
"unit, price, stock, category, status, usage_method, contraindication "
+
"FROM medicines "
+
"WHERE (name ILIKE ? OR generic_name ILIKE ?) AND status = 'active'"
searchPattern
:=
"%"
+
keyword
+
"%"
args
:=
[]
interface
{}{
searchPattern
,
searchPattern
}
if
category
!=
""
{
query
+=
" AND category = ?"
args
=
append
(
args
,
category
)
}
query
+=
" ORDER BY name ASC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"medicines"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"medicines"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/order_detail.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// OrderDetailTool 查询支付订单详情
type
OrderDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
OrderDetailTool
)
Name
()
string
{
return
"query_order_detail"
}
func
(
t
*
OrderDetailTool
)
Description
()
string
{
return
"查询支付订单详情,包含订单号、金额、状态、支付方式、关联业务等完整信息"
}
func
(
t
*
OrderDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"order_id"
,
Type
:
"string"
,
Description
:
"订单ID"
,
Required
:
true
},
}
}
func
(
t
*
OrderDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
orderID
,
_
:=
params
[
"order_id"
]
.
(
string
)
if
orderID
==
""
{
return
nil
,
fmt
.
Errorf
(
"请提供订单ID"
)
}
query
:=
"SELECT id, order_no, user_id, order_type, related_id, amount, status, "
+
"payment_method, transaction_id, paid_at, refunded_at, expire_at, remark, created_at "
+
"FROM payment_orders WHERE id = ? AND deleted_at IS NULL"
args
:=
[]
interface
{}{
orderID
}
// 患者只能查自己的订单
if
userRole
==
"patient"
{
query
+=
" AND user_id = ?"
args
=
append
(
args
,
userID
)
}
var
result
map
[
string
]
interface
{}
row
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
rows
,
err
:=
row
.
Rows
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"查询订单失败"
)
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
if
rows
.
Next
()
{
result
=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
result
[
col
]
=
vals
[
i
]
}
}
}
if
result
==
nil
{
return
nil
,
fmt
.
Errorf
(
"订单不存在"
)
}
return
result
,
nil
}
server/pkg/agent/tools/order_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// OrderListTool 查询支付订单列表
type
OrderListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
OrderListTool
)
Name
()
string
{
return
"query_order_list"
}
func
(
t
*
OrderListTool
)
Description
()
string
{
return
"查询支付订单列表:患者查自己的订单,管理员查所有订单,支持按状态和类型过滤"
}
func
(
t
*
OrderListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"订单状态过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"pending"
,
"paid"
,
"refunded"
,
"cancelled"
,
"completed"
}},
{
Name
:
"order_type"
,
Type
:
"string"
,
Description
:
"订单类型过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"consult"
,
"prescription"
,
"pharmacy"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
OrderListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
orderType
,
_
:=
params
[
"order_type"
]
.
(
string
)
query
:=
"SELECT id, order_no, order_type, related_id, amount, status, payment_method, paid_at, created_at "
+
"FROM payment_orders WHERE deleted_at IS NULL"
args
:=
[]
interface
{}{}
switch
userRole
{
case
"patient"
:
query
+=
" AND user_id = ?"
args
=
append
(
args
,
userID
)
case
"admin"
:
// 管理员查所有
default
:
return
nil
,
fmt
.
Errorf
(
"无权限查询订单"
)
}
if
status
!=
""
{
query
+=
" AND status = ?"
args
=
append
(
args
,
status
)
}
if
orderType
!=
""
{
query
+=
" AND order_type = ?"
args
=
append
(
args
,
orderType
)
}
query
+=
" ORDER BY created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"orders"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"orders"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/patient_profile.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PatientProfileTool 查询患者健康档案
type
PatientProfileTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PatientProfileTool
)
Name
()
string
{
return
"query_patient_profile"
}
func
(
t
*
PatientProfileTool
)
Description
()
string
{
return
"查询患者健康档案(基本信息、过敏史、病史、保险信息),患者查自己的,医生按patient_id查"
}
func
(
t
*
PatientProfileTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"patient_id"
,
Type
:
"string"
,
Description
:
"患者ID(医生/管理员使用,患者无需传入自动查自己的)"
,
Required
:
false
},
}
}
func
(
t
*
PatientProfileTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
targetID
:=
userID
if
pid
,
ok
:=
params
[
"patient_id"
]
.
(
string
);
ok
&&
pid
!=
""
{
if
userRole
==
"patient"
&&
pid
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"患者只能查看自己的健康档案"
)
}
targetID
=
pid
}
if
targetID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
// 查询用户基本信息
userInfo
:=
make
(
map
[
string
]
interface
{})
uRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT id, real_name, phone, gender, age, avatar, role, status, created_at
FROM users WHERE id = ?`
,
targetID
)
.
Rows
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"用户不存在"
)
}
defer
uRows
.
Close
()
if
uRows
.
Next
()
{
uCols
,
_
:=
uRows
.
Columns
()
uVals
:=
make
([]
interface
{},
len
(
uCols
))
uPtrs
:=
make
([]
interface
{},
len
(
uCols
))
for
i
:=
range
uVals
{
uPtrs
[
i
]
=
&
uVals
[
i
]
}
if
err
:=
uRows
.
Scan
(
uPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
uCols
{
userInfo
[
col
]
=
uVals
[
i
]
}
}
}
else
{
return
nil
,
fmt
.
Errorf
(
"用户不存在"
)
}
// 查询健康档案
profile
:=
make
(
map
[
string
]
interface
{})
pRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT gender, birth_date, medical_history, allergy_history,
emergency_contact, insurance_type, insurance_no
FROM patient_profiles WHERE user_id = ?`
,
targetID
)
.
Rows
()
if
err
==
nil
{
defer
pRows
.
Close
()
if
pRows
.
Next
()
{
pCols
,
_
:=
pRows
.
Columns
()
pVals
:=
make
([]
interface
{},
len
(
pCols
))
pPtrs
:=
make
([]
interface
{},
len
(
pCols
))
for
i
:=
range
pVals
{
pPtrs
[
i
]
=
&
pVals
[
i
]
}
if
err
:=
pRows
.
Scan
(
pPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
pCols
{
profile
[
col
]
=
pVals
[
i
]
}
}
}
}
// 查询最近问诊数
var
consultCount
int64
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT COUNT(*) FROM consultations WHERE patient_id = ? AND deleted_at IS NULL"
,
targetID
)
.
Scan
(
&
consultCount
)
return
map
[
string
]
interface
{}{
"user"
:
userInfo
,
"health_profile"
:
profile
,
"consult_count"
:
consultCount
,
},
nil
}
server/pkg/agent/tools/prescription_detail.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionDetailTool 查询处方详情
type
PrescriptionDetailTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PrescriptionDetailTool
)
Name
()
string
{
return
"query_prescription_detail"
}
func
(
t
*
PrescriptionDetailTool
)
Description
()
string
{
return
"查询处方详情,包括药品明细、用法用量、状态和费用,支持处方ID或处方编号(RX开头)查询"
}
func
(
t
*
PrescriptionDetailTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"prescription_id"
,
Type
:
"string"
,
Description
:
"处方ID(UUID)或处方编号(RX开头)"
,
Required
:
true
},
}
}
func
(
t
*
PrescriptionDetailTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
prescriptionID
,
ok
:=
params
[
"prescription_id"
]
.
(
string
)
if
!
ok
||
prescriptionID
==
""
{
return
nil
,
fmt
.
Errorf
(
"prescription_id 必填"
)
}
// 支持处方编号查询
resolvedID
:=
prescriptionID
if
len
(
prescriptionID
)
>=
2
&&
prescriptionID
[
:
2
]
==
"RX"
{
var
id
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM prescriptions WHERE prescription_no = ?"
,
prescriptionID
)
.
Scan
(
&
id
)
.
Error
;
err
!=
nil
||
id
==
""
{
return
nil
,
fmt
.
Errorf
(
"处方编号 %s 不存在"
,
prescriptionID
)
}
resolvedID
=
id
}
// 查询处方主记录
detailRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT p.id, p.prescription_no, p.consult_id, p.patient_id, p.patient_name,
p.patient_gender, p.patient_age, p.diagnosis, p.allergy_history,
p.remark, p.total_amount, p.status, p.created_at,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name
FROM prescriptions p
LEFT JOIN doctors d ON p.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE p.id = ? AND p.deleted_at IS NULL`
,
resolvedID
)
.
Rows
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"处方不存在: %s"
,
prescriptionID
)
}
defer
detailRows
.
Close
()
cols
,
_
:=
detailRows
.
Columns
()
if
!
detailRows
.
Next
()
{
return
nil
,
fmt
.
Errorf
(
"处方不存在: %s"
,
prescriptionID
)
}
detail
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
detailRows
.
Scan
(
ptrs
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"处方不存在: %s"
,
prescriptionID
)
}
for
i
,
col
:=
range
cols
{
detail
[
col
]
=
vals
[
i
]
}
// 权限校验
if
userRole
==
"patient"
&&
detail
[
"patient_id"
]
!=
userID
{
return
nil
,
fmt
.
Errorf
(
"无权查看该处方"
)
}
// 查询药品明细
var
items
[]
map
[
string
]
interface
{}
itemRows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT medicine_name, specification, usage, dosage, frequency,
days, quantity, unit, price, note
FROM prescription_items
WHERE prescription_id = ?
ORDER BY created_at ASC`
,
resolvedID
)
.
Rows
()
if
err
==
nil
{
defer
itemRows
.
Close
()
itemCols
,
_
:=
itemRows
.
Columns
()
for
itemRows
.
Next
()
{
item
:=
make
(
map
[
string
]
interface
{})
iVals
:=
make
([]
interface
{},
len
(
itemCols
))
iPtrs
:=
make
([]
interface
{},
len
(
itemCols
))
for
i
:=
range
iVals
{
iPtrs
[
i
]
=
&
iVals
[
i
]
}
if
err
:=
itemRows
.
Scan
(
iPtrs
...
);
err
==
nil
{
for
i
,
col
:=
range
itemCols
{
item
[
col
]
=
iVals
[
i
]
}
items
=
append
(
items
,
item
)
}
}
}
detail
[
"items"
]
=
items
detail
[
"drug_count"
]
=
len
(
items
)
return
detail
,
nil
}
server/pkg/agent/tools/prescription_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionListTool 查询处方列表
type
PrescriptionListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
PrescriptionListTool
)
Name
()
string
{
return
"query_prescription_list"
}
func
(
t
*
PrescriptionListTool
)
Description
()
string
{
return
"查询处方列表:患者查自己的处方,医生查自己开的处方,支持按状态过滤"
}
func
(
t
*
PrescriptionListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"处方状态过滤(可选):pending/signed/dispensed/completed/cancelled"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
PrescriptionListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
query
:=
"SELECT p.id, p.prescription_no, p.patient_name, p.diagnosis, "
+
"p.total_amount, p.status, p.created_at, "
+
"d.name as doctor_name, dep.name as department_name, "
+
"(SELECT COUNT(*) FROM prescription_items pi WHERE pi.prescription_id = p.id) as drug_count "
+
"FROM prescriptions p "
+
"LEFT JOIN doctors d ON p.doctor_id = d.id "
+
"LEFT JOIN departments dep ON d.department_id = dep.id "
+
"WHERE p.deleted_at IS NULL"
args
:=
[]
interface
{}{}
switch
userRole
{
case
"patient"
:
query
+=
" AND p.patient_id = ?"
args
=
append
(
args
,
userID
)
case
"doctor"
:
var
doctorID
string
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
if
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"prescriptions"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
query
+=
" AND p.doctor_id = ?"
args
=
append
(
args
,
doctorID
)
case
"admin"
:
// 管理员查所有
default
:
return
nil
,
fmt
.
Errorf
(
"无权限查询处方"
)
}
if
status
!=
""
{
query
+=
" AND p.status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY p.created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"prescriptions"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"prescriptions"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/renewal_create.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateRenewalFn 创建续方申请回调,由 main.go 注入
var
CreateRenewalFn
func
(
ctx
context
.
Context
,
userID
,
chronicID
,
diseaseName
,
reason
string
,
medicines
[]
string
)
(
string
,
error
)
// CreateRenewalTool 患者创建续方申请
type
CreateRenewalTool
struct
{}
func
(
t
*
CreateRenewalTool
)
Name
()
string
{
return
"create_renewal_request"
}
func
(
t
*
CreateRenewalTool
)
Description
()
string
{
return
"患者发起续方申请,需要指定疾病名称和续方药品列表,提交后等待医生审批"
}
func
(
t
*
CreateRenewalTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"disease_name"
,
Type
:
"string"
,
Description
:
"疾病名称"
,
Required
:
true
},
{
Name
:
"medicines"
,
Type
:
"array"
,
Description
:
"续方药品名称列表"
,
Required
:
true
},
{
Name
:
"chronic_id"
,
Type
:
"string"
,
Description
:
"关联的慢病记录ID(可选)"
,
Required
:
false
},
{
Name
:
"reason"
,
Type
:
"string"
,
Description
:
"续方原因(可选)"
,
Required
:
false
},
}
}
func
(
t
*
CreateRenewalTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"patient"
{
return
nil
,
fmt
.
Errorf
(
"仅患者可发起续方申请"
)
}
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
diseaseName
,
ok
:=
params
[
"disease_name"
]
.
(
string
)
if
!
ok
||
diseaseName
==
""
{
return
nil
,
fmt
.
Errorf
(
"disease_name 必填"
)
}
// 解析药品列表
var
medicines
[]
string
if
arr
,
ok
:=
params
[
"medicines"
]
.
([]
interface
{});
ok
{
for
_
,
v
:=
range
arr
{
if
s
,
ok
:=
v
.
(
string
);
ok
{
medicines
=
append
(
medicines
,
s
)
}
}
}
if
len
(
medicines
)
==
0
{
return
nil
,
fmt
.
Errorf
(
"medicines 必填且至少包含一种药品"
)
}
chronicID
,
_
:=
params
[
"chronic_id"
]
.
(
string
)
reason
,
_
:=
params
[
"reason"
]
.
(
string
)
if
CreateRenewalFn
==
nil
{
return
nil
,
fmt
.
Errorf
(
"续方服务未初始化"
)
}
renewalID
,
err
:=
CreateRenewalFn
(
ctx
,
userID
,
chronicID
,
diseaseName
,
reason
,
medicines
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"创建续方申请失败: %v"
,
err
)
}
return
map
[
string
]
interface
{}{
"renewal_id"
:
renewalID
,
"disease_name"
:
diseaseName
,
"status"
:
"pending"
,
"message"
:
"续方申请已提交,等待医生审批"
,
},
nil
}
server/pkg/agent/tools/renewal_requests.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// RenewalRequestsTool 查询续方申请列表
type
RenewalRequestsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
RenewalRequestsTool
)
Name
()
string
{
return
"query_renewal_requests"
}
func
(
t
*
RenewalRequestsTool
)
Description
()
string
{
return
"查询续方申请列表:患者查自己的续方申请(含状态和AI建议),医生查待审批的续方申请"
}
func
(
t
*
RenewalRequestsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"状态过滤(可选):pending/approved/rejected"
,
Required
:
false
,
Enum
:
[]
string
{
"pending"
,
"approved"
,
"rejected"
}},
}
}
func
(
t
*
RenewalRequestsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userID
==
""
{
return
nil
,
fmt
.
Errorf
(
"未获取到用户信息"
)
}
status
,
_
:=
params
[
"status"
]
.
(
string
)
query
:=
"SELECT r.id, r.disease_name, r.medicines, r.reason, r.status, "
+
"r.doctor_note, r.ai_advice, r.created_at, "
+
"u.real_name as patient_name "
+
"FROM renewal_requests r "
+
"LEFT JOIN users u ON r.user_id = u.id "
+
"WHERE r.deleted_at IS NULL"
args
:=
[]
interface
{}{}
switch
userRole
{
case
"patient"
:
query
+=
" AND r.user_id = ?"
args
=
append
(
args
,
userID
)
case
"doctor"
:
// 医生查所有待审批的
if
status
==
""
{
status
=
"pending"
}
case
"admin"
:
// 管理员查所有
default
:
return
nil
,
fmt
.
Errorf
(
"无权限查询续方申请"
)
}
if
status
!=
""
{
query
+=
" AND r.status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY r.created_at DESC LIMIT 30"
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"renewals"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"renewals"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/system_logs.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// SystemLogsTool 查询系统操作日志(管理员专用)
type
SystemLogsTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
SystemLogsTool
)
Name
()
string
{
return
"query_system_logs"
}
func
(
t
*
SystemLogsTool
)
Description
()
string
{
return
"查询系统操作日志,支持按操作类型、资源过滤,仅管理员可用"
}
func
(
t
*
SystemLogsTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"action"
,
Type
:
"string"
,
Description
:
"操作类型过滤(可选,如login/create/update/delete)"
,
Required
:
false
},
{
Name
:
"resource"
,
Type
:
"string"
,
Description
:
"资源类型过滤(可选,如user/doctor/prescription)"
,
Required
:
false
},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认20,最大100"
,
Required
:
false
},
}
}
func
(
t
*
SystemLogsTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅管理员可查看系统日志"
)
}
limit
:=
20
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
100
{
limit
=
100
}
}
action
,
_
:=
params
[
"action"
]
.
(
string
)
resource
,
_
:=
params
[
"resource"
]
.
(
string
)
query
:=
"SELECT sl.id, sl.user_id, sl.action, sl.resource, sl.detail, sl.ip, sl.created_at, "
+
"u.real_name as user_name "
+
"FROM system_logs sl LEFT JOIN users u ON sl.user_id = u.id WHERE 1=1"
args
:=
[]
interface
{}{}
if
action
!=
""
{
query
+=
" AND sl.action = ?"
args
=
append
(
args
,
action
)
}
if
resource
!=
""
{
query
+=
" AND sl.resource LIKE ?"
args
=
append
(
args
,
"%"
+
resource
+
"%"
)
}
query
+=
" ORDER BY sl.created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"logs"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"logs"
:
results
,
"total"
:
len
(
results
),
},
nil
}
server/pkg/agent/tools/user_list.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// UserListTool 查询用户列表(管理员专用)
type
UserListTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
UserListTool
)
Name
()
string
{
return
"query_user_list"
}
func
(
t
*
UserListTool
)
Description
()
string
{
return
"查询系统用户列表,支持按角色、关键词搜索,仅管理员可用"
}
func
(
t
*
UserListTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{
{
Name
:
"role"
,
Type
:
"string"
,
Description
:
"角色过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"patient"
,
"doctor"
,
"admin"
}},
{
Name
:
"keyword"
,
Type
:
"string"
,
Description
:
"搜索关键词(姓名/手机号)"
,
Required
:
false
},
{
Name
:
"status"
,
Type
:
"string"
,
Description
:
"状态过滤(可选)"
,
Required
:
false
,
Enum
:
[]
string
{
"active"
,
"disabled"
}},
{
Name
:
"limit"
,
Type
:
"number"
,
Description
:
"返回数量,默认10,最大50"
,
Required
:
false
},
}
}
func
(
t
*
UserListTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
if
userRole
!=
"admin"
{
return
nil
,
fmt
.
Errorf
(
"仅管理员可查看用户列表"
)
}
limit
:=
10
if
v
,
ok
:=
params
[
"limit"
]
.
(
float64
);
ok
&&
v
>
0
{
limit
=
int
(
v
)
if
limit
>
50
{
limit
=
50
}
}
role
,
_
:=
params
[
"role"
]
.
(
string
)
keyword
,
_
:=
params
[
"keyword"
]
.
(
string
)
status
,
_
:=
params
[
"status"
]
.
(
string
)
query
:=
"SELECT id, phone, real_name, role, status, is_verified, created_at "
+
"FROM users WHERE deleted_at IS NULL"
args
:=
[]
interface
{}{}
if
role
!=
""
{
query
+=
" AND role = ?"
args
=
append
(
args
,
role
)
}
if
keyword
!=
""
{
query
+=
" AND (real_name LIKE ? OR phone LIKE ?)"
args
=
append
(
args
,
"%"
+
keyword
+
"%"
,
"%"
+
keyword
+
"%"
)
}
if
status
!=
""
{
query
+=
" AND status = ?"
args
=
append
(
args
,
status
)
}
query
+=
" ORDER BY created_at DESC LIMIT ?"
args
=
append
(
args
,
limit
)
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
query
,
args
...
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"users"
:
[]
interface
{}{},
"total"
:
0
},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
// 获取总数
var
total
int64
countQuery
:=
"SELECT COUNT(*) FROM users WHERE deleted_at IS NULL"
countArgs
:=
[]
interface
{}{}
if
role
!=
""
{
countQuery
+=
" AND role = ?"
countArgs
=
append
(
countArgs
,
role
)
}
if
keyword
!=
""
{
countQuery
+=
" AND (real_name LIKE ? OR phone LIKE ?)"
countArgs
=
append
(
countArgs
,
"%"
+
keyword
+
"%"
,
"%"
+
keyword
+
"%"
)
}
if
status
!=
""
{
countQuery
+=
" AND status = ?"
countArgs
=
append
(
countArgs
,
status
)
}
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
countQuery
,
countArgs
...
)
.
Scan
(
&
total
)
return
map
[
string
]
interface
{}{
"users"
:
results
,
"total"
:
total
,
},
nil
}
server/pkg/agent/tools/waiting_queue.go
0 → 100644
View file @
da795257
package
tools
import
(
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// WaitingQueueTool 查询医生的等候队列
type
WaitingQueueTool
struct
{
DB
*
gorm
.
DB
}
func
(
t
*
WaitingQueueTool
)
Name
()
string
{
return
"query_waiting_queue"
}
func
(
t
*
WaitingQueueTool
)
Description
()
string
{
return
"查询当前医生的等候队列,显示等候中的患者数量和列表"
}
func
(
t
*
WaitingQueueTool
)
Parameters
()
[]
agent
.
ToolParameter
{
return
[]
agent
.
ToolParameter
{}
}
func
(
t
*
WaitingQueueTool
)
Execute
(
ctx
context
.
Context
,
params
map
[
string
]
interface
{})
(
interface
{},
error
)
{
userRole
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserRole
)
.
(
string
)
userID
,
_
:=
ctx
.
Value
(
agent
.
ContextKeyUserID
)
.
(
string
)
if
userRole
!=
"doctor"
{
return
nil
,
fmt
.
Errorf
(
"仅医生可查看等候队列"
)
}
var
doctorID
string
if
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
"SELECT id FROM doctors WHERE user_id = ?"
,
userID
)
.
Scan
(
&
doctorID
)
.
Error
;
err
!=
nil
||
doctorID
==
""
{
return
map
[
string
]
interface
{}{
"waiting_count"
:
0
,
"patients"
:
[]
interface
{}{}},
nil
}
var
results
[]
map
[
string
]
interface
{}
rows
,
err
:=
t
.
DB
.
WithContext
(
ctx
)
.
Raw
(
`
SELECT c.id, c.serial_number, c.chief_complaint, c.type, c.created_at,
u.real_name as patient_name,
EXTRACT(EPOCH FROM (NOW() - c.created_at))::int as wait_seconds
FROM consultations c
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.doctor_id = ? AND c.status = 'pending' AND c.deleted_at IS NULL
ORDER BY c.created_at ASC`
,
doctorID
)
.
Rows
()
if
err
!=
nil
{
return
map
[
string
]
interface
{}{
"waiting_count"
:
0
,
"patients"
:
[]
interface
{}{}},
nil
}
defer
rows
.
Close
()
cols
,
_
:=
rows
.
Columns
()
for
rows
.
Next
()
{
row
:=
make
(
map
[
string
]
interface
{})
vals
:=
make
([]
interface
{},
len
(
cols
))
ptrs
:=
make
([]
interface
{},
len
(
cols
))
for
i
:=
range
vals
{
ptrs
[
i
]
=
&
vals
[
i
]
}
if
err
:=
rows
.
Scan
(
ptrs
...
);
err
==
nil
{
for
i
,
col
:=
range
cols
{
row
[
col
]
=
vals
[
i
]
}
results
=
append
(
results
,
row
)
}
}
return
map
[
string
]
interface
{}{
"waiting_count"
:
len
(
results
),
"patients"
:
results
,
},
nil
}
server/pkg/rag/embedder.go
View file @
da795257
...
...
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
)
...
...
@@ -148,17 +149,27 @@ func (e *Embedder) EmbedSingle(ctx context.Context, text string) ([]float32, err
}
// mockEmbed 模拟向量生成(用于测试或API未配置时)
// 使用 bag-of-words 方式:预定义关键词词表,文本中出现的关键词对应维度置1,然后归一化
// 这样语义相似的文本(共享关键词)会产生较高的余弦相似度
func
(
e
*
Embedder
)
mockEmbed
(
texts
[]
string
)
[][]
float32
{
// 生成简单的模拟向量(基于文本哈希)
dimension
:=
1024
embeddings
:=
make
([][]
float32
,
len
(
texts
))
for
i
,
text
:=
range
texts
{
vec
:=
make
([]
float32
,
dimension
)
// 遍历预定义关键词,命中则在对应维度置1
for
j
,
kw
:=
range
mockVocab
{
if
j
>=
dimension
{
break
}
if
containsKeyword
(
text
,
kw
)
{
vec
[
j
]
=
1.0
}
}
// 用文本哈希填充剩余维度(提供区分度)
hash
:=
simpleHash
(
text
)
for
j
:=
0
;
j
<
dimension
;
j
++
{
// 使用哈希值生成伪随机向量
vec
[
j
]
=
float32
((
hash
>>
(
j
%
32
))
&
1
)
*
2
-
1
for
j
:=
len
(
mockVocab
);
j
<
dimension
;
j
++
{
vec
[
j
]
=
float32
((
hash
>>
(
uint
(
j
)
%
32
))
&
1
)
*
0.1
hash
=
hash
*
1103515245
+
12345
}
// 归一化
...
...
@@ -178,6 +189,61 @@ func (e *Embedder) mockEmbed(texts []string) [][]float32 {
return
embeddings
}
// containsKeyword 检查文本是否包含关键词(不区分大小写)
func
containsKeyword
(
text
,
keyword
string
)
bool
{
tl
:=
strings
.
ToLower
(
text
)
kl
:=
strings
.
ToLower
(
keyword
)
return
strings
.
Contains
(
tl
,
kl
)
}
// mockVocab 预定义关键词词表(医疗+业务领域),每个关键词对应向量的一个维度
// 共享关键词的文本会在相同维度上有值,从而产生较高的余弦相似度
var
mockVocab
=
[]
string
{
// 医疗基础
"药品"
,
"药物"
,
"用药"
,
"处方"
,
"开方"
,
"症状"
,
"病症"
,
"诊断"
,
"科室"
,
"部门"
,
"推荐"
,
"知识"
,
"知识库"
,
"检索"
,
"搜索"
,
"病历"
,
"病史"
,
"记录"
,
"检查"
,
"检验"
,
"报告"
,
"解读"
,
"安全"
,
"禁忌"
,
"相互作用"
,
"剂量"
,
"用量"
,
"计算"
,
// 问诊
"问诊"
,
"就诊"
,
"接诊"
,
"挂号"
,
"预约"
,
"主诉"
,
"等候"
,
"队列"
,
"consultation"
,
"consult"
,
"accept"
,
"waiting"
,
"queue"
,
// 处方
"prescription"
,
"medicine"
,
"catalog"
,
"药品目录"
,
"库存"
,
// 患者
"患者"
,
"档案"
,
"信息"
,
"资料"
,
"健康"
,
"profile"
,
"patient"
,
// 健康指标
"指标"
,
"血压"
,
"血糖"
,
"心率"
,
"体温"
,
"metric"
,
"health"
,
// 医生
"医生"
,
"doctor"
,
"排班"
,
"schedule"
,
"时段"
,
// 科室
"department"
,
"科室列表"
,
// 慢病
"慢病"
,
"慢性"
,
"续方"
,
"续药"
,
"审批"
,
"chronic"
,
"renewal"
,
// 支付
"订单"
,
"支付"
,
"付款"
,
"退款"
,
"order"
,
"payment"
,
// 收入
"收入"
,
"余额"
,
"提现"
,
"账单"
,
"收益"
,
"分成"
,
"income"
,
// 管理
"管理"
,
"统计"
,
"趋势"
,
"运营"
,
"仪表盘"
,
"dashboard"
,
"admin"
,
// 用户
"用户"
,
"user"
,
"列表"
,
"list"
,
// 系统
"日志"
,
"操作"
,
"系统"
,
"log"
,
"system"
,
// 导航
"导航"
,
"页面"
,
"跳转"
,
"navigate"
,
"page"
,
// 工作流
"工作流"
,
"流程"
,
"审核"
,
"workflow"
,
// 通知
"通知"
,
"提醒"
,
"notification"
,
// 随访
"随访"
,
"复诊"
,
"follow"
,
// 工具名关键词
"query"
,
"create"
,
"search"
,
"generate"
,
"record"
,
"check"
,
"drug"
,
"medical"
,
"symptom"
,
"knowledge"
,
"expression"
,
// 英文补充
"detail"
,
"stats"
,
"trend"
,
"report"
,
"lab"
,
}
func
simpleHash
(
s
string
)
uint64
{
var
h
uint64
=
5381
for
i
:=
0
;
i
<
len
(
s
);
i
++
{
...
...
web/src/api/agent.ts
View file @
da795257
...
...
@@ -70,6 +70,7 @@ export interface AgentDefinition {
description
:
string
;
category
:
string
;
system_prompt
:
string
;
prompt_template_id
:
number
|
null
;
tools
:
string
;
// JSON array string
skills
:
string
;
// JSON array string of skill_ids
config
:
string
;
...
...
@@ -85,6 +86,8 @@ export type AgentDefinitionParams = {
description
?:
string
;
category
?:
string
;
system_prompt
?:
string
;
prompt_template_id
?:
number
|
null
;
clear_template
?:
boolean
;
tools
?:
string
;
skills
?:
string
[];
max_iterations
?:
number
;
...
...
@@ -174,6 +177,9 @@ export const agentApi = {
listTools
:
()
=>
get
<
{
id
:
string
;
name
:
string
;
display_name
:
string
;
description
:
string
;
category
:
string
;
parameters
:
Record
<
string
,
unknown
>
;
status
:
string
;
is_enabled
:
boolean
;
cache_ttl
:
number
;
timeout
:
number
;
max_retries
:
number
;
created_at
:
string
}[]
>
(
'
/agent/tools
'
),
createSession
:
(
agentId
:
string
)
=>
post
<
{
session_id
:
string
}
>
(
'
/agent/sessions
'
,
{
agent_id
:
agentId
}),
getSessions
:
(
agentId
?:
string
)
=>
get
<
AgentSession
[]
>
(
'
/agent/sessions
'
,
{
params
:
agentId
?
{
agent_id
:
agentId
}
:
{}
}),
...
...
web/src/app/(main)/admin/agents/page.tsx
View file @
da795257
...
...
@@ -15,6 +15,8 @@ import {
import
{
useQuery
,
useMutation
,
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
{
agentApi
,
skillApi
,
httpToolApi
}
from
'
@/api/agent
'
;
import
type
{
ToolCall
,
AgentDefinition
}
from
'
@/api/agent
'
;
import
{
adminApi
}
from
'
@/api/admin
'
;
import
type
{
PromptTemplateData
}
from
'
@/api/admin
'
;
import
AllToolsTab
from
'
./AllToolsTab
'
;
import
BuiltinToolsTab
from
'
./BuiltinToolsTab
'
;
import
HTTPToolsTab
from
'
./HTTPToolsTab
'
;
...
...
@@ -83,6 +85,12 @@ function AgentsTab() {
});
const
agents
:
AgentDefinition
[]
=
agentsData
?.
data
||
[];
const
{
data
:
templatesData
}
=
useQuery
({
queryKey
:
[
'
prompt-templates
'
],
queryFn
:
()
=>
adminApi
.
getPromptTemplates
(),
});
const
templates
:
PromptTemplateData
[]
=
templatesData
?.
data
||
[];
useEffect
(()
=>
{
agentApi
.
listTools
().
then
(
res
=>
{
if
(
res
.
data
?.
length
>
0
)
{
...
...
@@ -95,12 +103,15 @@ function AgentsTab() {
mutationFn
:
(
values
:
Record
<
string
,
unknown
>
)
=>
{
const
toolsArr
=
values
.
tools_array
as
string
[]
||
[];
const
skillsArr
=
values
.
skills_array
as
string
[]
||
[];
const
templateId
=
values
.
prompt_template_id
as
number
|
undefined
;
const
params
=
{
agent_id
:
values
.
agent_id
as
string
,
name
:
values
.
name
as
string
,
description
:
values
.
description
as
string
,
category
:
values
.
category
as
string
,
system_prompt
:
values
.
system_prompt
as
string
,
prompt_template_id
:
templateId
||
null
,
clear_template
:
!
templateId
,
tools
:
JSON
.
stringify
(
toolsArr
),
skills
:
skillsArr
,
max_iterations
:
values
.
max_iterations
as
number
,
...
...
@@ -145,6 +156,7 @@ function AgentsTab() {
form
.
setFieldsValue
({
agent_id
:
agent
.
agent_id
,
name
:
agent
.
name
,
description
:
agent
.
description
,
category
:
agent
.
category
,
system_prompt
:
agent
.
system_prompt
,
prompt_template_id
:
agent
.
prompt_template_id
||
undefined
,
tools_array
:
toolsArr
,
skills_array
:
skillsArr
,
max_iterations
:
agent
.
max_iterations
,
status
:
agent
.
status
===
'
active
'
,
});
...
...
@@ -238,6 +250,16 @@ function AgentsTab() {
),
},
{
title
:
'
描述
'
,
dataIndex
:
'
description
'
,
key
:
'
description
'
,
ellipsis
:
true
},
{
title
:
'
提示词
'
,
key
:
'
prompt_source
'
,
width
:
120
,
render
:
(
_
:
unknown
,
r
:
AgentDefinition
)
=>
{
if
(
r
.
prompt_template_id
)
{
const
tpl
=
templates
.
find
(
t
=>
t
.
id
===
r
.
prompt_template_id
);
return
<
Tag
color=
"cyan"
>
{
tpl
?.
name
||
`模板#${r.prompt_template_id}`
}
</
Tag
>;
}
return
<
Tag
>
自定义
</
Tag
>;
},
},
{
title
:
'
类别
'
,
dataIndex
:
'
category
'
,
key
:
'
category
'
,
width
:
110
,
render
:
(
v
:
string
)
=>
<
Tag
color=
{
categoryColor
[
v
]
||
'
default
'
}
>
{
categoryLabel
[
v
]
||
v
}
</
Tag
>,
...
...
@@ -312,8 +334,18 @@ function AgentsTab() {
<
Form
.
Item
name=
"category"
label=
"类别"
>
<
Select
options=
{
CATEGORY_OPTIONS
}
placeholder=
"选择类别"
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"system_prompt"
label=
"系统提示词"
>
<
Input
.
TextArea
rows=
{
5
}
placeholder=
"输入 Agent 的系统提示词(留空则使用数据库中关联的提示词模板)"
/>
<
Form
.
Item
name=
"prompt_template_id"
label=
"关联提示词模板"
extra=
"优先使用关联模板的内容作为系统提示词,未关联时使用下方的系统提示词"
>
<
Select
allowClear
placeholder=
"选择提示词模板(可选)"
options=
{
templates
.
filter
(
t
=>
t
.
status
===
'
active
'
).
map
(
t
=>
({
value
:
t
.
id
,
label
:
`${t.name}(${t.template_key})`
,
}))
}
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"system_prompt"
label=
"系统提示词"
extra=
"当未关联模板时使用此提示词"
>
<
Input
.
TextArea
rows=
{
5
}
placeholder=
"输入 Agent 的系统提示词"
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"tools_array"
label=
"关联工具"
>
<
Select
...
...
web/src/app/(main)/doctor/consult/AIPanel.tsx
View file @
da795257
import
React
,
{
useState
,
useEffect
}
from
'
react
'
;
import
React
,
{
useState
,
useEffect
,
useRef
,
useCallback
}
from
'
react
'
;
import
{
Tabs
,
Typography
,
Space
,
Empty
,
Tag
,
Alert
,
Spin
,
Badge
,
Button
,
Collapse
,
Timeline
,
Divider
,
message
,
...
...
@@ -20,6 +20,9 @@ const { Text } = Typography;
type
AIScene
=
'
consult_diagnosis
'
|
'
consult_medication
'
|
'
consult_lab_advice
'
|
'
consult_medical_record
'
|
'
consult_follow_up
'
|
'
consult_order_template
'
;
// 支持流式的场景
const
STREAM_SCENES
:
AIScene
[]
=
[
'
consult_diagnosis
'
,
'
consult_medication
'
];
interface
SceneState
{
content
:
string
;
loading
:
boolean
;
...
...
@@ -47,32 +50,66 @@ const AIPanel: React.FC<AIPanelProps> = ({
})
=>
{
const
[
scenes
,
setScenes
]
=
useState
<
Record
<
string
,
SceneState
>>
({});
const
[
preConsultSubTab
,
setPreConsultSubTab
]
=
useState
(
'
chat
'
);
const
abortControllersRef
=
useRef
<
Record
<
string
,
AbortController
>>
({});
const
getScene
=
(
scene
:
string
):
SceneState
=>
scenes
[
scene
]
||
{
content
:
''
,
loading
:
false
,
toolCalls
:
[]
};
const
updateScene
=
(
scene
:
string
,
update
:
Partial
<
SceneState
>
)
=>
{
const
updateScene
=
useCallback
(
(
scene
:
string
,
update
:
Partial
<
SceneState
>
)
=>
{
setScenes
(
prev
=>
({
...
prev
,
[
scene
]:
{
...
getScene
(
scene
),
...
update
},
[
scene
]:
{
...
(
prev
[
scene
]
||
{
content
:
''
,
loading
:
false
,
toolCalls
:
[]
}
),
...
update
},
}));
};
},
[]);
const
handleAIAssistStream
=
useCallback
((
scene
:
AIScene
)
=>
{
if
(
!
activeConsultId
)
return
;
// 取消之前的同场景请求
abortControllersRef
.
current
[
scene
]?.
abort
();
updateScene
(
scene
,
{
content
:
''
,
loading
:
true
,
toolCalls
:
[]
});
let
accumulated
=
''
;
const
controller
=
consultApi
.
aiAssistStream
(
activeConsultId
,
scene
,
{
onChunk
:
(
content
:
string
)
=>
{
accumulated
+=
content
;
updateScene
(
scene
,
{
content
:
accumulated
,
loading
:
true
});
},
onDone
:
()
=>
{
updateScene
(
scene
,
{
loading
:
false
});
if
(
scene
===
'
consult_diagnosis
'
)
onDiagnosisChange
?.(
accumulated
);
if
(
scene
===
'
consult_medication
'
)
onMedicationChange
?.(
accumulated
);
},
onError
:
(
error
:
string
)
=>
{
updateScene
(
scene
,
{
content
:
accumulated
||
(
'
AI分析失败:
'
+
error
),
loading
:
false
,
});
},
});
abortControllersRef
.
current
[
scene
]
=
controller
;
},
[
activeConsultId
,
updateScene
,
onDiagnosisChange
,
onMedicationChange
]);
const
handleAIAssist
=
async
(
scene
:
AIScene
)
=>
{
const
handleAIAssist
=
useCallback
(
async
(
scene
:
AIScene
)
=>
{
if
(
!
activeConsultId
)
return
;
// 鉴别诊断和用药建议走流式
if
(
STREAM_SCENES
.
includes
(
scene
))
{
handleAIAssistStream
(
scene
);
return
;
}
updateScene
(
scene
,
{
loading
:
true
});
try
{
const
res
=
await
consultApi
.
aiAssist
(
activeConsultId
,
scene
);
const
text
=
res
.
data
?.
response
||
'
暂无分析结果
'
;
const
toolCalls
=
res
.
data
?.
tool_calls
||
[];
updateScene
(
scene
,
{
content
:
text
,
toolCalls
,
loading
:
false
});
if
(
scene
===
'
consult_diagnosis
'
)
onDiagnosisChange
?.(
text
);
if
(
scene
===
'
consult_medication
'
)
onMedicationChange
?.(
text
);
}
catch
(
err
:
unknown
)
{
const
text
=
'
AI分析失败:
'
+
((
err
as
Error
)?.
message
||
'
请稍后重试
'
);
updateScene
(
scene
,
{
content
:
text
,
toolCalls
:
[],
loading
:
false
});
}
};
}
,
[
activeConsultId
,
updateScene
,
handleAIAssistStream
])
;
// Auto-trigger diagnosis + medication when patient sends new message
useEffect
(()
=>
{
...
...
@@ -82,6 +119,13 @@ const AIPanel: React.FC<AIPanelProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
patientMessageTrigger
,
activeConsultId
]);
// Cleanup abort controllers on unmount
useEffect
(()
=>
{
return
()
=>
{
Object
.
values
(
abortControllersRef
.
current
).
forEach
(
c
=>
c
.
abort
());
};
},
[]);
const
renderToolCalls
=
(
toolCalls
:
ToolCall
[])
=>
{
if
(
!
toolCalls
||
toolCalls
.
length
===
0
)
return
null
;
return
(
...
...
@@ -137,6 +181,23 @@ const AIPanel: React.FC<AIPanelProps> = ({
extra
?:
React
.
ReactNode
,
)
=>
{
const
state
=
getScene
(
scene
);
const
isStreamScene
=
STREAM_SCENES
.
includes
(
scene
);
// 流式场景:loading 期间如果已有内容,显示内容+光标
if
(
state
.
loading
&&
isStreamScene
&&
state
.
content
)
{
return
(
<
div
>
<
div
style=
{
{
maxHeight
:
360
,
overflow
:
'
auto
'
,
padding
:
'
4px 0
'
}
}
>
<
MarkdownRenderer
content=
{
state
.
content
+
'
▍
'
}
fontSize=
{
12
}
lineHeight=
{
1.6
}
/>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#0891B2
'
,
marginTop
:
6
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
}
}
>
<
Spin
size=
"small"
/>
<
span
>
AI 正在分析中...
</
span
>
</
div
>
</
div
>
);
}
if
(
state
.
loading
)
{
return
(
<
div
style=
{
{
textAlign
:
'
center
'
,
padding
:
'
32px 16px
'
}
}
>
...
...
web/src/app/(main)/patient/chronic/page.tsx
View file @
da795257
...
...
@@ -2,8 +2,8 @@
import
React
,
{
useState
}
from
'
react
'
;
import
{
Card
,
Tabs
,
Button
,
Tag
,
Space
,
Form
,
Input
,
Select
,
DatePicker
,
Switch
,
message
,
Popconfirm
,
Typography
,
Row
,
Col
,
Card
,
Tabs
,
Button
,
Tag
,
Space
,
Form
,
Input
,
Select
,
App
,
DatePicker
,
Switch
,
Popconfirm
,
Typography
,
Row
,
Col
,
TimePicker
,
Statistic
,
Empty
,
}
from
'
antd
'
;
import
{
DrawerForm
,
ProTable
}
from
'
@ant-design/pro-components
'
;
...
...
@@ -36,6 +36,7 @@ const metricTypeMap: Record<string, { label: string; unit: string; hasValue2: bo
// ========== 慢病档案 Tab ==========
const
ChronicRecordsTab
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
qc
=
useQueryClient
();
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
const
[
editing
,
setEditing
]
=
useState
<
ChronicRecord
|
null
>
(
null
);
...
...
@@ -48,7 +49,7 @@ const ChronicRecordsTab: React.FC = () => {
const
saveMutation
=
useMutation
({
mutationFn
:
(
values
:
any
)
=>
{
const
payload
=
{
...
values
,
diagnosis_date
:
values
.
diagnosis_date
?.
toISOString
()
};
const
payload
=
{
...
values
,
diagnosis_date
:
values
.
diagnosis_date
?
dayjs
(
values
.
diagnosis_date
).
toISOString
()
:
null
};
return
editing
?
chronicApi
.
updateRecord
(
editing
.
id
,
payload
)
:
chronicApi
.
createRecord
(
payload
);
...
...
@@ -56,7 +57,6 @@ const ChronicRecordsTab: React.FC = () => {
onSuccess
:
()
=>
{
message
.
success
(
editing
?
'
更新成功
'
:
'
添加成功
'
);
qc
.
invalidateQueries
({
queryKey
:
[
'
chronic-records
'
]
});
setModalOpen
(
false
);
form
.
resetFields
();
setEditing
(
null
);
},
...
...
@@ -124,8 +124,8 @@ const ChronicRecordsTab: React.FC = () => {
onOpenChange=
{
(
open
)
=>
{
if
(
!
open
)
{
setModalOpen
(
false
);
setEditing
(
null
);
form
.
resetFields
();
}
}
}
form=
{
form
}
width=
{
600
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
onFinish=
{
async
(
values
)
=>
{
saveMutation
.
mutate
(
values
);
return
true
;
}
}
drawerProps=
{
{
placement
:
'
right
'
}
}
onFinish=
{
async
(
values
)
=>
{
await
saveMutation
.
mutateAsync
(
values
);
return
true
;
}
}
submitter=
{
{
submitButtonProps
:
{
loading
:
saveMutation
.
isPending
}
}
}
>
<
Form
.
Item
name=
"disease_name"
label=
"疾病名称"
rules=
{
[{
required
:
true
}]
}
>
...
...
@@ -168,6 +168,7 @@ const ChronicRecordsTab: React.FC = () => {
// ========== 续方申请 Tab ==========
const
RenewalsTab
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
qc
=
useQueryClient
();
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
const
[
aiLoading
,
setAiLoading
]
=
useState
<
string
|
null
>
(
null
);
...
...
@@ -185,7 +186,6 @@ const RenewalsTab: React.FC = () => {
onSuccess
:
()
=>
{
message
.
success
(
'
续方申请已提交
'
);
qc
.
invalidateQueries
({
queryKey
:
[
'
renewals
'
]
});
setModalOpen
(
false
);
form
.
resetFields
();
},
onError
:
()
=>
message
.
error
(
'
提交失败
'
),
...
...
@@ -260,8 +260,8 @@ const RenewalsTab: React.FC = () => {
onOpenChange=
{
(
open
)
=>
{
if
(
!
open
)
{
setModalOpen
(
false
);
form
.
resetFields
();
}
}
}
form=
{
form
}
width=
{
480
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
onFinish=
{
async
(
values
)
=>
{
createMutation
.
mutate
(
values
);
return
true
;
}
}
drawerProps=
{
{
placement
:
'
right
'
}
}
onFinish=
{
async
(
values
)
=>
{
await
createMutation
.
mutateAsync
(
values
);
return
true
;
}
}
submitter=
{
{
submitButtonProps
:
{
loading
:
createMutation
.
isPending
}
}
}
>
<
Form
.
Item
name=
"chronic_id"
label=
"关联慢病档案"
>
...
...
@@ -284,6 +284,7 @@ const RenewalsTab: React.FC = () => {
// ========== 用药提醒 Tab ==========
const
RemindersTab
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
qc
=
useQueryClient
();
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
const
[
editing
,
setEditing
]
=
useState
<
MedicationReminder
|
null
>
(
null
);
...
...
@@ -296,15 +297,15 @@ const RemindersTab: React.FC = () => {
const
payload
=
{
...
values
,
remind_times
:
values
.
remind_times
?.
map
((
t
:
any
)
=>
t
.
format
(
'
HH:mm
'
))
||
[],
start_date
:
values
.
start_date
?.
toISOString
()
,
end_date
:
values
.
end_date
?.
toISOString
()
,
start_date
:
values
.
start_date
?
dayjs
(
values
.
start_date
).
toISOString
()
:
null
,
end_date
:
values
.
end_date
?
dayjs
(
values
.
end_date
).
toISOString
()
:
null
,
};
return
editing
?
chronicApi
.
updateReminder
(
editing
.
id
,
payload
)
:
chronicApi
.
createReminder
(
payload
);
},
onSuccess
:
()
=>
{
message
.
success
(
editing
?
'
更新成功
'
:
'
添加成功
'
);
qc
.
invalidateQueries
({
queryKey
:
[
'
reminders
'
]
});
setModalOpen
(
false
);
form
.
resetFields
();
setEditing
(
null
);
form
.
resetFields
();
setEditing
(
null
);
},
onError
:
()
=>
message
.
error
(
'
操作失败
'
),
});
...
...
@@ -371,8 +372,8 @@ const RemindersTab: React.FC = () => {
onOpenChange=
{
(
open
)
=>
{
if
(
!
open
)
{
setModalOpen
(
false
);
setEditing
(
null
);
form
.
resetFields
();
}
}
}
form=
{
form
}
width=
{
600
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
onFinish=
{
async
(
values
)
=>
{
saveMutation
.
mutate
(
values
);
return
true
;
}
}
drawerProps=
{
{
placement
:
'
right
'
}
}
onFinish=
{
async
(
values
)
=>
{
await
saveMutation
.
mutateAsync
(
values
);
return
true
;
}
}
submitter=
{
{
submitButtonProps
:
{
loading
:
saveMutation
.
isPending
}
}
}
>
<
Row
gutter=
{
16
}
>
...
...
@@ -401,6 +402,7 @@ const RemindersTab: React.FC = () => {
// ========== 健康指标 Tab ==========
const
MetricsTab
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
qc
=
useQueryClient
();
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
const
[
activeType
,
setActiveType
]
=
useState
(
'
blood_pressure
'
);
...
...
@@ -418,13 +420,13 @@ const MetricsTab: React.FC = () => {
...
values
,
metric_type
:
activeType
,
unit
:
meta
.
unit
,
recorded_at
:
values
.
recorded_at
?.
toISOString
()
||
new
Date
().
toISOString
(),
recorded_at
:
values
.
recorded_at
?
dayjs
(
values
.
recorded_at
).
toISOString
()
:
dayjs
().
toISOString
(),
});
},
onSuccess
:
()
=>
{
message
.
success
(
'
记录成功
'
);
qc
.
invalidateQueries
({
queryKey
:
[
'
metrics
'
,
activeType
]
});
setModalOpen
(
false
);
form
.
resetFields
();
form
.
resetFields
();
},
onError
:
()
=>
message
.
error
(
'
记录失败
'
),
});
...
...
@@ -535,8 +537,8 @@ const MetricsTab: React.FC = () => {
onOpenChange=
{
(
open
)
=>
{
if
(
!
open
)
{
setModalOpen
(
false
);
form
.
resetFields
();
}
}
}
form=
{
form
}
width=
{
480
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
onFinish=
{
async
(
values
)
=>
{
createMutation
.
mutate
(
values
);
return
true
;
}
}
drawerProps=
{
{
placement
:
'
right
'
}
}
onFinish=
{
async
(
values
)
=>
{
await
createMutation
.
mutateAsync
(
values
);
return
true
;
}
}
submitter=
{
{
submitButtonProps
:
{
loading
:
createMutation
.
isPending
}
}
}
>
<
Row
gutter=
{
16
}
>
...
...
web/src/components/GlobalAIFloat/ChatPanel.tsx
View file @
da795257
'
use client
'
;
import
React
,
{
useState
,
useRef
,
useEffect
,
useCallback
,
useMemo
}
from
'
react
'
;
import
{
Input
,
Button
,
Tag
,
Spin
,
Tooltip
}
from
'
antd
'
;
import
{
Input
,
Button
,
Tag
,
Spin
,
Tooltip
,
App
}
from
'
antd
'
;
import
{
SendOutlined
,
RobotOutlined
,
...
...
@@ -22,7 +22,6 @@ import ToolCallCard from './ToolCallCard';
import
ToolResultCard
from
'
./ToolResultCard
'
;
import
SuggestedActions
,
{
parseActions
}
from
'
./SuggestedActions
'
;
import
{
validateNavigationPermission
}
from
'
../../lib/navigation-event
'
;
import
{
message
as
antMessage
}
from
'
antd
'
;
import
type
{
ChatMessage
,
ToolCall
,
WidgetRole
}
from
'
./types
'
;
import
{
ROLE_AGENT_ID
,
ROLE_AGENT_NAME
,
ROLE_THEME
,
QUICK_ITEMS
}
from
'
./types
'
;
...
...
@@ -50,6 +49,7 @@ const QUICK_ICON: Record<WidgetRole, React.ReactNode> = {
};
const
ChatPanel
:
React
.
FC
<
ChatPanelProps
>
=
({
role
,
patientContext
})
=>
{
const
{
message
:
antMessage
}
=
App
.
useApp
();
const
store
=
useAIAssistStore
();
const
messages
=
Array
.
isArray
(
store
.
messages
)
?
store
.
messages
:
[];
const
sessionId
=
store
.
sessionId
||
''
;
...
...
@@ -78,31 +78,33 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ role, patientContext }) => {
return
welcomeMap
[
role
];
},
[
agentName
,
role
]);
// 创建新会话
const
createNewSession
=
useCallback
(
async
()
=>
{
try
{
const
res
=
await
agentApi
.
createSession
(
agentId
);
setSessionId
(
res
.
data
.
session_id
);
return
res
.
data
.
session_id
;
}
catch
(
e
)
{
console
.
warn
(
'
[ChatPanel] 创建会话失败:
'
,
e
);
return
''
;
}
},
[
agentId
,
setSessionId
]);
// 加载最近会话的函数
const
loadLatestSession
=
useCallback
(
async
()
=>
{
if
(
isSessionLoaded
)
return
;
try
{
console
.
log
(
'
[ChatPanel] 加载最近会话, agentId:
'
,
agentId
);
const
res
=
await
agentApi
.
getSessions
(
agentId
);
const
sessions
=
res
.
data
;
console
.
log
(
'
[ChatPanel] 获取到会话列表:
'
,
sessions
?.
length
||
0
,
'
个
'
);
if
(
sessions
&&
sessions
.
length
>
0
)
{
// 打印前3条会话的时间,用于调试排序
sessions
.
slice
(
0
,
3
).
forEach
((
s
,
idx
)
=>
{
console
.
log
(
`[ChatPanel] 会话
${
idx
}
:
${
s
.
session_id
}
, updated_at:
${
s
.
updated_at
}
`
);
});
const
latest
=
sessions
[
0
];
console
.
log
(
'
[ChatPanel] 恢复会话:
'
,
latest
.
session_id
,
'
updated_at:
'
,
latest
.
updated_at
);
setSessionId
(
latest
.
session_id
);
// 尝试恢复历史消息
try
{
const
history
=
JSON
.
parse
(
latest
.
history
||
'
[]
'
)
as
{
role
:
string
;
content
:
string
}[];
console
.
log
(
'
[ChatPanel] 解析历史消息:
'
,
history
.
length
,
'
条
'
);
if
(
history
.
length
>
0
)
{
const
restored
:
ChatMessage
[]
=
history
.
map
(
h
=>
({
role
:
h
.
role
as
'
user
'
|
'
assistant
'
,
...
...
@@ -110,23 +112,29 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ role, patientContext }) => {
timestamp
:
new
Date
(),
}));
setMessages
(
restored
);
setSessionLoaded
(
true
);
return
;
}
else
{
// 会话存在但没有历史消息,显示欢迎语,复用该会话
setMessages
([{
role
:
'
system
'
,
content
:
welcomeContent
,
timestamp
:
new
Date
()
}]);
}
}
catch
(
e
)
{
console
.
warn
(
'
[ChatPanel] 解析历史失败:
'
,
e
);
setMessages
([{
role
:
'
system
'
,
content
:
welcomeContent
,
timestamp
:
new
Date
()
}]);
}
setSessionLoaded
(
true
);
return
;
}
// 没有历史会话,显示欢迎消息
// 完全没有会话,才创建新会话
await
createNewSession
();
setMessages
([{
role
:
'
system
'
,
content
:
welcomeContent
,
timestamp
:
new
Date
()
}]);
setSessionLoaded
(
true
);
}
catch
(
e
)
{
console
.
warn
(
'
[ChatPanel] 加载会话失败:
'
,
e
);
await
createNewSession
();
setMessages
([{
role
:
'
system
'
,
content
:
welcomeContent
,
timestamp
:
new
Date
()
}]);
setSessionLoaded
(
true
);
}
},
[
agentId
,
isSessionLoaded
,
welcomeContent
,
setMessages
,
setSessionId
,
setSessionLoaded
]);
},
[
agentId
,
isSessionLoaded
,
welcomeContent
,
setMessages
,
setSessionId
,
setSessionLoaded
,
createNewSession
]);
// 当组件挂载且会话未加载时,自动加载最近会话
useEffect
(()
=>
{
...
...
@@ -242,10 +250,10 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ role, patientContext }) => {
abortRef
.
current
=
agentApi
.
chatStream
(
agentId
,
buildRequestBody
(
userMessage
),
createStreamCallbacks
(
msgId
));
};
const
handleNewChat
=
()
=>
{
const
handleNewChat
=
async
()
=>
{
stopStreaming
();
//
清空 sessionId,但保持 isSessionLoaded = true,避免重新加载旧会话
setSessionId
(
''
);
//
预创建新会话,获取 session_id
await
createNewSession
(
);
setMessages
([{
role
:
'
system
'
,
content
:
welcomeContent
,
timestamp
:
new
Date
()
}]);
// 标记为已加载,防止自动恢复旧会话
setSessionLoaded
(
true
);
...
...
web/src/components/GlobalAIFloat/ToolCallCard.tsx
View file @
da795257
...
...
@@ -27,28 +27,86 @@ import {
UnorderedListOutlined
,
AuditOutlined
,
ScheduleOutlined
,
SolutionOutlined
,
TeamOutlined
,
HeartOutlined
,
ExperimentOutlined
,
DollarOutlined
,
DashboardOutlined
,
UserOutlined
,
FileTextOutlined
,
CalendarOutlined
,
ProfileOutlined
,
BarChartOutlined
,
WalletOutlined
,
}
from
'
@ant-design/icons
'
;
// 工具元数据映射(互联网医院场景)
const
TOOL_META
:
Record
<
string
,
{
label
:
string
;
color
:
string
;
icon
:
React
.
ReactNode
}
>
=
{
// 基础查询
query_drug
:
{
label
:
'
药品查询
'
,
color
:
'
#52c41a
'
,
icon
:
<
MedicineBoxOutlined
/>
},
query_medical_record
:
{
label
:
'
病历查询
'
,
color
:
'
#0D9488
'
,
icon
:
<
FileSearchOutlined
/>
},
query_symptom_knowledge
:
{
label
:
'
症状知识
'
,
color
:
'
#0891B2
'
,
icon
:
<
BulbOutlined
/>
},
recommend_department
:
{
label
:
'
科室推荐
'
,
color
:
'
#13c2c2
'
,
icon
:
<
ApartmentOutlined
/>
},
// 处方安全
check_drug_interaction
:
{
label
:
'
药物相互作用
'
,
color
:
'
#f5222d
'
,
icon
:
<
WarningOutlined
/>
},
check_contraindication
:
{
label
:
'
禁忌症检查
'
,
color
:
'
#fa8c16
'
,
icon
:
<
StopOutlined
/>
},
calculate_dosage
:
{
label
:
'
剂量计算
'
,
color
:
'
#faad14
'
,
icon
:
<
CalculatorOutlined
/>
},
// 知识库
search_medical_knowledge
:
{
label
:
'
知识检索
'
,
color
:
'
#2f54eb
'
,
icon
:
<
BookOutlined
/>
},
write_knowledge
:
{
label
:
'
知识写入
'
,
color
:
'
#52c41a
'
,
icon
:
<
EditOutlined
/>
},
list_knowledge_collections
:{
label
:
'
知识库列表
'
,
color
:
'
#2f54eb
'
,
icon
:
<
UnorderedListOutlined
/>
},
// 导航 & 通知
navigate_page
:
{
label
:
'
页面导航
'
,
color
:
'
#0D9488
'
,
icon
:
<
CompassOutlined
/>
},
send_notification
:
{
label
:
'
发送通知
'
,
color
:
'
#fa541c
'
,
icon
:
<
BellOutlined
/>
},
// Agent & 工作流
call_agent
:
{
label
:
'
调用Agent
'
,
color
:
'
#0891B2
'
,
icon
:
<
RobotOutlined
/>
},
trigger_workflow
:
{
label
:
'
触发工作流
'
,
color
:
'
#13c2c2
'
,
icon
:
<
DeploymentUnitOutlined
/>
},
query_workflow_status
:
{
label
:
'
工作流状态
'
,
color
:
'
#13c2c2
'
,
icon
:
<
DeploymentUnitOutlined
/>
},
eval_expression
:
{
label
:
'
表达式计算
'
,
color
:
'
#a0d911
'
,
icon
:
<
CodeOutlined
/>
},
write_knowledge
:
{
label
:
'
知识写入
'
,
color
:
'
#52c41a
'
,
icon
:
<
EditOutlined
/>
},
list_knowledge_collections
:{
label
:
'
知识库列表
'
,
color
:
'
#2f54eb
'
,
icon
:
<
UnorderedListOutlined
/>
},
request_human_review
:
{
label
:
'
人工审核
'
,
color
:
'
#fa8c16
'
,
icon
:
<
AuditOutlined
/>
},
// 表达式 & 随访
eval_expression
:
{
label
:
'
表达式计算
'
,
color
:
'
#a0d911
'
,
icon
:
<
CodeOutlined
/>
},
generate_follow_up_plan
:
{
label
:
'
随访计划
'
,
color
:
'
#eb2f96
'
,
icon
:
<
ScheduleOutlined
/>
},
generate_tool
:
{
label
:
'
工具生成
'
,
color
:
'
#722ed1
'
,
icon
:
<
CodeOutlined
/>
},
// 问诊管理
query_consultation_list
:
{
label
:
'
问诊列表
'
,
color
:
'
#1890ff
'
,
icon
:
<
SolutionOutlined
/>
},
query_consultation_detail
:
{
label
:
'
问诊详情
'
,
color
:
'
#1890ff
'
,
icon
:
<
SolutionOutlined
/>
},
create_consultation
:
{
label
:
'
创建问诊
'
,
color
:
'
#52c41a
'
,
icon
:
<
SolutionOutlined
/>
},
accept_consultation
:
{
label
:
'
接诊
'
,
color
:
'
#13c2c2
'
,
icon
:
<
SolutionOutlined
/>
},
query_waiting_queue
:
{
label
:
'
等候队列
'
,
color
:
'
#faad14
'
,
icon
:
<
TeamOutlined
/>
},
// 处方管理
query_prescription_list
:
{
label
:
'
处方列表
'
,
color
:
'
#722ed1
'
,
icon
:
<
ProfileOutlined
/>
},
query_prescription_detail
:
{
label
:
'
处方详情
'
,
color
:
'
#722ed1
'
,
icon
:
<
ProfileOutlined
/>
},
search_medicine_catalog
:
{
label
:
'
药品目录
'
,
color
:
'
#52c41a
'
,
icon
:
<
MedicineBoxOutlined
/>
},
// 患者信息
query_patient_profile
:
{
label
:
'
患者档案
'
,
color
:
'
#eb2f96
'
,
icon
:
<
UserOutlined
/>
},
query_health_metrics
:
{
label
:
'
健康指标
'
,
color
:
'
#f5222d
'
,
icon
:
<
HeartOutlined
/>
},
query_lab_reports
:
{
label
:
'
化验报告
'
,
color
:
'
#fa8c16
'
,
icon
:
<
ExperimentOutlined
/>
},
// 医生 & 科室
query_doctor_list
:
{
label
:
'
医生列表
'
,
color
:
'
#1890ff
'
,
icon
:
<
TeamOutlined
/>
},
query_doctor_detail
:
{
label
:
'
医生详情
'
,
color
:
'
#1890ff
'
,
icon
:
<
UserOutlined
/>
},
query_department_list
:
{
label
:
'
科室列表
'
,
color
:
'
#13c2c2
'
,
icon
:
<
ApartmentOutlined
/>
},
// 慢病管理
query_chronic_records
:
{
label
:
'
慢病记录
'
,
color
:
'
#f5222d
'
,
icon
:
<
FileTextOutlined
/>
},
create_chronic_record
:
{
label
:
'
创建慢病记录
'
,
color
:
'
#f5222d
'
,
icon
:
<
FileTextOutlined
/>
},
query_renewal_requests
:
{
label
:
'
续方申请
'
,
color
:
'
#fa8c16
'
,
icon
:
<
ProfileOutlined
/>
},
create_renewal_request
:
{
label
:
'
创建续方
'
,
color
:
'
#fa8c16
'
,
icon
:
<
ProfileOutlined
/>
},
// 健康指标写入
record_health_metric
:
{
label
:
'
记录指标
'
,
color
:
'
#eb2f96
'
,
icon
:
<
HeartOutlined
/>
},
// 排班管理
query_doctor_schedule
:
{
label
:
'
排班查询
'
,
color
:
'
#1890ff
'
,
icon
:
<
CalendarOutlined
/>
},
create_doctor_schedule
:
{
label
:
'
创建排班
'
,
color
:
'
#1890ff
'
,
icon
:
<
CalendarOutlined
/>
},
// 支付订单
query_order_list
:
{
label
:
'
订单列表
'
,
color
:
'
#faad14
'
,
icon
:
<
DollarOutlined
/>
},
query_order_detail
:
{
label
:
'
订单详情
'
,
color
:
'
#faad14
'
,
icon
:
<
DollarOutlined
/>
},
// 医生收入
query_income_stats
:
{
label
:
'
收入统计
'
,
color
:
'
#52c41a
'
,
icon
:
<
WalletOutlined
/>
},
query_income_records
:
{
label
:
'
收入明细
'
,
color
:
'
#52c41a
'
,
icon
:
<
WalletOutlined
/>
},
// 管理统计
query_dashboard_stats
:
{
label
:
'
运营概览
'
,
color
:
'
#722ed1
'
,
icon
:
<
DashboardOutlined
/>
},
query_dashboard_trend
:
{
label
:
'
趋势分析
'
,
color
:
'
#722ed1
'
,
icon
:
<
BarChartOutlined
/>
},
query_user_list
:
{
label
:
'
用户列表
'
,
color
:
'
#1890ff
'
,
icon
:
<
TeamOutlined
/>
},
query_system_logs
:
{
label
:
'
系统日志
'
,
color
:
'
#8c8c8c
'
,
icon
:
<
FileTextOutlined
/>
},
};
// 参数中文映射
...
...
@@ -65,6 +123,20 @@ const PARAM_LABELS: Record<string, string> = {
patient_name
:
'
患者姓名
'
,
consult_id
:
'
问诊ID
'
,
level
:
'
级别
'
,
channel
:
'
通道
'
,
target_user_id
:
'
目标用户
'
,
description
:
'
描述
'
,
input
:
'
输入
'
,
// 问诊
status
:
'
状态
'
,
doctor_id
:
'
医生ID
'
,
doctor_name
:
'
医生姓名
'
,
consultation_id
:
'
问诊ID
'
,
chief_complaint
:
'
主诉
'
,
// 处方
prescription_id
:
'
处方ID
'
,
medicine_name
:
'
药品名称
'
,
// 患者
metric_type
:
'
指标类型
'
,
start_date
:
'
开始日期
'
,
end_date
:
'
结束日期
'
,
// 排班
date
:
'
日期
'
,
time_slot
:
'
时段
'
,
max_patients
:
'
最大接诊数
'
,
// 订单
order_id
:
'
订单ID
'
,
order_type
:
'
订单类型
'
,
// 管理
period
:
'
统计周期
'
,
days
:
'
天数
'
,
role
:
'
角色
'
,
resource
:
'
资源类型
'
,
limit
:
'
条数
'
,
offset
:
'
偏移
'
,
};
interface
ToolCallCardProps
{
...
...
web/src/components/GlobalAIFloat/ToolResultCard.tsx
View file @
da795257
...
...
@@ -13,6 +13,17 @@ import {
RightOutlined
,
ExclamationCircleOutlined
,
DatabaseOutlined
,
SolutionOutlined
,
HeartOutlined
,
DollarOutlined
,
DashboardOutlined
,
TeamOutlined
,
CalendarOutlined
,
UserOutlined
,
ExperimentOutlined
,
ProfileOutlined
,
WalletOutlined
,
BarChartOutlined
,
}
from
'
@ant-design/icons
'
;
import
{
Tag
}
from
'
antd
'
;
...
...
@@ -258,6 +269,156 @@ const DataTable: React.FC<{ data: Record<string, unknown>[] }> = ({ data }) => {
);
};
/** 问诊卡片 */
const
ConsultationCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
{
const
statusMap
:
Record
<
string
,
{
color
:
string
;
text
:
string
}
>
=
{
waiting
:
{
color
:
'
orange
'
,
text
:
'
等待中
'
},
in_progress
:
{
color
:
'
blue
'
,
text
:
'
进行中
'
},
completed
:
{
color
:
'
green
'
,
text
:
'
已完成
'
},
cancelled
:
{
color
:
'
default
'
,
text
:
'
已取消
'
},
};
const
s
=
statusMap
[
String
(
data
.
status
||
''
)]
||
{
color
:
'
default
'
,
text
:
String
(
data
.
status
||
'
-
'
)
};
return
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#eff6ff
'
,
border
:
'
1px solid #bfdbfe
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
SolutionOutlined
style=
{
{
color
:
'
#2563eb
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#1e40af
'
,
fontSize
:
12
}
}
>
{
String
(
data
.
patient_name
||
data
.
doctor_name
||
'
问诊
'
)
}
#
{
String
(
data
.
id
||
data
.
consultation_id
||
''
)
}
</
span
>
<
Tag
color=
{
s
.
color
}
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
}
}
>
{
s
.
text
}
</
Tag
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#3b82f6
'
,
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
}
}
>
{
data
.
chief_complaint
?
<
span
>
主���:
{
String
(
data
.
chief_complaint
)
}
</
span
>
:
null
}
{
data
.
doctor_name
?
<
span
>
医生:
{
String
(
data
.
doctor_name
)
}
</
span
>
:
null
}
{
data
.
department_name
?
<
span
>
科室:
{
String
(
data
.
department_name
)
}
</
span
>
:
null
}
{
data
.
created_at
?
<
span
>
时间:
{
String
(
data
.
created_at
).
slice
(
0
,
16
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
};
/** 健康指标卡片 */
const
HealthMetricCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#fef2f2
'
,
border
:
'
1px solid #fecaca
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
HeartOutlined
style=
{
{
color
:
'
#ef4444
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#991b1b
'
,
fontSize
:
12
}
}
>
{
String
(
data
.
metric_type
||
data
.
type
||
'
健康指标
'
)
}
</
span
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#dc2626
'
,
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
}
}
>
{
data
.
value
!==
undefined
?
<
span
>
数值:
{
String
(
data
.
value
)
}
{
String
(
data
.
unit
||
''
)
}
</
span
>
:
null
}
{
data
.
recorded_at
?
<
span
>
时间:
{
String
(
data
.
recorded_at
).
slice
(
0
,
16
)
}
</
span
>
:
null
}
{
data
.
status
?
<
span
>
状态:
{
String
(
data
.
status
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
/** 订单卡片 */
const
OrderCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
{
const
statusMap
:
Record
<
string
,
{
color
:
string
;
text
:
string
}
>
=
{
pending
:
{
color
:
'
orange
'
,
text
:
'
待支付
'
},
paid
:
{
color
:
'
green
'
,
text
:
'
已支付
'
},
refunded
:
{
color
:
'
red
'
,
text
:
'
已退款
'
},
cancelled
:
{
color
:
'
default
'
,
text
:
'
已取消
'
},
};
const
s
=
statusMap
[
String
(
data
.
status
||
''
)]
||
{
color
:
'
default
'
,
text
:
String
(
data
.
status
||
'
-
'
)
};
return
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#fffbeb
'
,
border
:
'
1px solid #fde68a
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
DollarOutlined
style=
{
{
color
:
'
#d97706
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#92400e
'
,
fontSize
:
12
}
}
>
订单 #
{
String
(
data
.
id
||
data
.
order_id
||
''
)
}
</
span
>
<
Tag
color=
{
s
.
color
}
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
}
}
>
{
s
.
text
}
</
Tag
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#b45309
'
,
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
}
}
>
{
data
.
amount
!==
undefined
?
<
span
>
金额: ¥
{
String
(
data
.
amount
)
}
</
span
>
:
null
}
{
data
.
order_type
?
<
span
>
类型:
{
String
(
data
.
order_type
)
}
</
span
>
:
null
}
{
data
.
created_at
?
<
span
>
时间:
{
String
(
data
.
created_at
).
slice
(
0
,
16
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
};
/** 仪表盘统计卡片 */
const
DashboardCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#f5f3ff
'
,
border
:
'
1px solid #ddd6fe
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
6
}
}
>
<
DashboardOutlined
style=
{
{
color
:
'
#7c3aed
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#5b21b6
'
,
fontSize
:
12
}
}
>
运营概览
</
span
>
</
div
>
<
div
style=
{
{
display
:
'
grid
'
,
gridTemplateColumns
:
'
repeat(2, 1fr)
'
,
gap
:
6
}
}
>
{
Object
.
entries
(
data
).
filter
(([,
v
])
=>
typeof
v
===
'
number
'
||
typeof
v
===
'
string
'
).
slice
(
0
,
8
).
map
(([
k
,
v
])
=>
(
<
div
key=
{
k
}
style=
{
{
background
:
'
#ede9fe
'
,
borderRadius
:
4
,
padding
:
'
4px 8px
'
,
textAlign
:
'
center
'
}
}
>
<
div
style=
{
{
fontSize
:
14
,
fontWeight
:
600
,
color
:
'
#6d28d9
'
}
}
>
{
String
(
v
)
}
</
div
>
<
div
style=
{
{
fontSize
:
10
,
color
:
'
#7c3aed
'
}
}
>
{
k
.
replace
(
/_/g
,
'
'
)
}
</
div
>
</
div
>
))
}
</
div
>
</
div
>
);
/** 收入统计卡片 */
const
IncomeCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#f0fdf4
'
,
border
:
'
1px solid #bbf7d0
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
6
}
}
>
<
WalletOutlined
style=
{
{
color
:
'
#16a34a
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#166534
'
,
fontSize
:
12
}
}
>
收入统计
</
span
>
</
div
>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
,
fontSize
:
11
,
color
:
'
#15803d
'
}
}
>
{
data
.
balance
!==
undefined
?
<
span
>
余额: ¥
{
String
(
data
.
balance
)
}
</
span
>
:
null
}
{
data
.
month_income
!==
undefined
?
<
span
>
本月收入: ¥
{
String
(
data
.
month_income
)
}
</
span
>
:
null
}
{
data
.
month_consults
!==
undefined
?
<
span
>
本月问诊:
{
String
(
data
.
month_consults
)
}
次
</
span
>
:
null
}
{
data
.
total_income
!==
undefined
?
<
span
>
总收入: ¥
{
String
(
data
.
total_income
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
/** 处方卡片 */
const
PrescriptionCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
{
const
statusMap
:
Record
<
string
,
{
color
:
string
;
text
:
string
}
>
=
{
pending
:
{
color
:
'
orange
'
,
text
:
'
待审核
'
},
approved
:
{
color
:
'
green
'
,
text
:
'
已通过
'
},
rejected
:
{
color
:
'
red
'
,
text
:
'
已驳回
'
},
dispensed
:
{
color
:
'
blue
'
,
text
:
'
已发药
'
},
};
const
s
=
statusMap
[
String
(
data
.
status
||
''
)]
||
{
color
:
'
default
'
,
text
:
String
(
data
.
status
||
'
-
'
)
};
return
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#faf5ff
'
,
border
:
'
1px solid #e9d5ff
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
ProfileOutlined
style=
{
{
color
:
'
#7c3aed
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#5b21b6
'
,
fontSize
:
12
}
}
>
处方 #
{
String
(
data
.
id
||
data
.
prescription_id
||
''
)
}
</
span
>
<
Tag
color=
{
s
.
color
}
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
}
}
>
{
s
.
text
}
</
Tag
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#7c3aed
'
,
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
}
}
>
{
data
.
patient_name
?
<
span
>
患者:
{
String
(
data
.
patient_name
)
}
</
span
>
:
null
}
{
data
.
doctor_name
?
<
span
>
医生:
{
String
(
data
.
doctor_name
)
}
</
span
>
:
null
}
{
data
.
created_at
?
<
span
>
时间:
{
String
(
data
.
created_at
).
slice
(
0
,
16
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
};
/** 化验报告卡片 */
const
LabReportCard
:
React
.
FC
<
{
data
:
Record
<
string
,
unknown
>
}
>
=
({
data
})
=>
(
<
div
style=
{
{
padding
:
8
,
background
:
'
#fff7ed
'
,
border
:
'
1px solid #fed7aa
'
,
borderRadius
:
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
ExperimentOutlined
style=
{
{
color
:
'
#ea580c
'
}
}
/>
<
span
style=
{
{
fontWeight
:
500
,
color
:
'
#9a3412
'
,
fontSize
:
12
}
}
>
{
String
(
data
.
report_name
||
data
.
name
||
'
化验报告
'
)
}
</
span
>
{
data
.
abnormal
?
<
Tag
color=
"red"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
}
}
>
异常
</
Tag
>
:
null
}
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#c2410c
'
,
display
:
'
flex
'
,
gap
:
12
,
flexWrap
:
'
wrap
'
}
}
>
{
data
.
report_date
?
<
span
>
日期:
{
String
(
data
.
report_date
).
slice
(
0
,
10
)
}
</
span
>
:
null
}
{
data
.
hospital
?
<
span
>
机构:
{
String
(
data
.
hospital
)
}
</
span
>
:
null
}
</
div
>
</
div
>
);
// ── 主组件 ──
const
ToolResultCard
:
React
.
FC
<
ToolResultCardProps
>
=
({
toolName
,
success
,
result
})
=>
{
...
...
@@ -275,14 +436,16 @@ const ToolResultCard: React.FC<ToolResultCardProps> = ({ toolName, success, resu
// 按工具类型定制渲染
switch
(
toolName
)
{
case
'
query_drug
'
:
{
case
'
query_drug
'
:
case
'
search_medicine_catalog
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
map
((
d
,
i
)
=>
<
DrugCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
DrugCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
recommend_department
'
:
{
case
'
recommend_department
'
:
case
'
query_department_list
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
map
((
d
,
i
)
=>
<
DepartmentCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
...
...
@@ -298,6 +461,56 @@ const ToolResultCard: React.FC<ToolResultCardProps> = ({ toolName, success, resu
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
NavigateCard
data=
{
data
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
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
ConsultationCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
ConsultationCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_prescription_list
'
:
case
'
query_prescription_detail
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
PrescriptionCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
PrescriptionCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_health_metrics
'
:
case
'
record_health_metric
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
HealthMetricCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
HealthMetricCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_lab_reports
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
LabReportCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
LabReportCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_order_list
'
:
case
'
query_order_detail
'
:
{
if
(
Array
.
isArray
(
data
))
{
return
<>
{
(
data
as
Record
<
string
,
unknown
>
[]).
slice
(
0
,
5
).
map
((
d
,
i
)
=>
<
OrderCard
key=
{
i
}
data=
{
d
}
/>)
}
</>;
}
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
OrderCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_income_stats
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
IncomeCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
case
'
query_dashboard_stats
'
:
{
if
(
data
&&
!
Array
.
isArray
(
data
))
return
<
DashboardCard
data=
{
data
as
Record
<
string
,
unknown
>
}
/>;
break
;
}
}
// 数组数据 → 表格
...
...
web/src/components/GlobalAIFloat/types.ts
View file @
da795257
...
...
@@ -83,21 +83,30 @@ export const QUICK_ITEMS: Record<WidgetRole, QuickItem[]> = {
{
label
:
'
预问诊
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
pre_consult
'
,
pageName
:
'
预问诊
'
,
operation
:
'
view_list
'
,
route
:
'
/patient/pre-consult
'
}
},
{
label
:
'
找医生
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
find_doctor
'
,
pageName
:
'
找医生
'
,
operation
:
'
view_list
'
,
route
:
'
/patient/doctors
'
}
},
{
label
:
'
我的问诊
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
my_consultations
'
,
pageName
:
'
我的问诊
'
,
operation
:
'
view_list
'
,
route
:
'
/patient/consult
'
}
},
{
label
:
'
头痛
'
,
type
:
'
text
'
},
{
label
:
'
发热
'
,
type
:
'
text
'
},
{
label
:
'
咳嗽
'
,
type
:
'
text
'
},
{
label
:
'
我的处方
'
,
type
:
'
text
'
},
{
label
:
'
我的订单
'
,
type
:
'
text
'
},
{
label
:
'
健康指标
'
,
type
:
'
text
'
},
{
label
:
'
化验报告
'
,
type
:
'
text
'
},
{
label
:
'
慢病续方
'
,
type
:
'
text
'
},
],
doctor
:
[
{
label
:
'
等候队列
'
,
type
:
'
text
'
},
{
label
:
'
今日问诊
'
,
type
:
'
text
'
},
{
label
:
'
鉴别诊断建议
'
,
type
:
'
text
'
},
{
label
:
'
用药方案推荐
'
,
type
:
'
text
'
},
{
label
:
'
检查项目建议
'
,
type
:
'
text
'
},
{
label
:
'
我的排班
'
,
type
:
'
text
'
},
{
label
:
'
收入统计
'
,
type
:
'
text
'
},
{
label
:
'
续方审批
'
,
type
:
'
text
'
},
],
admin
:
[
{
label
:
'
运营概览
'
,
type
:
'
text
'
},
{
label
:
'
趋势分析
'
,
type
:
'
text
'
},
{
label
:
'
注册医生
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
doctor_management
'
,
pageName
:
'
医生管理
'
,
operation
:
'
open_add
'
,
route
:
'
/admin/doctors?action=add
'
}
},
{
label
:
'
添加科室
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
department_management
'
,
pageName
:
'
科室管理
'
,
operation
:
'
open_add
'
,
route
:
'
/admin/departments?action=add
'
}
},
{
label
:
'
患者列表
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
patient_management
'
,
pageName
:
'
患者管理
'
,
operation
:
'
view_list
'
,
route
:
'
/admin/patients
'
}
},
{
label
:
'
医生列表
'
,
type
:
'
embed
'
,
embed
:
{
pageCode
:
'
doctor_management
'
,
pageName
:
'
医生管理
'
,
operation
:
'
view_list
'
,
route
:
'
/admin/doctors
'
}
},
{
label
:
'
查看运营数据
'
,
type
:
'
text
'
},
{
label
:
'
用户列表
'
,
type
:
'
text
'
},
{
label
:
'
订单查询
'
,
type
:
'
text
'
},
{
label
:
'
系统日志
'
,
type
:
'
text
'
},
{
label
:
'
管理Agent
'
,
type
:
'
text
'
},
],
};
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