import { DIMENSIONS, type Dimension, type QueryPlan, } from "../../../shared/assets/assistant.js"; import type { Vehicle } from "../../types.js"; import { toRow } from "./model.js"; // Kept as an input/context adapter for existing callers. Natural language is no longer // reduced to a closed vocabulary: the conversation planner needs the actual question. export type SafeInput = { message: string; tokens: string[]; entities: { token: string; dimension: Dimension; value: string }[]; catalog: Partial>; demand: string; unknown: boolean; sensitive: boolean; }; export function projectQuestion( message: string, vehicles: Vehicle[], previous?: QueryPlan, ): SafeInput { const catalog: SafeInput["catalog"] = {}; for (const dimension of DIMENSIONS) { const values = [ ...new Set(vehicles.map((v) => toRow(v)[dimension]).filter(Boolean)), ]; // Prefer mentioned/current values if a large catalog must be truncated. values.sort( (a, b) => Number( message.includes(b) || previous?.filters[dimension]?.includes(b), ) - Number( message.includes(a) || previous?.filters[dimension]?.includes(a), ), ); catalog[dimension] = values.slice(0, 150); } const entities: SafeInput["entities"] = []; for (const dimension of DIMENSIONS) for (const value of catalog[dimension] || []) { if ( message.includes(value) || (dimension === "city" && value.endsWith("市") && message.includes(value.slice(0, -1))) || previous?.filters[dimension]?.includes(value) ) { entities.push({ token: `E${entities.length + 1}`, dimension, value }); } } const mentioned = entities.filter( (e) => message.includes(e.value) || (e.dimension === "city" && e.value.endsWith("市") && message.includes(e.value.slice(0, -1))), ); return { message, tokens: [ message, ...mentioned.map((e) => e.token), ...(message.match(/20\d\d-\d{2}-\d{2}/g) || []), ], entities, catalog, // Demand feedback records the request instead of silently dropping unknown words. demand: message.replace(/sk-[a-z0-9]{8,}/gi, "[凭据已隐藏]").slice(0, 500), unknown: false, sensitive: false, }; } export function externalContext(safe: SafeInput, previous?: QueryPlan) { return { message: safe.message, catalog: safe.catalog, previous: previous || null, }; } export function restoreEntities(plan: QueryPlan, safe: SafeInput): QueryPlan { return { ...plan, filters: Object.fromEntries( Object.entries(plan.filters).map(([key, values]) => [ key, values.map( (value) => safe.entities.find((e) => e.token === value && e.dimension === key) ?.value || value, ), ]), ), }; }