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
22d9e969
Commit
22d9e969
authored
Mar 05, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
05686ff8
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
54 additions
and
79 deletions
+54
-79
.claude/settings.local.json
.claude/settings.local.json
+2
-1
server/migrations/add_foreign_key_constraints.sql
server/migrations/add_foreign_key_constraints.sql
+1
-2
web/src/app/(main)/admin/ai-config/page.tsx
web/src/app/(main)/admin/ai-config/page.tsx
+1
-73
web/src/app/(main)/admin/ai-logs/page.tsx
web/src/app/(main)/admin/ai-logs/page.tsx
+41
-2
web/src/app/(main)/doctor/consult/ChatPanel.tsx
web/src/app/(main)/doctor/consult/ChatPanel.tsx
+9
-1
No files found.
.claude/settings.local.json
View file @
22d9e969
...
...
@@ -52,7 +52,8 @@
"Bash(wc:*)"
,
"Bash(rm:*)"
,
"Bash(
\"
E:
\\\\
internet-hospital
\\\\
后端审核报告.md
\"
:*)"
,
"Bash(
\"
E:
\\\\
internet-hospital
\\\\
server
\\\\
migrations
\\\\
add_foreign_key_constraints.sql
\"
:*)"
"Bash(
\"
E:
\\\\
internet-hospital
\\\\
server
\\\\
migrations
\\\\
add_foreign_key_constraints.sql
\"
:*)"
,
"Bash(sed:*)"
]
}
}
server/migrations/add_foreign_key_constraints.sql
View file @
22d9e969
...
...
@@ -25,7 +25,6 @@ ADD CONSTRAINT fk_doctor_reviews_user
FOREIGN
KEY
(
user_id
)
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
doctor_reviews
ADD
CONSTRAINT
fk_doctor_reviews_doctor
FOREIGN
KEY
(
doctor_id
)
REFERENCES
doctors
(
id
)
ON
DELETE
CASCADE
;
-- 3. Consultation相关外键约束
...
...
@@ -86,7 +85,7 @@ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER
TABLE
renewal_requests
ADD
CONSTRAINT
fk_renewal_requests_chronic
FOREIGN
KEY
(
chronic_
record_
id
)
REFERENCES
chronic_records
(
id
)
ON
DELETE
CASCADE
;
FOREIGN
KEY
(
chronic_id
)
REFERENCES
chronic_records
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
medication_reminders
ADD
CONSTRAINT
fk_medication_reminders_user
...
...
web/src/app/(main)/admin/ai-config/page.tsx
View file @
22d9e969
...
...
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
import
{
Card
,
Row
,
Col
,
Typography
,
Space
,
Tabs
,
Form
,
Input
,
Select
,
Switch
,
Button
,
Tag
,
Modal
,
Divider
,
Alert
,
InputNumber
,
Spin
,
message
,
DatePicker
,
Tooltip
,
message
,
}
from
'
antd
'
;
import
{
DrawerForm
,
ProFormText
,
ProFormSelect
,
ProFormTextArea
,
ProTable
}
from
'
@ant-design/pro-components
'
;
import
{
...
...
@@ -14,7 +14,6 @@ import {
SafetyOutlined
,
ExclamationCircleOutlined
,
HistoryOutlined
,
ReloadOutlined
,
}
from
'
@ant-design/icons
'
;
import
dayjs
from
'
dayjs
'
;
import
{
adminApi
}
from
'
@/api/admin
'
;
import
type
{
AIUsageStats
,
PromptTemplateData
}
from
'
@/api/admin
'
;
...
...
@@ -86,14 +85,6 @@ const AdminAIConfigPage: React.FC = () => {
const
[
templateModalType
,
setTemplateModalType
]
=
useState
<
'
add
'
|
'
edit
'
>
(
'
add
'
);
const
[
editingTemplate
,
setEditingTemplate
]
=
useState
<
PromptTemplateData
|
null
>
(
null
);
const
[
templateSaving
,
setTemplateSaving
]
=
useState
(
false
);
const
[
logSceneFilter
,
setLogSceneFilter
]
=
useState
<
string
|
undefined
>
(
undefined
);
const
[
logSuccessFilter
,
setLogSuccessFilter
]
=
useState
<
string
|
undefined
>
(
undefined
);
const
[
logDateRange
,
setLogDateRange
]
=
useState
<
[
dayjs
.
Dayjs
,
dayjs
.
Dayjs
]
|
null
>
(
null
);
const
[
logData
,
setLogData
]
=
useState
<
any
[]
>
([]);
const
[
logLoading
,
setLogLoading
]
=
useState
(
false
);
const
[
logTotal
,
setLogTotal
]
=
useState
(
0
);
const
[
logPage
,
setLogPage
]
=
useState
(
1
);
const
[
logPageSize
,
setLogPageSize
]
=
useState
(
20
);
useEffect
(()
=>
{
const
fetchConfig
=
async
()
=>
{
...
...
@@ -181,10 +172,6 @@ const AdminAIConfigPage: React.FC = () => {
fetchTemplates
();
},
[]);
const
fetchAILogs
=
async
()
=>
{
// TODO: 实现 AI 日志获取逻辑
console
.
log
(
'
获取 AI 日志
'
,
{
logSceneFilter
,
logSuccessFilter
,
logDateRange
});
};
const
handleAddTemplate
=
()
=>
{
setTemplateModalType
(
'
add
'
);
...
...
@@ -417,65 +404,6 @@ const AdminAIConfigPage: React.FC = () => {
</
Card
>
),
},
{
key
:
'
logs
'
,
label
:
<
span
><
HistoryOutlined
/>
模型日志
</
span
>,
children
:
(
<
Card
style=
{
{
borderRadius
:
12
,
border
:
'
1px solid #E0F2F1
'
}
}
>
<
div
style=
{
{
marginBottom
:
16
,
display
:
'
flex
'
,
justifyContent
:
'
space-between
'
,
alignItems
:
'
center
'
}
}
>
<
Space
wrap
>
<
Select
placeholder=
"场景筛选"
style=
{
{
width
:
150
}
}
allowClear
value=
{
logSceneFilter
}
onChange=
{
setLogSceneFilter
}
options=
{
Object
.
entries
(
sceneLabels
).
map
(([
k
,
v
])
=>
({
label
:
v
,
value
:
k
}))
}
/>
<
Select
placeholder=
"状态筛选"
style=
{
{
width
:
120
}
}
allowClear
value=
{
logSuccessFilter
}
onChange=
{
setLogSuccessFilter
}
options=
{
[{
label
:
'
成功
'
,
value
:
'
true
'
},
{
label
:
'
失败
'
,
value
:
'
false
'
}]
}
/>
<
DatePicker
.
RangePicker
value=
{
logDateRange
}
onChange=
{
(
dates
)
=>
setLogDateRange
(
dates
)
}
/>
<
Button
icon=
{
<
ReloadOutlined
/>
}
onClick=
{
fetchAILogs
}
>
刷新
</
Button
>
</
Space
>
</
div
>
<
ProTable
search=
{
false
}
columns=
{
[
{
title
:
'
ID
'
,
dataIndex
:
'
id
'
,
key
:
'
id
'
,
width
:
60
},
{
title
:
'
场景
'
,
dataIndex
:
'
scene
'
,
key
:
'
scene
'
,
width
:
130
,
render
:
(
v
:
string
)
=>
<
Tag
color=
"blue"
>
{
sceneLabels
[
v
]
||
v
}
</
Tag
>
},
{
title
:
'
模型
'
,
dataIndex
:
'
model
'
,
key
:
'
model
'
,
width
:
140
},
{
title
:
'
请求摘要
'
,
dataIndex
:
'
request_content
'
,
key
:
'
request_content
'
,
width
:
180
,
render
:
(
v
:
string
)
=>
<
Tooltip
title=
{
v
}
><
Text
ellipsis
style=
{
{
maxWidth
:
180
,
display
:
'
block
'
}
}
>
{
v
||
'
-
'
}
</
Text
></
Tooltip
>,
},
{
title
:
'
Tokens
'
,
key
:
'
tokens
'
,
width
:
100
,
render
:
(
_
:
any
,
record
:
AILogItem
)
=>
<
Text
type=
"secondary"
>
{
record
.
total_tokens
||
0
}
</
Text
>
},
{
title
:
'
耗时
'
,
dataIndex
:
'
response_time_ms
'
,
key
:
'
response_time_ms
'
,
width
:
80
,
render
:
(
v
:
number
)
=>
<
Text
type=
"secondary"
>
{
v
?
(
v
/
1000
).
toFixed
(
2
)
+
'
s
'
:
'
-
'
}
</
Text
>
},
{
title
:
'
状态
'
,
dataIndex
:
'
success
'
,
key
:
'
success
'
,
width
:
80
,
render
:
(
v
:
boolean
,
record
:
AILogItem
)
=>
(
<
Space
direction=
"vertical"
size=
{
0
}
>
<
Tag
color=
{
v
?
'
success
'
:
'
error
'
}
>
{
v
?
'
成功
'
:
'
失败
'
}
</
Tag
>
{
record
.
is_mock
&&
<
Tag
color=
"default"
>
模拟
</
Tag
>
}
</
Space
>
),
},
{
title
:
'
时间
'
,
dataIndex
:
'
created_at
'
,
key
:
'
created_at
'
,
width
:
160
,
render
:
(
v
:
string
)
=>
v
?
dayjs
(
v
).
format
(
'
YYYY-MM-DD HH:mm:ss
'
)
:
'
-
'
},
{
title
:
'
操作
'
,
key
:
'
action
'
,
width
:
80
,
render
:
(
_
:
any
,
record
:
AILogItem
)
=>
(
<
Button
type=
"link"
size=
"small"
onClick=
{
()
=>
{
setLogDetailItem
(
record
);
setLogDetailVisible
(
true
);
}
}
>
详情
</
Button
>
),
},
]
}
dataSource=
{
logData
}
rowKey=
"id"
loading=
{
logLoading
}
pagination=
{
{
current
:
logPage
,
pageSize
:
logPageSize
,
total
:
logTotal
,
showSizeChanger
:
true
,
showTotal
:
(
t
)
=>
`共 ${t} 条`
,
onChange
:
(
p
,
ps
)
=>
{
setLogPage
(
p
);
setLogPageSize
(
ps
);
},
}
}
/>
</
Card
>
),
},
]
}
/>
<
DrawerForm
...
...
web/src/app/(main)/admin/ai-logs/page.tsx
View file @
22d9e969
...
...
@@ -48,7 +48,7 @@ interface TraceDetail {
const
token
=
()
=>
localStorage
.
getItem
(
'
access_token
'
)
||
''
;
async
function
fetchAIStats
(
page
:
number
,
agentID
:
string
,
startDate
?:
string
,
endDate
?:
string
)
{
const
params
=
new
URLSearchParams
({
page
:
String
(
page
),
page_size
:
'
2
0
'
});
const
params
=
new
URLSearchParams
({
page
:
String
(
page
),
page_size
:
'
1
0
'
});
if
(
agentID
)
params
.
append
(
'
agent_id
'
,
agentID
);
if
(
startDate
)
params
.
append
(
'
start_date
'
,
startDate
);
if
(
endDate
)
params
.
append
(
'
end_date
'
,
endDate
);
...
...
@@ -56,7 +56,11 @@ async function fetchAIStats(page: number, agentID: string, startDate?: string, e
headers
:
{
Authorization
:
`Bearer
${
token
()}
`
},
});
const
data
=
await
res
.
json
();
return
data
.
data
??
{};
return
{
...
data
.
data
,
logs
:
data
.
data
?.
recent_logs
||
[],
total_calls
:
data
.
data
?.
logs_total
||
0
,
};
}
async
function
fetchTrace
(
traceID
:
string
)
{
...
...
@@ -341,6 +345,41 @@ export default function AILogsPage() {
</
div
>
),
},
{
key
:
'
ai-model-logs
'
,
label
:
<
Space
><
ThunderboltOutlined
/>
AI模型日志
</
Space
>,
children
:
(
<
div
style=
{
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
12
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
8
,
alignItems
:
'
center
'
}
}
>
<
Select
placeholder=
"场景筛选"
style=
{
{
width
:
150
}
}
allowClear
onChange=
{
setAiAgentFilter
}
options=
{
[
{
label
:
'
预问诊对话
'
,
value
:
'
pre_consult_chat
'
},
{
label
:
'
预问诊报告
'
,
value
:
'
pre_consult_analysis
'
},
{
label
:
'
鉴别诊断
'
,
value
:
'
consult_diagnosis
'
},
{
label
:
'
用药建议
'
,
value
:
'
consult_medication
'
},
{
label
:
'
处方审核
'
,
value
:
'
prescription_review
'
},
]
}
/>
<
RangePicker
defaultValue=
{
[
dayjs
(),
dayjs
()]
}
onChange=
{
(
dates
)
=>
{
setAiDateRange
([
dates
?.[
0
]?.
format
(
'
YYYY-MM-DD
'
)
||
''
,
dates
?.[
1
]?.
format
(
'
YYYY-MM-DD
'
)
||
''
]);
}
}
/>
</
div
>
<
Table
dataSource=
{
aiStats
?.
logs
||
[]
}
columns=
{
aiLogColumns
}
rowKey=
"id"
loading=
{
aiLoading
}
size=
"small"
pagination=
{
{
current
:
aiPage
,
pageSize
:
20
,
total
:
aiStats
?.
total_calls
||
0
,
showTotal
:
(
t
)
=>
`共 ${t} 条`
,
onChange
:
(
page
)
=>
setAiPage
(
page
),
}
}
/>
</
div
>
),
},
{
key
:
'
agent-stats
'
,
label
:
<
Space
><
FundOutlined
/>
统计分析
</
Space
>,
...
...
web/src/app/(main)/doctor/consult/ChatPanel.tsx
View file @
22d9e969
...
...
@@ -7,7 +7,7 @@ import {
UserOutlined
,
VideoCameraOutlined
,
SendOutlined
,
StopOutlined
,
FileTextOutlined
,
ThunderboltOutlined
,
SmileOutlined
,
PictureOutlined
,
PaperClipOutlined
,
SwapOutlined
,
ClockCircleOutlined
,
SwapOutlined
,
ClockCircleOutlined
,
RobotOutlined
,
}
from
'
@ant-design/icons
'
;
import
type
{
ConsultMessage
}
from
'
@/api/consult
'
;
import
{
consultApi
}
from
'
@/api/consult
'
;
...
...
@@ -22,6 +22,14 @@ import { MessageBubble } from './MessageBubble';
const
{
Text
}
=
Typography
;
const
{
TextArea
}
=
Input
;
const
CATEGORY_LABELS
:
Record
<
string
,
string
>
=
{
greeting
:
'
问候
'
,
inquiry
:
'
问诊
'
,
diagnosis
:
'
诊断
'
,
prescription
:
'
处方
'
,
closing
:
'
结束
'
,
};
// 系统级默认快捷回复(fallback)
const
DEFAULT_REPLIES
=
[
{
category
:
'
greeting
'
,
content
:
'
您好,请详细描述一下您的不适情况。
'
},
...
...
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