247 lines
6.1 KiB
TypeScript
247 lines
6.1 KiB
TypeScript
/**
|
||
* 对话状态常量
|
||
*/
|
||
export const ConversationStatus = {
|
||
ACTIVE: 'ACTIVE',
|
||
ENDED: 'ENDED',
|
||
ARCHIVED: 'ARCHIVED',
|
||
} as const;
|
||
|
||
export type ConversationStatusType =
|
||
(typeof ConversationStatus)[keyof typeof ConversationStatus];
|
||
|
||
/**
|
||
* 咨询阶段常量
|
||
*/
|
||
export const ConsultingStage = {
|
||
GREETING: 'greeting',
|
||
NEEDS_DISCOVERY: 'needs_discovery',
|
||
INFO_COLLECTION: 'info_collection',
|
||
ASSESSMENT: 'assessment',
|
||
RECOMMENDATION: 'recommendation',
|
||
OBJECTION_HANDLING: 'objection_handling',
|
||
CONVERSION: 'conversion',
|
||
HANDOFF: 'handoff',
|
||
} as const;
|
||
|
||
export type ConsultingStageType =
|
||
(typeof ConsultingStage)[keyof typeof ConsultingStage];
|
||
|
||
/**
|
||
* 咨询状态结构(存储为JSON)
|
||
*/
|
||
export interface ConsultingStateJson {
|
||
strategyId: string;
|
||
currentStageId: string;
|
||
stageTurnCount: number;
|
||
collectedInfo: Record<string, unknown>;
|
||
assessmentResult?: {
|
||
recommendedPrograms: string[];
|
||
suitabilityScore: number;
|
||
highlights: string[];
|
||
concerns: string[];
|
||
};
|
||
conversionPath?: string;
|
||
stageHistory: Array<{
|
||
stageId: string;
|
||
enteredAt: string;
|
||
exitedAt?: string;
|
||
turnsInStage: number;
|
||
}>;
|
||
}
|
||
|
||
/**
|
||
* Device info structure
|
||
*/
|
||
export interface DeviceInfo {
|
||
ip?: string;
|
||
userAgent?: string;
|
||
fingerprint?: string;
|
||
region?: string;
|
||
}
|
||
|
||
/**
|
||
* Conversation Domain Entity
|
||
*/
|
||
export class ConversationEntity {
|
||
readonly id: string;
|
||
userId: string;
|
||
status: ConversationStatusType;
|
||
title: string | null;
|
||
summary: string | null;
|
||
category: string | null;
|
||
messageCount: number;
|
||
userMessageCount: number;
|
||
assistantMessageCount: number;
|
||
totalInputTokens: number;
|
||
totalOutputTokens: number;
|
||
rating: number | null;
|
||
feedback: string | null;
|
||
hasConverted: boolean;
|
||
consultingStage: ConsultingStageType | null;
|
||
consultingState: ConsultingStateJson | null;
|
||
collectedInfo: Record<string, unknown> | null;
|
||
recommendedPrograms: string[] | null;
|
||
conversionPath: string | null;
|
||
deviceInfo: DeviceInfo | null;
|
||
readonly createdAt: Date;
|
||
updatedAt: Date;
|
||
endedAt: Date | null;
|
||
|
||
private constructor(props: {
|
||
id: string;
|
||
userId: string;
|
||
status: ConversationStatusType;
|
||
title: string | null;
|
||
summary: string | null;
|
||
category: string | null;
|
||
messageCount: number;
|
||
userMessageCount: number;
|
||
assistantMessageCount: number;
|
||
totalInputTokens: number;
|
||
totalOutputTokens: number;
|
||
rating: number | null;
|
||
feedback: string | null;
|
||
hasConverted: boolean;
|
||
consultingStage: ConsultingStageType | null;
|
||
consultingState: ConsultingStateJson | null;
|
||
collectedInfo: Record<string, unknown> | null;
|
||
recommendedPrograms: string[] | null;
|
||
conversionPath: string | null;
|
||
deviceInfo: DeviceInfo | null;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
endedAt: Date | null;
|
||
}) {
|
||
Object.assign(this, props);
|
||
}
|
||
|
||
static create(props: {
|
||
id: string;
|
||
userId: string;
|
||
title?: string;
|
||
category?: string;
|
||
deviceInfo?: DeviceInfo;
|
||
}): ConversationEntity {
|
||
const now = new Date();
|
||
return new ConversationEntity({
|
||
id: props.id,
|
||
userId: props.userId,
|
||
status: ConversationStatus.ACTIVE,
|
||
title: props.title || null,
|
||
summary: null,
|
||
category: props.category || null,
|
||
messageCount: 0,
|
||
userMessageCount: 0,
|
||
assistantMessageCount: 0,
|
||
totalInputTokens: 0,
|
||
totalOutputTokens: 0,
|
||
rating: null,
|
||
feedback: null,
|
||
hasConverted: false,
|
||
consultingStage: ConsultingStage.GREETING,
|
||
consultingState: null,
|
||
collectedInfo: null,
|
||
recommendedPrograms: null,
|
||
conversionPath: null,
|
||
deviceInfo: props.deviceInfo || null,
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
endedAt: null,
|
||
});
|
||
}
|
||
|
||
static fromPersistence(props: {
|
||
id: string;
|
||
userId: string;
|
||
status: ConversationStatusType;
|
||
title: string | null;
|
||
summary: string | null;
|
||
category: string | null;
|
||
messageCount: number;
|
||
userMessageCount: number;
|
||
assistantMessageCount: number;
|
||
totalInputTokens: number;
|
||
totalOutputTokens: number;
|
||
rating: number | null;
|
||
feedback: string | null;
|
||
hasConverted: boolean;
|
||
consultingStage: ConsultingStageType | null;
|
||
consultingState: ConsultingStateJson | null;
|
||
collectedInfo: Record<string, unknown> | null;
|
||
recommendedPrograms: string[] | null;
|
||
conversionPath: string | null;
|
||
deviceInfo: DeviceInfo | null;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
endedAt: Date | null;
|
||
}): ConversationEntity {
|
||
return new ConversationEntity(props);
|
||
}
|
||
|
||
incrementMessageCount(role: 'user' | 'assistant'): void {
|
||
this.messageCount++;
|
||
if (role === 'user') {
|
||
this.userMessageCount++;
|
||
} else {
|
||
this.assistantMessageCount++;
|
||
}
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
addTokens(inputTokens: number, outputTokens: number): void {
|
||
this.totalInputTokens += inputTokens;
|
||
this.totalOutputTokens += outputTokens;
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
updateConsultingStage(stage: ConsultingStageType): void {
|
||
this.consultingStage = stage;
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
updateConsultingState(state: ConsultingStateJson): void {
|
||
this.consultingState = state;
|
||
this.collectedInfo = state.collectedInfo;
|
||
if (state.assessmentResult?.recommendedPrograms) {
|
||
this.recommendedPrograms = state.assessmentResult.recommendedPrograms;
|
||
}
|
||
if (state.conversionPath) {
|
||
this.conversionPath = state.conversionPath;
|
||
}
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
setRating(rating: number, feedback?: string): void {
|
||
this.rating = rating;
|
||
if (feedback) {
|
||
this.feedback = feedback;
|
||
}
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
markAsConverted(): void {
|
||
this.hasConverted = true;
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
end(): void {
|
||
this.status = ConversationStatus.ENDED;
|
||
this.endedAt = new Date();
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
archive(): void {
|
||
this.status = ConversationStatus.ARCHIVED;
|
||
this.updatedAt = new Date();
|
||
}
|
||
|
||
isActive(): boolean {
|
||
return this.status === ConversationStatus.ACTIVE;
|
||
}
|
||
|
||
isEnded(): boolean {
|
||
return this.status === ConversationStatus.ENDED;
|
||
}
|
||
}
|