forked from zhouruizhe/gmTouringMiniApp
162 lines
5.7 KiB
TypeScript
162 lines
5.7 KiB
TypeScript
import { getPoiRepository } from '@/data/poi'
|
|
import {
|
|
adjustLocalPlan,
|
|
clonePlanResponse,
|
|
cloneTravelPreferences,
|
|
createLocalPlan,
|
|
createRecommendedPlan,
|
|
normalizePlanningRequest,
|
|
normalizeTravelPreferences,
|
|
type PlanningOrigin,
|
|
type PlanningRequest,
|
|
type PlanResponse,
|
|
type TravelPreferences,
|
|
TravelValidationError,
|
|
} from '@/domain/travel'
|
|
import { createRemoteRecommendations, isRemoteTravelAssistantEnabled } from './remote'
|
|
import { loadPlan, loadPlanningRequest, loadPreferences, savePlanningRequest, savePreferences } from './storage'
|
|
|
|
interface LocalSession {
|
|
plan: PlanResponse
|
|
preferences: TravelPreferences
|
|
request: PlanningRequest
|
|
planningOrigin: PlanningOrigin | null
|
|
}
|
|
|
|
const localSessions = new Map<string, LocalSession>()
|
|
|
|
function clonePlanningRequest(request: PlanningRequest, includeOrigin = true): PlanningRequest {
|
|
return {
|
|
mode: request.mode,
|
|
preferences: cloneTravelPreferences(request.preferences),
|
|
durationMinutes: request.durationMinutes,
|
|
selectedPoiIds: [...request.selectedPoiIds],
|
|
...(includeOrigin && request.origin ? { origin: { ...request.origin } } : {}),
|
|
}
|
|
}
|
|
|
|
function rememberLocalSession(plan: PlanResponse, request: PlanningRequest): void {
|
|
const repository = getPoiRepository()
|
|
localSessions.set(plan.conversationId, {
|
|
plan: clonePlanResponse(plan, repository),
|
|
preferences: cloneTravelPreferences(request.preferences),
|
|
request: clonePlanningRequest(request),
|
|
planningOrigin: request.origin ? { ...request.origin } : null,
|
|
})
|
|
}
|
|
|
|
function restoreLocalSession(conversationId: string): LocalSession | null {
|
|
const remembered = localSessions.get(conversationId)
|
|
if (remembered)
|
|
return remembered
|
|
|
|
const plan = loadPlan()
|
|
const preferences = loadPreferences()
|
|
if (!plan || !preferences || plan.conversationId !== conversationId)
|
|
return null
|
|
const storedRequest = loadPlanningRequest()
|
|
rememberLocalSession(plan, storedRequest ?? {
|
|
mode: plan.itinerary.planningMode,
|
|
preferences,
|
|
durationMinutes: plan.itinerary.requestedMinutes,
|
|
selectedPoiIds: plan.itinerary.planningMode === 'custom'
|
|
? plan.itinerary.items.map(item => item.placeId)
|
|
: [],
|
|
})
|
|
return localSessions.get(conversationId) ?? null
|
|
}
|
|
|
|
function normalizePlanningOrigin(origin?: PlanningOrigin): PlanningOrigin | null {
|
|
if (!origin
|
|
|| origin.coordinateSystem !== 'GCJ02'
|
|
|| !Number.isFinite(origin.longitude)
|
|
|| !Number.isFinite(origin.latitude)
|
|
|| origin.longitude < -180
|
|
|| origin.longitude > 180
|
|
|| origin.latitude < -90
|
|
|| origin.latitude > 90) {
|
|
return null
|
|
}
|
|
return { ...origin }
|
|
}
|
|
|
|
function isPlanningRequest(input: TravelPreferences | PlanningRequest): input is PlanningRequest {
|
|
return typeof input === 'object' && input !== null && 'mode' in input && 'preferences' in input
|
|
}
|
|
|
|
export function createPlan(input: PlanningRequest): Promise<PlanResponse>
|
|
export function createPlan(input: TravelPreferences, planningOrigin?: PlanningOrigin): Promise<PlanResponse>
|
|
export async function createPlan(
|
|
input: TravelPreferences | PlanningRequest,
|
|
planningOrigin?: PlanningOrigin,
|
|
): Promise<PlanResponse> {
|
|
const repository = getPoiRepository()
|
|
const request = normalizePlanningRequest(isPlanningRequest(input)
|
|
? input
|
|
: {
|
|
mode: 'quick',
|
|
preferences: normalizeTravelPreferences(input),
|
|
durationMinutes: input.duration === 'half_day' ? 240 : 480,
|
|
selectedPoiIds: [],
|
|
...(normalizePlanningOrigin(planningOrigin) ? { origin: normalizePlanningOrigin(planningOrigin)! } : {}),
|
|
}, repository)
|
|
if (isRemoteTravelAssistantEnabled()) {
|
|
const candidates = repository.getPublishedPois().map(poi => ({
|
|
id: poi.id,
|
|
name: poi.name,
|
|
categoryCode: poi.categoryCode,
|
|
tagCodes: poi.tagCodes,
|
|
summary: poi.summary,
|
|
recommendationIndex: poi.recommendationIndex,
|
|
}))
|
|
const remote = await createRemoteRecommendations(request, candidates)
|
|
const plan = createRecommendedPlan(
|
|
request,
|
|
remote.recommendations,
|
|
repository,
|
|
remote.assistantMessage,
|
|
remote.generationMode === 'real' ? 'remote_ai' : 'remote_mock',
|
|
)
|
|
rememberLocalSession(plan, request)
|
|
savePlanningRequest(request)
|
|
return clonePlanResponse(plan, repository)
|
|
}
|
|
|
|
const plan = createLocalPlan(request, repository)
|
|
rememberLocalSession(plan, request)
|
|
savePlanningRequest(request)
|
|
return clonePlanResponse(plan, repository)
|
|
}
|
|
|
|
export async function adjustPlan(conversationId: string, message: string): Promise<PlanResponse> {
|
|
const normalizedConversationId = conversationId.trim()
|
|
const normalizedMessage = message.trim()
|
|
if (!normalizedConversationId)
|
|
throw new TravelValidationError('会话 ID 不能为空')
|
|
if (!normalizedMessage)
|
|
throw new TravelValidationError('请填写需要调整的内容')
|
|
if (normalizedMessage.length > 500)
|
|
throw new TravelValidationError('调整内容不能超过 500 字')
|
|
|
|
const repository = getPoiRepository()
|
|
const session = restoreLocalSession(normalizedConversationId)
|
|
if (!session)
|
|
throw new Error('本机行程会话不存在,请重新生成行程')
|
|
const adjusted = adjustLocalPlan(
|
|
session.plan,
|
|
session.preferences,
|
|
normalizedMessage,
|
|
repository,
|
|
session.planningOrigin ?? undefined,
|
|
session.request,
|
|
)
|
|
if (session.plan.source !== 'local_poc') {
|
|
adjusted.plan.assistantMessage = '已在本机按你的补充要求重新核算路线;如需 AI 重新选点,请返回规划页重新生成。'
|
|
adjusted.plan.source = session.plan.source
|
|
}
|
|
rememberLocalSession(adjusted.plan, adjusted.request)
|
|
savePlanningRequest(adjusted.request)
|
|
savePreferences(adjusted.preferences)
|
|
return clonePlanResponse(adjusted.plan, repository)
|
|
}
|