ЮИ - Автопрогоны: доработать иконки и hover-состояния saved sessions
This commit is contained in:
@@ -1907,10 +1907,10 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||
|
||||
const hasLooseVatPayableBridge =
|
||||
/(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
|
||||
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:\u043d\u0430\u043c\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e)|\u043d\u0430\u043c\s+\u043d\u0430\u0434\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043d\u0430\u043c\s+\u043d\u0443\u0436\u043d\u043e\s+\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u043d\u0434\u0441\s+\u043a\s+\u0443\u043f\u043b\u0430\u0442\u0435)/iu.test(
|
||||
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:(?:\u043d\u0430\u043c|(?:\u043c\u044b\s+)?\u0434\u043e\u043b\u0436\u043d\u044b)\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e|\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0430\u0434\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0443\u0436\u043d\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043d\u0434\u0441\s+\u043a\s+\u0443\u043f\u043b\u0430\u0442\u0435)/iu.test(
|
||||
text
|
||||
) &&
|
||||
/(?:\u0437\u0430\s+(?:\d{4}|(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?)|\u043d\u0430\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?|\b[1-4]\s*(?:\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043a\u0432\.?)\b)/iu.test(
|
||||
/(?:\u0437\u0430\s+(?:\d{4}|(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?)|\u043d\u0430\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?|\u0432\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?|\b[1-4]\s*(?:\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043a\u0432\.?)\b)/iu.test(
|
||||
text
|
||||
);
|
||||
if (hasLooseVatPayableBridge) {
|
||||
|
||||
@@ -1056,6 +1056,99 @@ function normalizeCounterpartyName(value: string): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
function resolvePreferredCounterpartyReplyLabel(...values: Array<string | null | undefined>): string | undefined {
|
||||
const candidates = Array.from(
|
||||
new Set(
|
||||
values
|
||||
.map((value) => (typeof value === "string" ? value.trim() : ""))
|
||||
.filter((value) => value.length > 0)
|
||||
)
|
||||
);
|
||||
if (candidates.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const genericPattern = /^(?:группа|контрагент|компания|организация)$/iu;
|
||||
candidates.sort((left, right) => {
|
||||
const leftNormalized = normalizeCounterpartyName(left);
|
||||
const rightNormalized = normalizeCounterpartyName(right);
|
||||
const leftGeneric = genericPattern.test(leftNormalized);
|
||||
const rightGeneric = genericPattern.test(rightNormalized);
|
||||
if (leftGeneric !== rightGeneric) {
|
||||
return leftGeneric ? 1 : -1;
|
||||
}
|
||||
if (leftNormalized && rightNormalized) {
|
||||
if (leftNormalized.includes(rightNormalized) && leftNormalized !== rightNormalized) {
|
||||
return -1;
|
||||
}
|
||||
if (rightNormalized.includes(leftNormalized) && leftNormalized !== rightNormalized) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return right.length - left.length;
|
||||
});
|
||||
return candidates[0] || undefined;
|
||||
}
|
||||
|
||||
function repairCounterpartyReplyLabel(
|
||||
replyText: string,
|
||||
intent: string,
|
||||
extractedFilters: AddressFilterSet | null | undefined
|
||||
): string {
|
||||
if (intent !== "list_documents_by_counterparty" && intent !== "bank_operations_by_counterparty") {
|
||||
return replyText;
|
||||
}
|
||||
const requestedCounterparty =
|
||||
extractedFilters && typeof extractedFilters.counterparty === "string"
|
||||
? extractedFilters.counterparty.trim()
|
||||
: "";
|
||||
if (!requestedCounterparty) {
|
||||
return replyText;
|
||||
}
|
||||
const requestedNormalized = normalizeCounterpartyName(requestedCounterparty);
|
||||
if (!requestedNormalized) {
|
||||
return replyText;
|
||||
}
|
||||
let repaired = String(replyText ?? "");
|
||||
repaired = repaired.replace(/Контрагент:\s*([^\n.]+?)(?=\s+Найдено\s+документов:)/gu, (_match, label) => {
|
||||
const normalizedLabel = normalizeCounterpartyName(String(label ?? ""));
|
||||
if (!normalizedLabel || normalizedLabel === requestedNormalized) {
|
||||
return `Контрагент: ${label}`;
|
||||
}
|
||||
const labelLooksGeneric = /^(?:группа|контрагент|компания|организация)$/iu.test(String(label ?? "").trim());
|
||||
const requestedContainsLabel = requestedNormalized.includes(normalizedLabel);
|
||||
return labelLooksGeneric || requestedContainsLabel
|
||||
? `Контрагент: ${requestedCounterparty}`
|
||||
: `Контрагент: ${label}`;
|
||||
});
|
||||
repaired = repaired.replace(/Контрагент:\s*([^\n.]+)([.\n])/gu, (match, label, suffix) => {
|
||||
const normalizedLabel = normalizeCounterpartyName(String(label ?? ""));
|
||||
if (!normalizedLabel || normalizedLabel === requestedNormalized) {
|
||||
return match;
|
||||
}
|
||||
const labelLooksGeneric = /^(?:группа|контрагент|компания|организация)$/iu.test(String(label ?? "").trim());
|
||||
const requestedContainsLabel = requestedNormalized.includes(normalizedLabel);
|
||||
const shouldReplace = labelLooksGeneric || requestedContainsLabel;
|
||||
return shouldReplace ? `Контрагент: ${requestedCounterparty}${suffix}` : match;
|
||||
});
|
||||
repaired = repaired.replace(/Контрагент:\s*([^\n]+?)\.\.([^\n]*)(?=\n|$)/gu, (_match, left, right) => {
|
||||
const leftPart = String(left ?? "").trim().replace(/[.]+$/u, "");
|
||||
const rightPart = String(right ?? "").trim().replace(/^[.]+/u, "");
|
||||
return rightPart.length > 0 ? `Контрагент: ${leftPart}. ${rightPart}` : `Контрагент: ${leftPart}.`;
|
||||
});
|
||||
repaired = repaired.replace(/Контрагент:\s*([^\n.]+?)\.\s+Найдено\s+документов:/gu, (_match, label) => {
|
||||
const normalizedLabel = normalizeCounterpartyName(String(label ?? ""));
|
||||
if (!normalizedLabel || normalizedLabel === requestedNormalized) {
|
||||
return `Контрагент: ${label}. Найдено документов:`;
|
||||
}
|
||||
const labelLooksGeneric = /^(?:группа|контрагент|компания|организация)$/iu.test(String(label ?? "").trim());
|
||||
const requestedContainsLabel = requestedNormalized.includes(normalizedLabel);
|
||||
return labelLooksGeneric || requestedContainsLabel
|
||||
? `Контрагент: ${requestedCounterparty}. Найдено документов:`
|
||||
: `Контрагент: ${label}. Найдено документов:`;
|
||||
});
|
||||
return repaired;
|
||||
}
|
||||
|
||||
function hasCounterpartyShipmentItemFlowSignal(userMessage: string): boolean {
|
||||
const text = normalizeSearchText(String(userMessage ?? ""));
|
||||
if (!text) {
|
||||
@@ -3497,12 +3590,13 @@ export class AddressQueryService {
|
||||
) =>
|
||||
composeOptionsFromFilters(filterSet, {
|
||||
...options,
|
||||
counterpartyHint:
|
||||
typeof options.counterpartyHint === "string"
|
||||
? options.counterpartyHint
|
||||
: anchor?.anchor_type === "counterparty"
|
||||
? anchor.anchor_value_resolved ?? anchor.anchor_value_raw ?? undefined
|
||||
: undefined,
|
||||
counterpartyHint: resolvePreferredCounterpartyReplyLabel(
|
||||
typeof options.counterpartyHint === "string" ? options.counterpartyHint : undefined,
|
||||
typeof filterSet.counterparty === "string" ? filterSet.counterparty : undefined,
|
||||
anchor?.anchor_type === "counterparty"
|
||||
? anchor.anchor_value_resolved ?? anchor.anchor_value_raw ?? undefined
|
||||
: undefined
|
||||
),
|
||||
accountHint:
|
||||
typeof options.accountHint === "string"
|
||||
? options.accountHint
|
||||
@@ -4127,7 +4221,7 @@ export class AddressQueryService {
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: replyText,
|
||||
reply_text: repairCounterpartyReplyLabel(replyText, intent.intent, filters.extracted_filters),
|
||||
reply_type: inferReplyType(responseType),
|
||||
response_type: responseType,
|
||||
debug: debugPayload
|
||||
@@ -4294,7 +4388,11 @@ export class AddressQueryService {
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: input.replyText,
|
||||
reply_text: repairCounterpartyReplyLabel(
|
||||
input.replyText,
|
||||
intent.intent,
|
||||
(input.extractedFilters ?? filters.extracted_filters) as AddressFilterSet
|
||||
),
|
||||
reply_type: inferReplyType(input.responseType),
|
||||
response_type: input.responseType,
|
||||
debug: debugPayload
|
||||
|
||||
@@ -100,6 +100,37 @@ function uniqueStrings(values: string[]): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCounterpartyDisplayLabel(value: string | null | undefined): string | null {
|
||||
const source = String(value ?? "").trim().replace(/[.]+$/u, "");
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
return source.replace(/\s+/gu, " ");
|
||||
}
|
||||
|
||||
function isGenericCounterpartyDisplayLabel(value: string | null | undefined): boolean {
|
||||
const normalized = normalizeCounterpartyDisplayLabel(value);
|
||||
if (!normalized) {
|
||||
return true;
|
||||
}
|
||||
return /^(?:группа|контрагент|компания|организация)$/iu.test(normalized);
|
||||
}
|
||||
|
||||
function resolvePreferredCounterpartyDisplayLabel(
|
||||
requestedHint: string | null | undefined,
|
||||
rowLabels: string[]
|
||||
): string | null {
|
||||
const requested = normalizeCounterpartyDisplayLabel(requestedHint);
|
||||
const resolvedFromRows =
|
||||
rowLabels.length === 1 ? normalizeCounterpartyDisplayLabel(rowLabels[0]) : null;
|
||||
if (requested && resolvedFromRows) {
|
||||
if (isGenericCounterpartyDisplayLabel(resolvedFromRows) || requested.includes(resolvedFromRows)) {
|
||||
return requested;
|
||||
}
|
||||
}
|
||||
return requested ?? resolvedFromRows;
|
||||
}
|
||||
|
||||
function formatTopRows(rows: ComposeStageRow[], limit = 6): string[] {
|
||||
return rows.slice(0, limit).map((row, index) => {
|
||||
const period = row.period ?? "дата не указана";
|
||||
@@ -4181,18 +4212,15 @@ function composeFactualReplyBody(
|
||||
}
|
||||
|
||||
if (intent === "list_documents_by_counterparty") {
|
||||
const resolvedCounterparty =
|
||||
(typeof options.counterpartyHint === "string" && options.counterpartyHint.trim().length > 0
|
||||
? options.counterpartyHint.trim()
|
||||
: null) ??
|
||||
(() => {
|
||||
const counterparties = uniqueStrings(
|
||||
rows
|
||||
.map((row) => extractCounterpartyName(row))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
);
|
||||
return counterparties.length === 1 ? counterparties[0] : null;
|
||||
})();
|
||||
const rowCounterparties = uniqueStrings(
|
||||
rows
|
||||
.map((row) => extractCounterpartyName(row))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
);
|
||||
const resolvedCounterparty = resolvePreferredCounterpartyDisplayLabel(
|
||||
typeof options.counterpartyHint === "string" ? options.counterpartyHint : null,
|
||||
rowCounterparties
|
||||
);
|
||||
const counterpartyLabel =
|
||||
typeof resolvedCounterparty === "string" && resolvedCounterparty.endsWith(".")
|
||||
? resolvedCounterparty
|
||||
|
||||
@@ -567,6 +567,9 @@ function isLikelyMojibakeToken(value: string): boolean {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
if (/^[А-ЯЁ]{2,5}$/u.test(token)) {
|
||||
return false;
|
||||
}
|
||||
if (MOJIBAKE_SINGLE_MARKER_PATTERN.test(token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export interface AssistantLivingChatRuntimeInput {
|
||||
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
||||
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
||||
buildAssistantOperationalBoundaryReply: () => string;
|
||||
buildAssistantCapabilityContractReply: () => string;
|
||||
buildAssistantCapabilityContractReply: (userMessage?: string) => string;
|
||||
}
|
||||
|
||||
export interface AssistantLivingChatRuntimeOutput {
|
||||
@@ -336,7 +336,7 @@ export async function runAssistantLivingChatRuntime(
|
||||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||||
livingChatSource = "deterministic_memory_recap_contract";
|
||||
} else if (capabilityMetaQuery) {
|
||||
chatText = input.buildAssistantCapabilityContractReply();
|
||||
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
||||
livingChatSource = "deterministic_capability_contract";
|
||||
} else {
|
||||
chatText = await input.executeLlmChat();
|
||||
|
||||
@@ -304,6 +304,12 @@ export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyD
|
||||
if (hasScopeMetaSignal) {
|
||||
return true;
|
||||
}
|
||||
const hasExplicitDeltaCapabilityMetaSignal =
|
||||
/(?:мож(?:ешь|ете)|уме(?:ешь|ете)).*(?:считать|рассчитывать|посчитать).*(?:дельт|delta|маржинальн|margin|рентабельн)/iu.test(raw) ||
|
||||
/(?:мож(?:ешь|ете)|уме(?:ешь|ете)).*(?:считать|рассчитывать|посчитать).*(?:дельт|delta|маржинальн|margin|рентабельн)/iu.test(repaired);
|
||||
if (hasExplicitDeltaCapabilityMetaSignal) {
|
||||
return true;
|
||||
}
|
||||
const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) ||
|
||||
hasAssistantCapabilityQuestionSignal(repaired) ||
|
||||
hasOperationalAdminActionRequestSignal(raw) ||
|
||||
|
||||
@@ -3649,13 +3649,20 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
function resolveAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) {
|
||||
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
|
||||
const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? "");
|
||||
const repairedRawMessageForGate = repairAddressMojibake(rawMessageForGate);
|
||||
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawMessageForGate) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(repairedRawMessageForGate) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(repairedInputMessage);
|
||||
const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawMessageForGate) ||
|
||||
const rawCapabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawMessageForGate) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawMessageForGate);
|
||||
const capabilityMetaQuery = rawCapabilityMetaQuery ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedInputMessage);
|
||||
const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
|
||||
const rawDataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
|
||||
hasDataRetrievalRequestSignal(repairedRawMessageForGate);
|
||||
const dataRetrievalSignal = rawDataRetrievalSignal ||
|
||||
hasDataRetrievalRequestSignal(repairedInputMessage);
|
||||
if (dataScopeMetaQuery || (capabilityMetaQuery && !dataRetrievalSignal)) {
|
||||
const rawCapabilityMetaOverride = rawCapabilityMetaQuery && !rawDataRetrievalSignal;
|
||||
if (dataScopeMetaQuery || rawCapabilityMetaOverride || (capabilityMetaQuery && !dataRetrievalSignal)) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
decision: "skip_address_lane",
|
||||
@@ -4064,6 +4071,9 @@ function hasAssistantCapabilityQuestionSignal(text) {
|
||||
if (/(?:каки[ею].*(?:фич|функц|возможност|отработан)|какого\s+рода\s+ошибк.*ты\s+мож(?:ешь|ете)|какие\s+ошибк.*ты\s+мож(?:ешь|ете))/iu.test(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:мож(?:ешь|ете)|уме(?:ешь|ете)).*(?:считать|рассчитывать|посчитать).*(?:дельт|delta|маржинальн|margin|рентабельн)/iu.test(normalized)) {
|
||||
return true;
|
||||
}
|
||||
const hasCanVerb = /(?:можешь|можете|умеешь|умеете|можно)/i.test(normalized);
|
||||
const hasControlAction = /(?:настро|установ|подключ|обнов|созда|подготов|сдела|делат|дела)/i.test(normalized);
|
||||
const hasAnalysisAction = /(?:найт|искать|провер|анализ|разоб|объясн|расска|подсказ|показ)/i.test(normalized);
|
||||
@@ -4092,8 +4102,27 @@ function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
||||
function hasLivingChatSignal(text) {
|
||||
return assistantLivingModePolicy.hasLivingChatSignal(text);
|
||||
}
|
||||
function buildAssistantCapabilityContractReply() {
|
||||
return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
||||
function buildAssistantCapabilityContractReply(userMessage = "") {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||
if (/(?:дельт|delta).*(?:договор|контракт)|(?:договор|контракт).*(?:дельт|delta)/iu.test(normalized)) {
|
||||
return [
|
||||
"По дельте по договорам отдельный подтвержденный маршрут в текущем контуре пока не включен.",
|
||||
"То есть я не буду делать вид, что уже считаю ее точно.",
|
||||
"Сейчас могу помочь с близкими вещами: показать договоры, документы, оплаты, выручку и хвосты по расчетам, чтобы подготовить основу под такой расчет.",
|
||||
"Если хочешь, следующим проходом можем именно этот контур добить архитектурно."
|
||||
].join("\n");
|
||||
}
|
||||
const registryReply = (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
||||
const normalizedReply = String(registryReply ?? "")
|
||||
.replace(/в режиме чтения/giu, "")
|
||||
.replace(/read[_ -]?only/giu, "")
|
||||
.replace(/По основным группам:/giu, "Основные направления:")
|
||||
.replace(/Если нужно, подскажу, как лучше сформулировать запрос под вашу задачу\./giu, "Если хотите, можно сразу задать конкретный вопрос по документам, остаткам, НДС, контрагенту или договору.")
|
||||
.replace(/Что не делаю:\s*/giu, "Не делаю только административные действия: ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
return normalizedReply || "Могу помогать с вопросами по данным 1С: НДС, контрагенты, долги, деньги, договоры и склад.";
|
||||
}
|
||||
const assistantProviderExecutionPolicy = (0, assistantProviderExecutionPolicy_1.createAssistantProviderExecutionPolicy)();
|
||||
const assistantLivingModePolicy = (0, assistantLivingModePolicy_1.createAssistantLivingModePolicy)({
|
||||
@@ -4161,6 +4190,8 @@ const assistantTransitionPolicy = (0, assistantTransitionPolicy_1.createAssistan
|
||||
compactWhitespace,
|
||||
repairAddressMojibake,
|
||||
countTokens,
|
||||
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||
hasDataRetrievalRequestSignal,
|
||||
findLastAddressAssistantItem,
|
||||
findLastOrganizationClarificationAddressDebug,
|
||||
mergeKnownOrganizations,
|
||||
|
||||
@@ -1,6 +1,68 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export function createAssistantTransitionPolicy(deps) {
|
||||
function normalizeFollowupText(value) {
|
||||
return deps.compactWhitespace(deps.repairAddressMojibake(String(value ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||
}
|
||||
|
||||
function hasSelectedObjectInventoryScopeSignal(text) {
|
||||
const normalized = normalizeFollowupText(text);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(
|
||||
normalized
|
||||
);
|
||||
}
|
||||
|
||||
function extractSelectedObjectLabel(text) {
|
||||
const repaired = deps.repairAddressMojibake(String(text ?? ""));
|
||||
const quotedMatch = repaired.match(
|
||||
/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)\s*[:,-]?\s*[«"]([^"»]+?)["»]/iu
|
||||
);
|
||||
if (quotedMatch?.[1]) {
|
||||
return deps.toNonEmptyString(quotedMatch[1]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function inferSelectedObjectInventoryFollowupIntent(userMessage, alternateMessage = null) {
|
||||
const samples = [userMessage, alternateMessage].map((item) => normalizeFollowupText(item)).filter(Boolean);
|
||||
if (samples.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
samples.some((sample) =>
|
||||
/(?:где\s+(?:мы\s+)?взял|откуда\s+(?:мы\s+)?взял|у\s+кого\s+купил|кто\s+постав|поставщик|источник\s+поступл|от\s+кого\s+поступ)/iu.test(
|
||||
sample
|
||||
)
|
||||
)
|
||||
) {
|
||||
return "inventory_purchase_provenance_for_item";
|
||||
}
|
||||
if (
|
||||
samples.some((sample) =>
|
||||
/(?:документ|накладн|счет|сч[её]т|акт|поступл|покажи\s+док|все\s+документ|все\s+операц)/iu.test(sample)
|
||||
)
|
||||
) {
|
||||
return "inventory_purchase_documents_for_item";
|
||||
}
|
||||
if (
|
||||
samples.some((sample) =>
|
||||
/(?:кому\s+продал|кому\s+ушл|куда\s+продал|кто\s+купил|реализац|отгруз|продаж)/iu.test(sample)
|
||||
)
|
||||
) {
|
||||
return "inventory_sale_trace_for_item";
|
||||
}
|
||||
if (samples.some((sample) => /(?:рентабел|маржин|прибыл|наценк|выгод)/iu.test(sample))) {
|
||||
return "inventory_profitability_for_item";
|
||||
}
|
||||
if (samples.some((sample) => /(?:цепочк|от\s+закупк.*до\s+продаж|от\s+покупк.*до\s+продаж)/iu.test(sample))) {
|
||||
return "inventory_purchase_to_sale_chain";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseDmyDateToIso(value) {
|
||||
const match = String(value ?? "").trim().match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
|
||||
if (!match) {
|
||||
@@ -90,6 +152,76 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
return earliestIsoDate ? computeMonthWindowFromIso(earliestIsoDate) : null;
|
||||
}
|
||||
|
||||
function isUsableFollowupSourceDebug(debug) {
|
||||
if (!debug || typeof debug !== "object") {
|
||||
return false;
|
||||
}
|
||||
const executionLane = deps.toNonEmptyString(debug.execution_lane);
|
||||
const detectedIntent = deps.toNonEmptyString(debug.detected_intent);
|
||||
const selectedRecipe = deps.toNonEmptyString(debug.selected_recipe);
|
||||
const answerGroundingCheck =
|
||||
debug.answer_grounding_check && typeof debug.answer_grounding_check === "object"
|
||||
? debug.answer_grounding_check
|
||||
: null;
|
||||
const groundingStatus = deps.toNonEmptyString(answerGroundingCheck?.status);
|
||||
if (groundingStatus === "grounded") {
|
||||
return true;
|
||||
}
|
||||
if (selectedRecipe) {
|
||||
return true;
|
||||
}
|
||||
return executionLane === "address_query" && Boolean(detectedIntent && detectedIntent !== "unknown");
|
||||
}
|
||||
|
||||
function findRecentUsableAddressAssistantItem(items) {
|
||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
||||
continue;
|
||||
}
|
||||
if (isUsableFollowupSourceDebug(item.debug)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readAddressDebugItemHint(debug) {
|
||||
if (!debug || typeof debug !== "object") {
|
||||
return null;
|
||||
}
|
||||
const extractedFilters =
|
||||
debug.extracted_filters && typeof debug.extracted_filters === "object" ? debug.extracted_filters : null;
|
||||
return (
|
||||
deps.toNonEmptyString(extractedFilters?.item) ??
|
||||
(deps.toNonEmptyString(debug.anchor_type) === "item"
|
||||
? deps.toNonEmptyString(debug.anchor_value_resolved) ?? deps.toNonEmptyString(debug.anchor_value_raw)
|
||||
: null)
|
||||
);
|
||||
}
|
||||
|
||||
function findRecentInventoryPurchaseProvenanceItem(items, itemHint = null) {
|
||||
const normalizedItemHint = deps.toNonEmptyString(itemHint);
|
||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
const debug = item?.debug;
|
||||
if (!item || item.role !== "assistant" || !debug || typeof debug !== "object") {
|
||||
continue;
|
||||
}
|
||||
if (deps.toNonEmptyString(debug.detected_intent) !== "inventory_purchase_provenance_for_item") {
|
||||
continue;
|
||||
}
|
||||
if (!normalizedItemHint) {
|
||||
return item;
|
||||
}
|
||||
const candidateItem = readAddressDebugItemHint(debug);
|
||||
if (candidateItem && candidateItem === normalizedItemHint) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
|
||||
if (
|
||||
sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
|
||||
@@ -270,7 +402,24 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
llmPreDecomposeMeta = null,
|
||||
addressNavigationState = null
|
||||
) {
|
||||
const previousAddressItem = deps.findLastAddressAssistantItem(items);
|
||||
const rawCapabilityMetaQuery =
|
||||
deps.shouldHandleAsAssistantCapabilityMetaQuery(userMessage) ||
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.shouldHandleAsAssistantCapabilityMetaQuery(String(alternateMessage ?? ""))
|
||||
: false);
|
||||
const rawDataRetrievalSignal =
|
||||
deps.hasDataRetrievalRequestSignal(userMessage) ||
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.hasDataRetrievalRequestSignal(String(alternateMessage ?? ""))
|
||||
: false);
|
||||
if (rawCapabilityMetaQuery && !rawDataRetrievalSignal) {
|
||||
return null;
|
||||
}
|
||||
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
||||
const previousAddressItem =
|
||||
(latestAddressItem && isUsableFollowupSourceDebug(latestAddressItem?.debug)
|
||||
? latestAddressItem
|
||||
: findRecentUsableAddressAssistantItem(items)) ?? latestAddressItem;
|
||||
const previousAddressDebug = previousAddressItem?.debug ?? null;
|
||||
const lastOrganizationClarificationDebug = deps.findLastOrganizationClarificationAddressDebug(items);
|
||||
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
||||
@@ -430,6 +579,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
}
|
||||
const sourceIntent = deps.toNonEmptyString(previousAddressDebug.detected_intent);
|
||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const llmSelectedObjectScopeDetected =
|
||||
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||
const resolvedPrimaryIntent = deps.resolveAddressIntent(deps.repairAddressMojibake(String(userMessage ?? ""))).intent;
|
||||
const resolvedAlternateIntent = deps.toNonEmptyString(alternateMessage)
|
||||
? deps.resolveAddressIntent(deps.repairAddressMojibake(String(alternateMessage ?? ""))).intent
|
||||
@@ -567,14 +718,15 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
? deps.hasFollowupMarker(String(alternateMessage ?? "")) ||
|
||||
deps.hasReferentialPointer(String(alternateMessage ?? ""))
|
||||
: false);
|
||||
const hasSelectedObjectInventorySignalPrimary = /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(
|
||||
String(userMessage ?? "")
|
||||
);
|
||||
const hasSelectedObjectInventorySignalPrimary =
|
||||
llmSelectedObjectScopeDetected || hasSelectedObjectInventoryScopeSignal(userMessage);
|
||||
const hasSelectedObjectInventorySignalAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(
|
||||
String(alternateMessage ?? "")
|
||||
)
|
||||
? hasSelectedObjectInventoryScopeSignal(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
const selectedObjectRetargetIntent =
|
||||
hasSelectedObjectInventorySignalPrimary || hasSelectedObjectInventorySignalAlternate
|
||||
? inferSelectedObjectInventoryFollowupIntent(userMessage, alternateMessage)
|
||||
: null;
|
||||
let inventoryRootFrame = deps.findRecentInventoryRootFrame(items);
|
||||
if (inventoryRootFrame && navigationOrganization && !deps.toNonEmptyString(inventoryRootFrame.filters?.organization)) {
|
||||
inventoryRootFrame = {
|
||||
@@ -649,7 +801,17 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
previousFilters.organization = organizationClarificationSelection;
|
||||
}
|
||||
if (inventoryPurchaseDateVatBridge) {
|
||||
const purchaseBridgeWindow = extractPurchaseDateBridgeWindow(previousAddressItem, addressNavigationState);
|
||||
const purchaseBridgeItem =
|
||||
previousAddressItem &&
|
||||
deps.toNonEmptyString(previousAddressDebug?.detected_intent) === "inventory_purchase_provenance_for_item"
|
||||
? previousAddressItem
|
||||
: findRecentInventoryPurchaseProvenanceItem(
|
||||
items,
|
||||
deps.toNonEmptyString(navigationFocusObjectLabel) ??
|
||||
readAddressDebugItemHint(previousAddressDebug) ??
|
||||
deps.toNonEmptyString(previousFilters.item)
|
||||
) ?? previousAddressItem;
|
||||
const purchaseBridgeWindow = extractPurchaseDateBridgeWindow(purchaseBridgeItem, addressNavigationState);
|
||||
if (purchaseBridgeWindow) {
|
||||
previousFilters.period_from = purchaseBridgeWindow.period_from;
|
||||
previousFilters.period_to = purchaseBridgeWindow.period_to;
|
||||
@@ -761,8 +923,6 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
if (
|
||||
!rootScopedPivot &&
|
||||
!deps.toNonEmptyString(previousFilters.item) &&
|
||||
navigationFocusObjectType === "item" &&
|
||||
navigationFocusObjectLabel &&
|
||||
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
|
||||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
||||
@@ -773,10 +933,14 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
hasSelectedObjectInventorySignalAlternate)
|
||||
) {
|
||||
previousFilters.item = navigationFocusObjectLabel;
|
||||
if (!previousAnchor) {
|
||||
const selectedObjectLabel =
|
||||
(navigationFocusObjectType === "item" ? navigationFocusObjectLabel : null) ??
|
||||
extractSelectedObjectLabel(userMessage) ??
|
||||
(deps.toNonEmptyString(alternateMessage) ? extractSelectedObjectLabel(String(alternateMessage ?? "")) : null);
|
||||
if (selectedObjectLabel) {
|
||||
previousFilters.item = selectedObjectLabel;
|
||||
previousAnchorType = "item";
|
||||
previousAnchor = navigationFocusObjectLabel;
|
||||
previousAnchor = selectedObjectLabel;
|
||||
}
|
||||
}
|
||||
if (organizationClarificationSelection && !previousAnchor) {
|
||||
@@ -817,6 +981,11 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
const carryoverTargetIntent =
|
||||
inventoryPurchaseDateVatBridge
|
||||
? "vat_liability_confirmed_for_tax_period"
|
||||
: selectedObjectRetargetIntent &&
|
||||
(explicitIntent === null ||
|
||||
explicitIntent === "inventory_on_hand_as_of_date" ||
|
||||
explicitIntent === sourceIntent)
|
||||
? selectedObjectRetargetIntent
|
||||
: followupSelectionMode === "carry_root_context"
|
||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||
: explicitInventorySameDatePivot
|
||||
|
||||
Reference in New Issue
Block a user