feat: 联调对话功能

This commit is contained in:
王天骄
2026-06-09 17:00:23 +08:00
parent 3414d0662c
commit 2192b855a1
77 changed files with 1082 additions and 487 deletions
+129 -27
View File
@@ -1,4 +1,11 @@
import { createTrainingSession, type PatientConfigPayload, type TrainingMode } from './session'
import {
FASTAPI_BASE_URL,
authHeaders,
createTrainingSession,
readError,
type PatientConfigPayload,
type TrainingMode
} from './session'
export type ScenarioRecommendation = {
id: string
@@ -11,6 +18,7 @@ export type ScenarioRecommendation = {
export type ScenarioOption = {
value: string
label: string
description?: string
}
export type ScenarioForm = {
@@ -27,6 +35,12 @@ export type ScenarioOptions = {
personalities: ScenarioOption[]
}
export type TrainingConfigRecommended = {
recommended: ScenarioForm
recommendedLabels: Record<ScenarioFormKey, string>
options: ScenarioOptions
}
export type ScenarioConfigPayload = ScenarioForm & {
caseId: string
caseNo: string
@@ -34,6 +48,61 @@ export type ScenarioConfigPayload = ScenarioForm & {
recommendationId?: string
}
type TrainingConfigApiKey = 'visit_environment' | 'age_group' | 'education_level' | 'personality'
const DEFAULT_CASE_ID = 1
type ApiEnvelope<T> = {
code: string
message: string
data: T
}
type ScenarioFormKey = keyof ScenarioForm
type TrainingConfigRecommendedResponse = {
case_id: number
recommended: {
visit_environment: string
age_group: string
education_level: string
personality: string
}
recommended_labels: {
visit_environment: string
age_group: string
education_level: string
personality: string
}
options?: Partial<Record<TrainingConfigApiKey, ScenarioOption[] | Record<string, string>>>
}
export const DEFAULT_SCENARIO_OPTIONS: ScenarioOptions = {
environments: [
{ value: 'outpatient', label: '门诊' },
{ value: 'emergency', label: '急诊' },
{ value: 'ward', label: '病房' }
],
ageGroups: [
{ value: 'child', label: '儿童' },
{ value: 'youth', label: '青年' },
{ value: 'middle_aged', label: '中年' },
{ value: 'elderly', label: '老年' }
],
educations: [
{ value: 'primary_or_below', label: '小学及以下' },
{ value: 'secondary', label: '中等教育' },
{ value: 'higher', label: '高等教育' }
],
personalities: [
{ value: 'calm', label: '平和' },
{ value: 'anxious', label: '焦虑' },
{ value: 'impatient', label: '急躁' },
{ value: 'cooperative', label: '配合' },
{ value: 'suspicious', label: '多疑' }
]
}
export function fetchScenarioOptions() {
return Promise.resolve({
recommendations: [
@@ -62,37 +131,70 @@ export function fetchScenarioOptions() {
}
}
] as ScenarioRecommendation[],
options: {
environments: [
{ value: 'outpatient', label: '门诊' },
{ value: 'emergency', label: '急诊' },
{ value: 'ward', label: '病房' }
],
ageGroups: [
{ value: 'child', label: '儿童' },
{ value: 'youth', label: '青年' },
{ value: 'middle_aged', label: '中年' },
{ value: 'elderly', label: '老年' }
],
educations: [
{ value: 'primary_or_below', label: '小学及以下' },
{ value: 'secondary', label: '中等教育' },
{ value: 'higher', label: '高等教育' }
],
personalities: [
{ value: 'calm', label: '平和' },
{ value: 'anxious', label: '焦虑' },
{ value: 'impatient', label: '急躁' },
{ value: 'cooperative', label: '配合' },
{ value: 'suspicious', label: '多疑' }
]
} as ScenarioOptions
options: DEFAULT_SCENARIO_OPTIONS
})
}
function normalizeApiOptions(
source: TrainingConfigRecommendedResponse['options'] | undefined,
key: keyof TrainingConfigRecommendedResponse['recommended'],
fallback: ScenarioOption[]
) {
const value = source?.[key]
if (Array.isArray(value)) return value
if (value && typeof value === 'object') {
return Object.entries(value).map(([optionValue, label]) => ({
value: optionValue,
label: String(label)
}))
}
return fallback
}
function normalizeTrainingConfig(payload: TrainingConfigRecommendedResponse): TrainingConfigRecommended {
return {
recommended: {
environment: payload.recommended.visit_environment,
ageGroup: payload.recommended.age_group,
education: payload.recommended.education_level,
personality: payload.recommended.personality
},
recommendedLabels: {
environment: payload.recommended_labels.visit_environment,
ageGroup: payload.recommended_labels.age_group,
education: payload.recommended_labels.education_level,
personality: payload.recommended_labels.personality
},
options: {
environments: normalizeApiOptions(payload.options, 'visit_environment', DEFAULT_SCENARIO_OPTIONS.environments),
ageGroups: normalizeApiOptions(payload.options, 'age_group', DEFAULT_SCENARIO_OPTIONS.ageGroups),
educations: normalizeApiOptions(payload.options, 'education_level', DEFAULT_SCENARIO_OPTIONS.educations),
personalities: normalizeApiOptions(payload.options, 'personality', DEFAULT_SCENARIO_OPTIONS.personalities)
}
}
}
export async function fetchTrainingConfigOptions(caseId: number) {
const response = await fetch(`${FASTAPI_BASE_URL}/training-config/options?case_id=${encodeURIComponent(caseId)}`, {
method: 'GET',
headers: authHeaders()
})
if (!response.ok) {
throw new Error(await readError(response))
}
const result = (await response.json()) as ApiEnvelope<TrainingConfigRecommendedResponse>
if (result.code !== 'OK' || !result.data?.recommended) {
throw new Error(result.message || '推荐配置加载失败')
}
return normalizeTrainingConfig(result.data)
}
export function createScenarioConfig(payload: ScenarioConfigPayload) {
return createTrainingSession({
case_id: 1,
case_id: DEFAULT_CASE_ID,
training_type: 'diagnosis_treatment',
mode: payload.mode,
score_type: 'percentage',