diff --git a/Cargo.toml b/Cargo.toml index 8b42093..4a85b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ tokio = { version = "1", features = ["full"] } # Web axum = { version = "0.8", features = ["multipart"] } tower = "0.5" -tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs"] } +tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs", "set-header"] } # Database sea-orm = { version = "1.1", features = [ diff --git a/apps/miniprogram/src/pages/appointment/create/index.tsx b/apps/miniprogram/src/pages/appointment/create/index.tsx index 4635837..790c481 100644 --- a/apps/miniprogram/src/pages/appointment/create/index.tsx +++ b/apps/miniprogram/src/pages/appointment/create/index.tsx @@ -121,7 +121,7 @@ export default function AppointmentCreate() { appointment_date: appointmentDate, start_time: selectedSlot?.start_time || timeSlot, end_time: selectedSlot?.end_time || timeSlot, - reason: reason.trim() || undefined, + notes: reason.trim() || undefined, }); Taro.showToast({ title: '预约成功', icon: 'success' }); trackEvent('appointment_create', { doctor_id: selectedDoctor.id, date: appointmentDate }); diff --git a/apps/miniprogram/src/services/appointment.ts b/apps/miniprogram/src/services/appointment.ts index 48104a6..a1f0951 100644 --- a/apps/miniprogram/src/services/appointment.ts +++ b/apps/miniprogram/src/services/appointment.ts @@ -45,11 +45,11 @@ export async function getAppointment(id: string) { export async function createAppointment(data: { patient_id: string; doctor_id: string; - schedule_id?: string; appointment_date: string; start_time: string; end_time: string; - reason?: string; + notes?: string; + appointment_type?: string; }) { return api.post('/health/appointments', data); } diff --git a/apps/miniprogram/src/services/doctor/consultation.ts b/apps/miniprogram/src/services/doctor/consultation.ts index c43f250..76ca2bf 100644 --- a/apps/miniprogram/src/services/doctor/consultation.ts +++ b/apps/miniprogram/src/services/doctor/consultation.ts @@ -9,8 +9,8 @@ export interface ConsultationSession { doctor_id: string | null; consultation_type: string; status: string; - subject: string | null; - last_message: string | null; + subject?: string | null; + last_message?: string | null; last_message_at: string | null; unread_count_doctor?: number; created_at: string; diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e64c434..cbaf68c 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -45,6 +45,7 @@ const StatisticsDashboard = lazy(() => import('./pages/health/StatisticsDashboar const AiPromptList = lazy(() => import('./pages/health/AiPromptList')); const AiAnalysisList = lazy(() => import('./pages/health/AiAnalysisList')); const AiUsageDashboard = lazy(() => import('./pages/health/AiUsageDashboard')); +const AiConfigPage = lazy(() => import('./pages/health/AiConfigPage')); const AlertList = lazy(() => import('./pages/health/AlertList')); const AlertDashboard = lazy(() => import('./pages/health/AlertDashboard')); const AlertRuleList = lazy(() => import('./pages/health/AlertRuleList')); @@ -254,7 +255,7 @@ export default function App() { "/health/follow-up-records", "/health/consultations", "/health/points-rules", "/health/points-products", "/health/points-orders", "/health/offline-events", "/health/ai-prompts", "/health/ai-analysis", - "/health/ai-usage", "/health/alerts", "/health/alert-dashboard", + "/health/ai-usage", "/health/ai-config", "/health/alerts", "/health/alert-dashboard", "/health/alert-rules", "/health/devices", "/health/realtime-monitor", "/health/oauth-clients", "/health/dialysis", "/health/action-inbox", "/health/follow-up-templates", "/health/care-plans", "/health/shifts", @@ -325,6 +326,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/apps/web/src/api/ai/config.ts b/apps/web/src/api/ai/config.ts new file mode 100644 index 0000000..99055e3 --- /dev/null +++ b/apps/web/src/api/ai/config.ts @@ -0,0 +1,35 @@ +import client from '../client'; + +export interface AiAgentConfig { + model: string; + temperature: number; + max_tokens: number; + max_iterations: number; + system_prompt: string; +} + +export interface AiAnalysisDefaults { + model: string; + temperature: number; + max_tokens: number; +} + +export interface AiConfig { + agent: AiAgentConfig; + analysis_defaults: AiAnalysisDefaults; +} + +export const aiConfigApi = { + get: async () => { + const resp = await client.get('/ai/config'); + return resp.data.data as AiConfig; + }, + getDefaults: async () => { + const resp = await client.get('/ai/config/defaults'); + return resp.data.data as AiConfig; + }, + update: async (config: AiConfig) => { + const resp = await client.put('/ai/config', { config }); + return resp.data.data as AiConfig; + }, +}; diff --git a/apps/web/src/pages/health/AiConfigPage.tsx b/apps/web/src/pages/health/AiConfigPage.tsx new file mode 100644 index 0000000..cc5419d --- /dev/null +++ b/apps/web/src/pages/health/AiConfigPage.tsx @@ -0,0 +1,280 @@ +import { useEffect, useState, useCallback } from 'react'; +import { + Card, + Form, + Input, + InputNumber, + Button, + Space, + message, + Divider, + Spin, + Tabs, +} from 'antd'; +import { SaveOutlined, UndoOutlined } from '@ant-design/icons'; +import { aiConfigApi, type AiConfig } from '../../api/ai/config'; +import { AuthButton } from '../../components/AuthButton'; +import { useThemeMode } from '../../hooks/useThemeMode'; + +const { TextArea } = Input; + +export default function AiConfigPage() { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [form] = Form.useForm(); + const isDark = useThemeMode(); + + const fetchConfig = useCallback(async () => { + setLoading(true); + try { + const data = await aiConfigApi.get(); + setConfig(data); + form.setFieldsValue({ + agentModel: data.agent.model, + agentTemperature: data.agent.temperature, + agentMaxTokens: data.agent.max_tokens, + agentMaxIterations: data.agent.max_iterations, + agentSystemPrompt: data.agent.system_prompt, + analysisModel: data.analysis_defaults.model, + analysisTemperature: data.analysis_defaults.temperature, + analysisMaxTokens: data.analysis_defaults.max_tokens, + }); + } catch { + message.error('加载 AI 配置失败'); + } finally { + setLoading(false); + } + }, [form]); + + useEffect(() => { + fetchConfig(); + }, [fetchConfig]); + + const handleSave = async () => { + try { + const values = await form.validateFields(); + setSaving(true); + + const updated: AiConfig = { + agent: { + model: values.agentModel, + temperature: values.agentTemperature, + max_tokens: values.agentMaxTokens, + max_iterations: values.agentMaxIterations, + system_prompt: values.agentSystemPrompt, + }, + analysis_defaults: { + model: values.analysisModel, + temperature: values.analysisTemperature, + max_tokens: values.analysisMaxTokens, + }, + }; + + const result = await aiConfigApi.update(updated); + setConfig(result); + message.success('AI 配置已保存'); + } catch (err: unknown) { + if (err && typeof err === 'object' && 'errorFields' in err) { + return; + } + message.error('保存 AI 配置失败'); + } finally { + setSaving(false); + } + }; + + const handleReset = async () => { + try { + const defaults = await aiConfigApi.getDefaults(); + form.setFieldsValue({ + agentModel: defaults.agent.model, + agentTemperature: defaults.agent.temperature, + agentMaxTokens: defaults.agent.max_tokens, + agentMaxIterations: defaults.agent.max_iterations, + agentSystemPrompt: defaults.agent.system_prompt, + analysisModel: defaults.analysis_defaults.model, + analysisTemperature: defaults.analysis_defaults.temperature, + analysisMaxTokens: defaults.analysis_defaults.max_tokens, + }); + message.info('已恢复为系统默认值(未保存)'); + } catch { + message.error('加载默认配置失败'); + } + }; + + if (loading && !config) { + return ( +
+ +
+ ); + } + + const cardStyle = isDark + ? { background: '#1f1f1f', borderColor: '#333' } + : {}; + + return ( +
+

AI 配置管理

+ +
+ + + + + + + + + + + + + + + + + + + + + + +