Commit 22d9e969 authored by yuguo's avatar yuguo

fix

parent 05686ff8
......@@ -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:*)"
]
}
}
......@@ -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
......
......@@ -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
......
......@@ -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: '20' });
const params = new URLSearchParams({ page: String(page), page_size: '10' });
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>,
......
......@@ -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: '您好,请详细描述一下您的不适情况。' },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment