ЮИ - редактура вопросов в АВТОПРОГОНАХ
This commit is contained in:
parent
0431595542
commit
9872ef5446
|
|
@ -179,6 +179,20 @@ Still open after the accepted phase8 replay:
|
|||
- proactive organization authority at the very beginning of a new multi-company bookkeeping session is still weaker than the target product feel; the current system now clarifies honestly and cleanly, but it does not yet always pre-offer company selection early in the conversational flow;
|
||||
- some user-facing inventory/counterparty labels inside business answers still deserve final presentation cleanup, but these are now post-stabilization quality refinements rather than continuity-authority blockers.
|
||||
|
||||
Latest phase9 proactive-authority evidence after the fresh multi-company replay:
|
||||
|
||||
- a new live replay `address_truth_harness_phase9_proactive_scope_offer_live_20260418_rerun3` is accepted end-to-end with `5/5` steps green;
|
||||
- on the very first smalltalk turn, the assistant now stays in normal living-chat mode but appends a business-first proactive organization offer instead of waiting for a later forced clarification;
|
||||
- explicit company choice in the next turn is now fixed deterministically into session authority before the first accounting route, so later business turns inherit one stable `active organization`;
|
||||
- the restored activity-age route for `ООО Альтернатива Плюс` is now proven again inside the same shared session, not only in isolated route checks;
|
||||
- the previously broken same-date inventory pivot after receivables is now routed as `inventory_on_hand_as_of_date` with the carried date `31.03.2020` and the carried organization `ООО Альтернатива Плюс`, without falling back into repeated company clarification;
|
||||
- this phase therefore closes the remaining gap called out at the end of phase8: proactive company authority is no longer purely reactive in fresh multi-company bookkeeping sessions.
|
||||
|
||||
Still open after the accepted phase9 replay:
|
||||
|
||||
- business answers are now semantically correct on this path, but some inventory list formatting still feels heavier and more mechanical than the target human style;
|
||||
- the next architecture slice should keep expanding saved-session proof across additional real user chains, while separately tightening answer presentation so exact routes do not feel template-driven even when the truth path is already correct.
|
||||
|
||||
## Ready Signal
|
||||
|
||||
The project can leave the current breakpoint when:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase9_proactive_scope_offer",
|
||||
"domain": "address_phase9_proactive_scope_offer",
|
||||
"title": "Phase 9 proactive organization offer replay for fresh multi-company sessions",
|
||||
"description": "Focused AGENT replay for the new living-chat authority layer. The scenario validates that a fresh smalltalk turn can proactively surface organization choice, that the selected company becomes active for the session, that organization activity age remains reachable, and that same-date inventory follow-up does not fall back into repeated company clarification.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_smalltalk_with_scope_offer",
|
||||
"title": "Fresh smalltalk offers organization scope without technical garbage",
|
||||
"question": "привет, как дела?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)привет|дела|норм|на связи",
|
||||
"(?i)организац|компан"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)tool_gate_reason",
|
||||
"(?i)address_mode",
|
||||
"(?i)living_reason",
|
||||
"(?i)mcp",
|
||||
"(?i)read_only",
|
||||
"(?i)snapshot_items"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"meta_smalltalk",
|
||||
"proactive_scope_offer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_choose_organization",
|
||||
"title": "Bare company choice fixes active organization for the session",
|
||||
"question": "Альтернатива Плюс",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)фиксир|рабочую организац",
|
||||
"(?i)Альтернатива Плюс"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)mcp",
|
||||
"(?i)read_only",
|
||||
"(?i)snapshot_items"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"company_selected",
|
||||
"living_scope_selection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_company_activity_age",
|
||||
"title": "Organization activity age is answered after proactive selection",
|
||||
"question": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"expected_intents": [
|
||||
"counterparty_activity_lifecycle"
|
||||
],
|
||||
"expected_recipe": "address_counterparty_activity_lifecycle_v1",
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)активност",
|
||||
"(?i)первая подтвержденная|не удается точно определить|лет"
|
||||
],
|
||||
"forbidden_direct_answer_patterns": [
|
||||
"(?i)не найден контрагент",
|
||||
"(?i)уточните точное наименование организации"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"organization_activity_age",
|
||||
"company_selected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_receivables_root",
|
||||
"title": "Receivables root keeps March 2020 in the active organization scope",
|
||||
"question": "кто нам должен на март 2020?",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"receivables_confirmed_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"period_from": "2020-03-01",
|
||||
"period_to": "2020-03-31"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)дебитор",
|
||||
"31\\.03\\.2020"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"settlements_receivables",
|
||||
"company_selected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_05_inventory_same_date",
|
||||
"title": "Inventory same-date follow-up does not ask for company again",
|
||||
"question": "остатки по складу на эту же дату",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "{{step_04_receivables_root.filters.as_of_date}}",
|
||||
"period_from": "{{step_04_receivables_root.filters.period_from}}",
|
||||
"period_to": "{{step_04_receivables_root.filters.period_to}}"
|
||||
},
|
||||
"required_direct_answer_patterns_all": [
|
||||
"(?i)на складе",
|
||||
"31\\.03\\.2020"
|
||||
],
|
||||
"forbidden_direct_answer_patterns": [
|
||||
"(?i)уточните организац",
|
||||
"(?i)по какой компании"
|
||||
],
|
||||
"required_filter_within_previous_step_period": {
|
||||
"as_of_date": "step_04_receivables_root"
|
||||
},
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"inventory_root",
|
||||
"same_date_pivot",
|
||||
"company_authority"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -73,6 +74,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,28 @@ function createAssistantBoundaryPolicy(deps) {
|
|||
"Если в нем несколько организаций, скажите, по какой смотреть данные."
|
||||
].join(" ");
|
||||
}
|
||||
function buildAssistantProactiveOrganizationOfferReply(scopeProbe = null) {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||
? scopeProbe.organizations
|
||||
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||
.filter((item) => item.length > 0)
|
||||
.filter((item, index, array) => array.indexOf(item) === index)
|
||||
: [];
|
||||
if (organizations.length === 1) {
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||
].join(" ");
|
||||
}
|
||||
if (organizations.length > 1) {
|
||||
const preview = organizations.slice(0, 10).join(", ");
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||
].join(" ");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function buildAssistantDataScopeSelectionReply(organization) {
|
||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||
return [
|
||||
|
|
@ -161,6 +183,7 @@ function createAssistantBoundaryPolicy(deps) {
|
|||
}
|
||||
return {
|
||||
buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -30,6 +31,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -46,6 +47,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -41,6 +42,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -31,6 +32,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ function findLastAddressDebugWithItem(items) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
function hasPriorAssistantTurn(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return false;
|
||||
}
|
||||
return items.some((item) => item && typeof item === "object" && item.role === "assistant");
|
||||
}
|
||||
function findLastAddressDebug(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
|
|
@ -157,6 +163,7 @@ async function runAssistantLivingChatRuntime(input) {
|
|||
let livingChatScriptGuardReason = null;
|
||||
let livingChatGroundingGuardApplied = false;
|
||||
let livingChatGroundingGuardReason = null;
|
||||
let livingChatProactiveScopeOfferApplied = false;
|
||||
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||
|
|
@ -256,6 +263,31 @@ async function runAssistantLivingChatRuntime(input) {
|
|||
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||
livingChatSource = "llm_chat_grounding_guard";
|
||||
}
|
||||
const shouldOfferProactiveOrganizationScope = !selectedOrganization &&
|
||||
!activeOrganization &&
|
||||
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||
input.modeDecision?.mode === "chat" &&
|
||||
input.hasLivingChatSignal(userMessage);
|
||||
if (shouldOfferProactiveOrganizationScope) {
|
||||
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||
...knownOrganizations,
|
||||
...(Array.isArray(proactiveScopeProbe?.organizations) ? proactiveScopeProbe.organizations : [])
|
||||
]);
|
||||
knownOrganizations = mergedKnownOrganizations;
|
||||
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||
activeOrganization = mergedKnownOrganizations[0];
|
||||
}
|
||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||
if (proactiveOffer) {
|
||||
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||
livingChatProactiveScopeOfferApplied = true;
|
||||
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||
if (!dataScopeProbe) {
|
||||
dataScopeProbe = proactiveScopeProbe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!chatText) {
|
||||
return {
|
||||
|
|
@ -288,6 +320,7 @@ async function runAssistantLivingChatRuntime(input) {
|
|||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
||||
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
|
||||
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
||||
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
||||
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
||||
|
|
|
|||
|
|
@ -5023,6 +5023,7 @@ class AssistantService {
|
|||
hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||
|
|
@ -5030,6 +5031,7 @@ class AssistantService {
|
|||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,28 @@ function createAssistantTransitionPolicy(deps) {
|
|||
(hasRestatementCue || hasBareSnapshotSameDateCue || hasBareSnapshotSameDatePhraseCue) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(normalized));
|
||||
}
|
||||
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||
const normalized = deps
|
||||
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||
if (!hasInventoryLexeme) {
|
||||
return false;
|
||||
}
|
||||
const sameDatePhrases = [
|
||||
"на ту же дат",
|
||||
"на эту же дат",
|
||||
"на эту дат",
|
||||
"эту дат",
|
||||
"та же дата",
|
||||
"тот же период",
|
||||
"этот же период"
|
||||
];
|
||||
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||
}
|
||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||
const normalized = deps.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
if (!normalized || deps.countTokens(normalized) > 4) {
|
||||
|
|
@ -170,6 +192,10 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame))
|
||||
: false;
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
let hasStrongFollowupReference = hasPrimaryIndexReferenceSignal ||
|
||||
hasAlternateIndexReferenceSignal ||
|
||||
hasOrganizationClarificationContinuation ||
|
||||
|
|
@ -451,6 +477,9 @@ function createAssistantTransitionPolicy(deps) {
|
|||
currentFrameKind === "generic") &&
|
||||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||
const explicitInventorySameDatePivot = Boolean(!inventoryRootFrame &&
|
||||
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||
if (rootScopedPivot) {
|
||||
previousIntent = null;
|
||||
|
|
@ -540,7 +569,9 @@ function createAssistantTransitionPolicy(deps) {
|
|||
hasSelectedObjectInventorySignalAlternate));
|
||||
const carryoverTargetIntent = followupSelectionMode === "carry_root_context"
|
||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
: explicitInventorySameDatePivot
|
||||
? "inventory_on_hand_as_of_date"
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
return {
|
||||
followupContext: {
|
||||
previous_intent: previousIntent ?? undefined,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
|||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||
|
|
@ -58,6 +59,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
|||
applyGroundingGuard: deps.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||
|
|
@ -69,6 +70,8 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||
|
|
@ -179,6 +182,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -186,6 +190,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export interface AssistantBoundaryPolicyGroundingGuardInput {
|
|||
|
||||
export interface AssistantBoundaryPolicy {
|
||||
buildAssistantDataScopeContractReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||
buildAssistantDataScopeSelectionReply: (organization: unknown) => string;
|
||||
buildAssistantOrganizationFactBoundaryReply: (organization: unknown) => string;
|
||||
buildAssistantOperationalBoundaryReply: () => string;
|
||||
|
|
@ -90,6 +91,32 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
|||
].join(" ");
|
||||
}
|
||||
|
||||
function buildAssistantProactiveOrganizationOfferReply(scopeProbe: Record<string, unknown> | null = null): string {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||
? scopeProbe.organizations
|
||||
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||
.filter((item) => item.length > 0)
|
||||
.filter((item, index, array) => array.indexOf(item) === index)
|
||||
: [];
|
||||
|
||||
if (organizations.length === 1) {
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
if (organizations.length > 1) {
|
||||
const preview = organizations.slice(0, 10).join(", ");
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function buildAssistantDataScopeSelectionReply(organization: unknown): string {
|
||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||
return [
|
||||
|
|
@ -219,6 +246,7 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
|||
|
||||
return {
|
||||
buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
|||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||
|
|
@ -29,6 +30,8 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
|||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||
|
|
@ -73,6 +76,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -80,6 +84,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -83,6 +84,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -64,6 +65,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
|||
hasOperationalAdminActionRequestSignal: AssistantLivingChatRuntimeInput["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: AssistantLivingChatRuntimeInput["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: AssistantLivingChatRuntimeInput["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: AssistantLivingChatRuntimeInput["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: AssistantLivingChatRuntimeInput["resolveDataScopeProbe"];
|
||||
|
|
@ -33,6 +34,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
|||
applyGroundingGuard: AssistantLivingChatRuntimeInput["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: AssistantLivingChatRuntimeInput["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply: AssistantLivingChatRuntimeInput["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOperationalBoundaryReply"];
|
||||
|
|
@ -76,6 +78,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
|||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
|
|
@ -84,6 +87,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
|||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export interface AssistantLivingChatRuntimeInput {
|
|||
hasOperationalAdminActionRequestSignal: (message: string) => boolean;
|
||||
hasOrganizationFactLookupSignal: (message: string) => boolean;
|
||||
hasOrganizationFactFollowupSignal: (message: string, items: unknown[]) => boolean;
|
||||
hasLivingChatSignal: (message: string) => boolean;
|
||||
shouldEmitOrganizationSelectionReply: (message: string, activeOrganization: string | null) => boolean;
|
||||
hasAssistantCapabilityQuestionSignal: (message: string) => boolean;
|
||||
resolveDataScopeProbe: () => Promise<Record<string, unknown> | null>;
|
||||
|
|
@ -51,6 +52,7 @@ export interface AssistantLivingChatRuntimeInput {
|
|||
};
|
||||
buildAssistantSafetyRefusalReply: () => string;
|
||||
buildAssistantDataScopeContractReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
||||
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
||||
buildAssistantOperationalBoundaryReply: () => string;
|
||||
|
|
@ -134,6 +136,13 @@ function findLastAddressDebugWithItem(items: unknown[]): Record<string, unknown>
|
|||
return null;
|
||||
}
|
||||
|
||||
function hasPriorAssistantTurn(items: unknown[]): boolean {
|
||||
if (!Array.isArray(items)) {
|
||||
return false;
|
||||
}
|
||||
return items.some((item) => item && typeof item === "object" && (item as { role?: string }).role === "assistant");
|
||||
}
|
||||
|
||||
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
|
|
@ -252,6 +261,7 @@ export async function runAssistantLivingChatRuntime(
|
|||
let livingChatScriptGuardReason: string | null = null;
|
||||
let livingChatGroundingGuardApplied = false;
|
||||
let livingChatGroundingGuardReason: string | null = null;
|
||||
let livingChatProactiveScopeOfferApplied = false;
|
||||
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||
|
|
@ -348,6 +358,33 @@ export async function runAssistantLivingChatRuntime(
|
|||
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||
livingChatSource = "llm_chat_grounding_guard";
|
||||
}
|
||||
|
||||
const shouldOfferProactiveOrganizationScope =
|
||||
!selectedOrganization &&
|
||||
!activeOrganization &&
|
||||
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||
input.modeDecision?.mode === "chat" &&
|
||||
input.hasLivingChatSignal(userMessage);
|
||||
if (shouldOfferProactiveOrganizationScope) {
|
||||
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||
...knownOrganizations,
|
||||
...(Array.isArray(proactiveScopeProbe?.organizations) ? (proactiveScopeProbe.organizations as unknown[]) : [])
|
||||
]);
|
||||
knownOrganizations = mergedKnownOrganizations;
|
||||
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||
activeOrganization = mergedKnownOrganizations[0];
|
||||
}
|
||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||
if (proactiveOffer) {
|
||||
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||
livingChatProactiveScopeOfferApplied = true;
|
||||
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||
if (!dataScopeProbe) {
|
||||
dataScopeProbe = proactiveScopeProbe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!chatText) {
|
||||
|
|
@ -385,6 +422,7 @@ export async function runAssistantLivingChatRuntime(
|
|||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
||||
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
|
||||
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
||||
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
||||
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
||||
|
|
|
|||
|
|
@ -4981,6 +4981,7 @@ export class AssistantService {
|
|||
hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||
|
|
@ -4988,6 +4989,7 @@ export class AssistantService {
|
|||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,29 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
);
|
||||
}
|
||||
|
||||
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||
const normalized = deps
|
||||
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||
if (!hasInventoryLexeme) {
|
||||
return false;
|
||||
}
|
||||
const sameDatePhrases = [
|
||||
"на ту же дат",
|
||||
"на эту же дат",
|
||||
"на эту дат",
|
||||
"эту дат",
|
||||
"та же дата",
|
||||
"тот же период",
|
||||
"этот же период"
|
||||
];
|
||||
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||
}
|
||||
|
||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||
const normalized = deps.compactWhitespace(
|
||||
deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase()
|
||||
|
|
@ -227,6 +250,10 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
Boolean(recentInventoryRootFrame)
|
||||
)
|
||||
: false;
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
let hasStrongFollowupReference =
|
||||
hasPrimaryIndexReferenceSignal ||
|
||||
hasAlternateIndexReferenceSignal ||
|
||||
|
|
@ -550,6 +577,11 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||
);
|
||||
const explicitInventorySameDatePivot = Boolean(
|
||||
!inventoryRootFrame &&
|
||||
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||
);
|
||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||
if (rootScopedPivot) {
|
||||
previousIntent = null;
|
||||
|
|
@ -651,7 +683,9 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
const carryoverTargetIntent =
|
||||
followupSelectionMode === "carry_root_context"
|
||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
: explicitInventorySameDatePivot
|
||||
? "inventory_on_hand_as_of_date"
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
return {
|
||||
followupContext: {
|
||||
previous_intent: previousIntent ?? undefined,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
|||
AddressAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: AddressAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal:
|
||||
|
|
@ -77,6 +78,8 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
|||
buildAssistantSafetyRefusalReply: AddressAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply:
|
||||
|
|
@ -164,6 +167,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||
|
|
@ -171,6 +175,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||
applyGroundingGuard: deps.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,23 @@ describe("assistantBoundaryPolicy", () => {
|
|||
expect(reply).not.toContain("\\");
|
||||
});
|
||||
|
||||
it("builds proactive organization offer without technical labels", () => {
|
||||
const policy = createPolicy();
|
||||
|
||||
const reply = policy.buildAssistantProactiveOrganizationOfferReply({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]
|
||||
});
|
||||
|
||||
expect(reply).toContain("Если дальше пойдём в данные 1С");
|
||||
expect(reply).toContain("ООО Альтернатива Плюс");
|
||||
expect(reply).toContain("ООО Лайсвуд");
|
||||
expect(reply).toContain("РАЙМ");
|
||||
expect(reply).not.toContain("MCP");
|
||||
expect(reply.toLowerCase()).not.toContain("snapshot");
|
||||
});
|
||||
|
||||
it("strips unexpected CJK fragments from live chat reply", () => {
|
||||
const policy = createPolicy();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function buildInput(overrides: Record<string, unknown> = {}) {
|
|||
hasOperationalAdminActionRequestSignal: () => false,
|
||||
hasOrganizationFactLookupSignal: () => false,
|
||||
hasOrganizationFactFollowupSignal: () => false,
|
||||
hasLivingChatSignal: () => true,
|
||||
shouldEmitOrganizationSelectionReply: () => false,
|
||||
hasAssistantCapabilityQuestionSignal: () => false,
|
||||
resolveDataScopeProbe: async () => null,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
|||
hasOperationalAdminActionRequestSignal: () => false,
|
||||
hasOrganizationFactLookupSignal: () => false,
|
||||
hasOrganizationFactFollowupSignal: () => false,
|
||||
hasLivingChatSignal: () => true,
|
||||
shouldEmitOrganizationSelectionReply: () => false,
|
||||
hasAssistantCapabilityQuestionSignal: () => false,
|
||||
resolveDataScopeProbe,
|
||||
|
|
@ -53,6 +54,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
|||
}),
|
||||
buildAssistantSafetyRefusalReply: () => "safety",
|
||||
buildAssistantDataScopeContractReply: () => "scope",
|
||||
buildAssistantProactiveOrganizationOfferReply: () => "",
|
||||
buildAssistantOrganizationFactBoundaryReply: () => "org-boundary",
|
||||
buildAssistantDataScopeSelectionReply: () => "org-selection",
|
||||
buildAssistantOperationalBoundaryReply: () => "ops",
|
||||
|
|
@ -134,6 +136,56 @@ describe("assistant living chat runtime adapter", () => {
|
|||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"],
|
||||
error: null
|
||||
}));
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "привет, как дела?",
|
||||
resolveDataScopeProbe,
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations) ? scopeProbe.organizations.join(", ") : "";
|
||||
return `offer:${organizations}`;
|
||||
}
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("llm-text");
|
||||
expect(output.chatText).toContain("offer:ООО Альтернатива Плюс, ООО Лайсвуд, РАЙМ");
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat_with_proactive_scope_offer");
|
||||
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(true);
|
||||
expect(output.debug?.living_chat_data_scope_probe_org_count).toBe(3);
|
||||
expect(output.debug?.assistant_known_organizations).toEqual(["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]);
|
||||
});
|
||||
|
||||
it("does not add proactive organization offer after the session already has assistant context", async () => {
|
||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"],
|
||||
error: null
|
||||
}));
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "привет еще раз",
|
||||
sessionItems: [{ role: "assistant", text: "Ранее уже отвечал." }],
|
||||
resolveDataScopeProbe,
|
||||
buildAssistantProactiveOrganizationOfferReply: () => "offer"
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toBe("llm-text");
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat");
|
||||
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(false);
|
||||
expect(resolveDataScopeProbe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("builds deterministic memory recap for prior selected-object address context", async () => {
|
||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||
const input = buildRuntimeInput({
|
||||
|
|
@ -144,6 +196,9 @@ describe("assistant living chat runtime adapter", () => {
|
|||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: {
|
||||
status: "grounded"
|
||||
},
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
||||
|
|
@ -160,7 +215,7 @@ describe("assistant living chat runtime adapter", () => {
|
|||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("Зеркало для инвалидов поворотное травмобезопасное");
|
||||
expect(output.chatText).toContain("кто поставил");
|
||||
expect(output.chatText).toContain("кто поставлял");
|
||||
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
|
||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -212,6 +212,48 @@ describe("assistantTransitionPolicy", () => {
|
|||
expect(contract.decision).toBe("continue_previous");
|
||||
});
|
||||
|
||||
it("retargets same-date inventory follow-up away from receivables intent", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.",
|
||||
debug: {
|
||||
detected_intent: "receivables_confirmed_as_of_date",
|
||||
extracted_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
anchor_type: "organization",
|
||||
anchor_value_resolved: 'ООО "Альтернатива Плюс"'
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => true,
|
||||
hasReferentialPointer: () => true,
|
||||
findRecentInventoryRootFrame: () => null,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"остатки по складу на эту же дату",
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
});
|
||||
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"questions": [
|
||||
"какие остатки на складе на март 2021",
|
||||
"давай по Альтернативе Плюс",
|
||||
"тогда покажи остатки на март 2021",
|
||||
"тогда покажи остатки на июль2017",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"а по этой позиции когда была закупка?",
|
||||
"покажи документы по этой позиции",
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
"suite_id": "assistant_saved_session_gen-ag04171508-760111",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-17T15:08:06+00:00",
|
||||
"generated_at": "2026-04-18T07:12:37.854Z",
|
||||
"generation_id": "gen-ag04171508-760111",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT replay for inventory clarification continuity and answer-shape cleanliness",
|
||||
"domain": "inventory_answer_shape_and_continuity",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
|
|
@ -26,7 +25,7 @@
|
|||
"user_message": "давай по Альтернативе Плюс"
|
||||
},
|
||||
{
|
||||
"user_message": "тогда покажи остатки на март 2021"
|
||||
"user_message": "тогда покажи остатки на июль2017"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
||||
|
|
@ -46,4 +45,4 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-a4V7FoXsdC",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружать чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NDC AI Normalizer Playground</title>
|
||||
<script type="module" crossorigin src="/assets/index-C8U6PD78.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CfrZGsZo.css">
|
||||
<script type="module" crossorigin src="/assets/index-3F56oUw0.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DNDajOYc.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState, type SyntheticEvent } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type DragEvent, type KeyboardEvent, type SyntheticEvent } from "react";
|
||||
import { apiClient } from "../api/client";
|
||||
import type {
|
||||
AssistantConversationItem,
|
||||
|
|
@ -553,6 +553,22 @@ function CopyOutlineIcon() {
|
|||
);
|
||||
}
|
||||
|
||||
function QuestionGripIcon() {
|
||||
return (
|
||||
<svg className="autoruns-question-grip-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
||||
<circle cx="4" cy="4" r="1" />
|
||||
<circle cx="8" cy="4" r="1" />
|
||||
<circle cx="12" cy="4" r="1" />
|
||||
<circle cx="4" cy="8" r="1" />
|
||||
<circle cx="8" cy="8" r="1" />
|
||||
<circle cx="12" cy="8" r="1" />
|
||||
<circle cx="4" cy="12" r="1" />
|
||||
<circle cx="8" cy="12" r="1" />
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AutoRunsHistoryPanel({
|
||||
connection,
|
||||
modelOptions,
|
||||
|
|
@ -605,6 +621,11 @@ export function AutoRunsHistoryPanel({
|
|||
const [autoGenHistory, setAutoGenHistory] = useState<AutoGenHistoryRecord[]>([]);
|
||||
const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState("");
|
||||
const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState<string[]>([]);
|
||||
const [generatedQuestionsBusy, setGeneratedQuestionsBusy] = useState(false);
|
||||
const [editingQuestionIndex, setEditingQuestionIndex] = useState<number | null>(null);
|
||||
const [editingQuestionDraft, setEditingQuestionDraft] = useState("");
|
||||
const [draggingQuestionIndex, setDraggingQuestionIndex] = useState<number | null>(null);
|
||||
const [dragOverQuestionIndex, setDragOverQuestionIndex] = useState<number | null>(null);
|
||||
const [activeAsyncJob, setActiveAsyncJob] = useState<AsyncEvalRunJob | null>(null);
|
||||
const [postAnalysis, setPostAnalysis] = useState<AutoRunPostAnalysisResponse | null>(null);
|
||||
const [autoGenBusy, setAutoGenBusy] = useState(false);
|
||||
|
|
@ -674,6 +695,7 @@ export function AutoRunsHistoryPanel({
|
|||
|
||||
const initialLoadDoneRef = useRef(false);
|
||||
const asyncJobPollTimerRef = useRef<number | null>(null);
|
||||
const questionEditorRef = useRef<HTMLInputElement | null>(null);
|
||||
const isSavedUserSessionsMode = autoGenSettings.mode === "saved_user_sessions";
|
||||
const selectedPersonality = useMemo(
|
||||
() => autogenPersonalities.find((item) => item.id === autoGenSettings.personalityId) ?? autogenPersonalities[0] ?? AUTOGEN_PERSONALITIES[0],
|
||||
|
|
@ -1772,6 +1794,192 @@ export function AutoRunsHistoryPanel({
|
|||
savedSessionQuestionDeleteModal.questionIndex
|
||||
]);
|
||||
|
||||
const updateGeneratedQuestions = useCallback(
|
||||
async (nextQuestions: string[], options?: { successLog?: string; revertQuestions?: string[] }) => {
|
||||
const generationId = selectedAutogenGeneration?.generation_id ?? "";
|
||||
const revertQuestions = options?.revertQuestions ?? editableGeneratedQuestions;
|
||||
|
||||
setEditableGeneratedQuestions(nextQuestions);
|
||||
if (!generationId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
setGeneratedQuestionsBusy(true);
|
||||
try {
|
||||
const payload = await apiClient.updateAutoRunAutogenQuestions({
|
||||
generation_id: generationId,
|
||||
questions: nextQuestions
|
||||
});
|
||||
setAutoGenHistory((prev) =>
|
||||
prev.map((item) => (item.generation_id === generationId ? payload.generation : item))
|
||||
);
|
||||
setEditableGeneratedQuestions([...(payload.generation.questions ?? [])]);
|
||||
if (options?.successLog) {
|
||||
log(options.successLog);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setEditableGeneratedQuestions(revertQuestions);
|
||||
setErrorText(`Вопросы к запуску: ${message}`);
|
||||
log(`Autogen questions update error: ${message}`);
|
||||
return false;
|
||||
} finally {
|
||||
setGeneratedQuestionsBusy(false);
|
||||
}
|
||||
},
|
||||
[editableGeneratedQuestions, log, selectedAutogenGeneration]
|
||||
);
|
||||
|
||||
const startQuestionEdit = useCallback(
|
||||
(questionIndex: number) => {
|
||||
setEditingQuestionIndex(questionIndex);
|
||||
setEditingQuestionDraft(editableGeneratedQuestions[questionIndex] ?? "");
|
||||
},
|
||||
[editableGeneratedQuestions]
|
||||
);
|
||||
|
||||
const stopQuestionEdit = useCallback(() => {
|
||||
setEditingQuestionIndex(null);
|
||||
setEditingQuestionDraft("");
|
||||
}, []);
|
||||
|
||||
const commitQuestionEdit = useCallback(
|
||||
async (questionIndex: number | null) => {
|
||||
if (questionIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentQuestion = editableGeneratedQuestions[questionIndex] ?? "";
|
||||
const nextText = editingQuestionDraft.trim();
|
||||
if (!nextText || nextText === currentQuestion) {
|
||||
stopQuestionEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
const nextQuestions = editableGeneratedQuestions.map((item, index) => (index === questionIndex ? nextText : item));
|
||||
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||
successLog: `Список вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||
revertQuestions: editableGeneratedQuestions
|
||||
});
|
||||
if (saved) {
|
||||
stopQuestionEdit();
|
||||
}
|
||||
},
|
||||
[editableGeneratedQuestions, editingQuestionDraft, selectedAutogenGeneration, stopQuestionEdit, updateGeneratedQuestions]
|
||||
);
|
||||
|
||||
const handleQuestionEditorBlur = useCallback(() => {
|
||||
void commitQuestionEdit(editingQuestionIndex);
|
||||
}, [commitQuestionEdit, editingQuestionIndex]);
|
||||
|
||||
const handleQuestionEditorKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
void commitQuestionEdit(editingQuestionIndex);
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
stopQuestionEdit();
|
||||
}
|
||||
},
|
||||
[commitQuestionEdit, editingQuestionIndex, stopQuestionEdit]
|
||||
);
|
||||
|
||||
const handleAddGeneratedQuestion = useCallback(async () => {
|
||||
const nextQuestions = [...editableGeneratedQuestions, "Новый вопрос"];
|
||||
const nextIndex = nextQuestions.length - 1;
|
||||
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||
successLog: `В список добавлен вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||
revertQuestions: editableGeneratedQuestions
|
||||
});
|
||||
if (saved) {
|
||||
setEditingQuestionIndex(nextIndex);
|
||||
setEditingQuestionDraft(nextQuestions[nextIndex]);
|
||||
}
|
||||
}, [editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]);
|
||||
|
||||
const handleDeleteGeneratedQuestion = useCallback(
|
||||
async (questionIndex: number) => {
|
||||
if (editableGeneratedQuestions.length <= 1) {
|
||||
setErrorText("В списке должен остаться хотя бы один вопрос.");
|
||||
return;
|
||||
}
|
||||
|
||||
const nextQuestions = editableGeneratedQuestions.filter((_, index) => index !== questionIndex);
|
||||
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||
successLog: `Из списка удален вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||
revertQuestions: editableGeneratedQuestions
|
||||
});
|
||||
if (!saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditingQuestionIndex((prev) => {
|
||||
if (prev === null) return prev;
|
||||
if (prev === questionIndex) return null;
|
||||
if (prev > questionIndex) return prev - 1;
|
||||
return prev;
|
||||
});
|
||||
setEditingQuestionDraft("");
|
||||
},
|
||||
[editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
|
||||
);
|
||||
|
||||
const handleQuestionDragStart = useCallback(
|
||||
(event: DragEvent<HTMLButtonElement>, questionIndex: number) => {
|
||||
if (generatedQuestionsBusy) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
setDraggingQuestionIndex(questionIndex);
|
||||
setDragOverQuestionIndex(questionIndex);
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", String(questionIndex));
|
||||
},
|
||||
[generatedQuestionsBusy]
|
||||
);
|
||||
|
||||
const handleQuestionDragOver = useCallback(
|
||||
(event: DragEvent<HTMLDivElement>, questionIndex: number) => {
|
||||
event.preventDefault();
|
||||
if (dragOverQuestionIndex !== questionIndex) {
|
||||
setDragOverQuestionIndex(questionIndex);
|
||||
}
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
},
|
||||
[dragOverQuestionIndex]
|
||||
);
|
||||
|
||||
const handleQuestionDrop = useCallback(
|
||||
async (event: DragEvent<HTMLDivElement>, questionIndex: number) => {
|
||||
event.preventDefault();
|
||||
const fromIndex = draggingQuestionIndex;
|
||||
setDragOverQuestionIndex(null);
|
||||
setDraggingQuestionIndex(null);
|
||||
if (fromIndex === null || fromIndex === questionIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextQuestions = [...editableGeneratedQuestions];
|
||||
const [movedQuestion] = nextQuestions.splice(fromIndex, 1);
|
||||
nextQuestions.splice(questionIndex, 0, movedQuestion);
|
||||
await updateGeneratedQuestions(nextQuestions, {
|
||||
successLog: `Порядок вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||
revertQuestions: editableGeneratedQuestions
|
||||
});
|
||||
},
|
||||
[draggingQuestionIndex, editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
|
||||
);
|
||||
|
||||
const handleQuestionDragEnd = useCallback(() => {
|
||||
setDraggingQuestionIndex(null);
|
||||
setDragOverQuestionIndex(null);
|
||||
}, []);
|
||||
|
||||
const openAutoGenDeleteModal = useCallback((item: AutoGenHistoryRecord) => {
|
||||
setAutoGenDeleteModal({
|
||||
open: true,
|
||||
|
|
@ -1905,10 +2113,27 @@ export function AutoRunsHistoryPanel({
|
|||
useEffect(() => {
|
||||
if (!selectedAutogenGeneration) {
|
||||
setEditableGeneratedQuestions([]);
|
||||
stopQuestionEdit();
|
||||
setDraggingQuestionIndex(null);
|
||||
setDragOverQuestionIndex(null);
|
||||
return;
|
||||
}
|
||||
setEditableGeneratedQuestions([...selectedAutogenGeneration.questions]);
|
||||
}, [selectedAutogenGeneration]);
|
||||
stopQuestionEdit();
|
||||
setDraggingQuestionIndex(null);
|
||||
setDragOverQuestionIndex(null);
|
||||
}, [selectedAutogenGeneration, stopQuestionEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingQuestionIndex === null) {
|
||||
return;
|
||||
}
|
||||
const timer = window.setTimeout(() => {
|
||||
questionEditorRef.current?.focus();
|
||||
questionEditorRef.current?.select();
|
||||
}, 0);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [editingQuestionIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
setLimitInput(String(filters.limit));
|
||||
|
|
@ -2403,6 +2628,9 @@ export function AutoRunsHistoryPanel({
|
|||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{false ? (
|
||||
<>
|
||||
{/* generated questions editor */}
|
||||
<div className="autoruns-generated-questions">
|
||||
<div className="autoruns-generated-questions-head">
|
||||
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
|
||||
|
|
@ -2451,6 +2679,99 @@ export function AutoRunsHistoryPanel({
|
|||
? "Запуск воспроизводит сохраненную пользовательскую сессию как один последовательный multi-turn сценарий assistant_stage1."
|
||||
: "Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="autoruns-generated-questions">
|
||||
<div className="autoruns-generated-questions-head">
|
||||
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
|
||||
</div>
|
||||
{editableGeneratedQuestions.length === 0 ? (
|
||||
<p className="muted">
|
||||
{isSavedUserSessionsMode
|
||||
? "Список вопросов пуст. Сначала сохраните живую пользовательскую сессию."
|
||||
: "Список вопросов пуст. Сгенерируйте пачку или добавьте вопрос вручную."}
|
||||
</p>
|
||||
) : (
|
||||
<div className="autoruns-generated-questions-list">
|
||||
{editableGeneratedQuestions.map((question, index) => (
|
||||
<div
|
||||
key={`${index}-${question.slice(0, 24)}`}
|
||||
className={[
|
||||
"autoruns-generated-question-item",
|
||||
dragOverQuestionIndex === index ? "drag-over" : "",
|
||||
draggingQuestionIndex === index ? "dragging" : "",
|
||||
editingQuestionIndex === index ? "editing" : ""
|
||||
].filter(Boolean).join(" ")}
|
||||
onDragOver={(event) => handleQuestionDragOver(event, index)}
|
||||
onDrop={(event) => void handleQuestionDrop(event, index)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="autoruns-question-grip-btn"
|
||||
draggable={!generatedQuestionsBusy && editingQuestionIndex !== index}
|
||||
disabled={generatedQuestionsBusy || editingQuestionIndex === index}
|
||||
onDragStart={(event) => handleQuestionDragStart(event, index)}
|
||||
onDragEnd={handleQuestionDragEnd}
|
||||
title="Перетащить вопрос"
|
||||
aria-label={`Перетащить вопрос ${index + 1}`}
|
||||
>
|
||||
<QuestionGripIcon />
|
||||
</button>
|
||||
{editingQuestionIndex === index ? (
|
||||
<>
|
||||
<input
|
||||
ref={questionEditorRef}
|
||||
className="autoruns-generated-question-input"
|
||||
value={editingQuestionDraft}
|
||||
onChange={(event) => setEditingQuestionDraft(event.target.value)}
|
||||
onBlur={handleQuestionEditorBlur}
|
||||
onKeyDown={handleQuestionEditorKeyDown}
|
||||
placeholder="Текст вопроса"
|
||||
disabled={generatedQuestionsBusy}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="autoruns-remove-question-btn"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => void handleDeleteGeneratedQuestion(index)}
|
||||
title="Удалить вопрос"
|
||||
aria-label={`Удалить вопрос ${index + 1}`}
|
||||
disabled={generatedQuestionsBusy}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="autoruns-generated-question-text"
|
||||
onDoubleClick={() => startQuestionEdit(index)}
|
||||
title="Двойной клик для редактирования"
|
||||
>
|
||||
{index + 1}. {question}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="autoruns-add-question-btn"
|
||||
onClick={() => void handleAddGeneratedQuestion()}
|
||||
disabled={!selectedAutogenGeneration || generatedQuestionsBusy}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
{isSavedUserSessionsMode ? (
|
||||
<h4>Сохраненные пользовательские сессии</h4>
|
||||
) : (
|
||||
<p className="muted">Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету.</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="autoruns-autogen-list">
|
||||
{autogenHistoryBusy ? (
|
||||
|
|
|
|||
|
|
@ -1535,24 +1535,137 @@ button:disabled {
|
|||
|
||||
.autoruns-generated-question-item {
|
||||
position: relative;
|
||||
display: block;
|
||||
display: grid;
|
||||
grid-template-columns: 22px minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
background: rgb(var(--rgb-surface-focus));
|
||||
padding: 7px 30px 7px 8px;
|
||||
padding: 7px 8px;
|
||||
font-size: 0.78rem;
|
||||
transition: background 0.15s ease, outline-color 0.15s ease, opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item span {
|
||||
display: block;
|
||||
.autoruns-generated-question-item.drag-over {
|
||||
outline: 1px solid rgba(var(--rgb-active), 0.75);
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.dragging {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing {
|
||||
background: rgb(var(--rgb-active));
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.autoruns-question-grip-btn {
|
||||
width: 18px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: grab;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.autoruns-question-grip-btn:hover:not(:disabled) {
|
||||
color: rgb(var(--rgb-text-main));
|
||||
background: rgba(var(--rgb-background), 0.3);
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-question-grip-btn {
|
||||
color: rgba(var(--rgb-active-text), 0.9);
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-question-grip-btn:hover:not(:disabled) {
|
||||
color: rgb(var(--rgb-active-text));
|
||||
background: rgba(var(--rgb-active-text), 0.14);
|
||||
}
|
||||
|
||||
.autoruns-question-grip-btn:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.autoruns-question-grip-svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-text {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgb(var(--rgb-text-main));
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.4;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-text:hover {
|
||||
color: rgb(var(--rgb-active));
|
||||
}
|
||||
|
||||
.autoruns-generated-question-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: rgba(var(--rgb-background), 0.55);
|
||||
color: rgb(var(--rgb-text-main));
|
||||
padding: 6px 8px;
|
||||
font: inherit;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-generated-question-input {
|
||||
background: rgba(var(--rgb-active-text), 0.14);
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-generated-question-input::placeholder {
|
||||
color: rgba(var(--rgb-active-text), 0.78);
|
||||
}
|
||||
|
||||
.autoruns-add-question-btn {
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: rgb(var(--rgb-surface-focus));
|
||||
color: rgb(var(--rgb-text-main));
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.autoruns-add-question-btn:hover:not(:disabled) {
|
||||
background: rgb(var(--rgb-active));
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.autoruns-add-question-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.autoruns-remove-question-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
flex: 0 0 auto;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
|
@ -1570,6 +1683,7 @@ button:disabled {
|
|||
justify-content: center;
|
||||
box-shadow: none;
|
||||
transition: color 0.15s ease;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.autoruns-remove-question-btn:hover {
|
||||
|
|
@ -1578,6 +1692,14 @@ button:disabled {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-remove-question-btn {
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.autoruns-generated-question-item.editing .autoruns-remove-question-btn:hover {
|
||||
color: rgba(var(--rgb-active-text), 0.82);
|
||||
}
|
||||
|
||||
.autoruns-remove-question-btn:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue