448 lines
16 KiB
JavaScript
448 lines
16 KiB
JavaScript
|
|
import {
|
|||
|
|
APPLICATION_TRANSPORT_MODE_OPTIONS,
|
|||
|
|
normalizeApplicationPreview,
|
|||
|
|
normalizeTransportModeOption
|
|||
|
|
} from '../../utils/expenseApplicationPreview.js'
|
|||
|
|
import {
|
|||
|
|
mergeComposerPrefill,
|
|||
|
|
resolveSuggestedActionPrefill
|
|||
|
|
} from '../../utils/assistantSuggestedActionPrefill.js'
|
|||
|
|
import { ASSISTANT_SCOPE_ACTION_SWITCH } from '../../utils/assistantSessionScope.js'
|
|||
|
|
import { buildSuggestedActionKey } from '../../utils/suggestedActionKey.js'
|
|||
|
|
import {
|
|||
|
|
TRAVEL_PLANNING_ACTION_GENERATE,
|
|||
|
|
TRAVEL_PLANNING_ACTION_SKIP,
|
|||
|
|
buildTravelPlanningRecommendation
|
|||
|
|
} from '../../utils/travelApplicationPlanning.js'
|
|||
|
|
import {
|
|||
|
|
SESSION_TYPE_APPLICATION,
|
|||
|
|
SESSION_TYPE_BUDGET,
|
|||
|
|
canUseBudgetAssistantSession
|
|||
|
|
} from './travelReimbursementConversationModel.js'
|
|||
|
|
import { STEWARD_ASSISTANT_NAME } from './useTravelReimbursementStewardRuntime.js'
|
|||
|
|
import {
|
|||
|
|
buildStewardFieldCompletionContinuation,
|
|||
|
|
buildStewardFieldCompletionRawText
|
|||
|
|
} from './stewardFieldCompletionModel.js'
|
|||
|
|
import { MAX_ATTACHMENTS, VISIBLE_ATTACHMENT_CHIPS } from './travelReimbursementAttachmentModel.js'
|
|||
|
|
|
|||
|
|
export const APPLICATION_PREVIEW_FIELD_ACTION_SET = 'set_application_preview_field'
|
|||
|
|
|
|||
|
|
export function useTravelReimbursementSuggestedActions({
|
|||
|
|
applicationPreviewEditor,
|
|||
|
|
attachedFiles,
|
|||
|
|
buildExpenseSceneSelectionActions,
|
|||
|
|
buildExpenseSceneSelectionMessage,
|
|||
|
|
commitApplicationPreviewEditor,
|
|||
|
|
composerDraft,
|
|||
|
|
composerFilesExpanded,
|
|||
|
|
composerTextareaRef,
|
|||
|
|
createMessage,
|
|||
|
|
currentUser,
|
|||
|
|
emit,
|
|||
|
|
handleGuidedShortcut,
|
|||
|
|
handleGuidedSuggestedAction,
|
|||
|
|
handleSceneSelectionApplicationGate,
|
|||
|
|
lockSuggestedActionMessage,
|
|||
|
|
mergeFilesWithLimit,
|
|||
|
|
messages,
|
|||
|
|
nextTick,
|
|||
|
|
openApplicationPreviewEditor,
|
|||
|
|
persistSessionState,
|
|||
|
|
resolveApplicationPreviewMissingFields,
|
|||
|
|
reviewActionBusy,
|
|||
|
|
router,
|
|||
|
|
scrollToBottom,
|
|||
|
|
sessionSwitchBusy,
|
|||
|
|
startExpenseSceneSelectionAfterIntentConfirmation,
|
|||
|
|
submitComposer,
|
|||
|
|
submitComposerInternal,
|
|||
|
|
submitting,
|
|||
|
|
switchSessionType,
|
|||
|
|
toast,
|
|||
|
|
adjustComposerTextareaHeight
|
|||
|
|
}) {
|
|||
|
|
async function runShortcut(shortcut) {
|
|||
|
|
if (shortcut?.action === 'switch_view' && shortcut?.targetSessionType) {
|
|||
|
|
if (shortcut.targetSessionType === SESSION_TYPE_BUDGET && !canUseBudgetAssistantSession(currentUser.value)) {
|
|||
|
|
toast('目前暂无权限访问预算编制助手')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (shortcut.active) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
await switchSessionType(shortcut.targetSessionType)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (await handleGuidedShortcut(shortcut)) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const prompt = String(shortcut?.prompt || '').trim()
|
|||
|
|
if (!prompt) return
|
|||
|
|
composerDraft.value = prompt
|
|||
|
|
submitComposer()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isSuggestedActionSelected(message, action) {
|
|||
|
|
const selectedKey = String(message?.selectedSuggestedActionKey || '').trim()
|
|||
|
|
return Boolean(selectedKey) && selectedKey === buildSuggestedActionKey(action)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildApplicationPreviewFieldAppliedText(message, fieldLabel = '', value = '') {
|
|||
|
|
const missingFields = resolveApplicationPreviewMissingFields(message)
|
|||
|
|
const resolvedFieldLabel = String(fieldLabel || '补充项').trim()
|
|||
|
|
const resolvedValue = String(value || '').trim()
|
|||
|
|
if (missingFields.length) {
|
|||
|
|
return [
|
|||
|
|
`已更新:**${resolvedFieldLabel}:${resolvedValue}**。`,
|
|||
|
|
'',
|
|||
|
|
`我重新检查了一遍,当前还需要补充:**${missingFields.join('、')}**。`,
|
|||
|
|
'',
|
|||
|
|
'请继续补齐下方核对表里的待补充项;补齐后我再继续推进申请提交。'
|
|||
|
|
].join('\n')
|
|||
|
|
}
|
|||
|
|
return [
|
|||
|
|
`已更新:**${resolvedFieldLabel}:${resolvedValue}**。`,
|
|||
|
|
'',
|
|||
|
|
'我已经重新同步下方申请核对表和费用测算。',
|
|||
|
|
'',
|
|||
|
|
'请继续核查表格内容;如果信息无误,点击确认进入审批环节。'
|
|||
|
|
].join('\n')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isStewardApplicationPreviewFieldCompletion(targetMessage, payload = {}) {
|
|||
|
|
return Boolean(
|
|||
|
|
payload.steward_delegated_field_completion ||
|
|||
|
|
String(targetMessage?.assistantName || '').trim() === STEWARD_ASSISTANT_NAME ||
|
|||
|
|
targetMessage?.stewardPlan
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function continueStewardApplicationFieldCompletion({
|
|||
|
|
targetMessage,
|
|||
|
|
action,
|
|||
|
|
sourcePreview,
|
|||
|
|
fieldKey,
|
|||
|
|
fieldLabel,
|
|||
|
|
value
|
|||
|
|
}) {
|
|||
|
|
if (!lockSuggestedActionMessage(targetMessage, action)) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const continuation = buildStewardFieldCompletionContinuation(
|
|||
|
|
targetMessage?.stewardContinuation || null,
|
|||
|
|
fieldKey,
|
|||
|
|
value
|
|||
|
|
)
|
|||
|
|
const userText = `选择${fieldLabel || '补充项'}:${value}`
|
|||
|
|
const carryText = buildStewardFieldCompletionRawText({
|
|||
|
|
preview: sourcePreview,
|
|||
|
|
fieldKey,
|
|||
|
|
fieldLabel,
|
|||
|
|
value,
|
|||
|
|
continuation
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (!action?.suppressUserEcho) {
|
|||
|
|
messages.value.push(createMessage('user', userText))
|
|||
|
|
}
|
|||
|
|
persistSessionState()
|
|||
|
|
nextTick(scrollToBottom)
|
|||
|
|
|
|||
|
|
await submitComposerInternal({
|
|||
|
|
rawText: carryText,
|
|||
|
|
userText,
|
|||
|
|
pendingText: '小财管家正在根据补齐信息查询票据并测算费用...',
|
|||
|
|
files: [],
|
|||
|
|
skipScopeGuard: true,
|
|||
|
|
skipApplicationModelReview: true,
|
|||
|
|
skipStewardPlan: true,
|
|||
|
|
skipUserMessage: true,
|
|||
|
|
sessionTypeOverride: SESSION_TYPE_APPLICATION,
|
|||
|
|
stewardContinuation: continuation
|
|||
|
|
})
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function applyApplicationPreviewFieldAction(message, action) {
|
|||
|
|
const payload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
|||
|
|
const fieldKey = String(payload.field_key || payload.fieldKey || '').trim()
|
|||
|
|
const fieldLabel = String(payload.field_label || payload.fieldLabel || action?.label || '').trim()
|
|||
|
|
let value = String(payload.value || action?.label || '').trim()
|
|||
|
|
const targetMessage = messages.value.find((item) => String(item.id || '') === String(message?.id || '')) || message
|
|||
|
|
const sourcePreview = targetMessage?.applicationPreview ||
|
|||
|
|
payload.applicationPreview ||
|
|||
|
|
payload.application_preview ||
|
|||
|
|
payload.preview ||
|
|||
|
|
null
|
|||
|
|
if (!sourcePreview || !fieldKey || !value) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if (fieldKey === 'transportMode') {
|
|||
|
|
value = normalizeTransportModeOption(value, '')
|
|||
|
|
}
|
|||
|
|
if (fieldKey === 'transportMode' && !APPLICATION_TRANSPORT_MODE_OPTIONS.includes(value)) {
|
|||
|
|
toast('请选择有效的出行方式。')
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if (isStewardApplicationPreviewFieldCompletion(targetMessage, payload)) {
|
|||
|
|
return continueStewardApplicationFieldCompletion({
|
|||
|
|
targetMessage,
|
|||
|
|
action,
|
|||
|
|
sourcePreview,
|
|||
|
|
fieldKey,
|
|||
|
|
fieldLabel,
|
|||
|
|
value
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
if (!lockSuggestedActionMessage(targetMessage, action)) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
targetMessage.applicationPreview = normalizeApplicationPreview(sourcePreview)
|
|||
|
|
messages.value.push(createMessage('user', `选择${fieldLabel || '补充项'}:${value}`))
|
|||
|
|
openApplicationPreviewEditor(targetMessage, fieldKey, targetMessage.applicationPreview?.fields?.[fieldKey] || '')
|
|||
|
|
applicationPreviewEditor.value = {
|
|||
|
|
...applicationPreviewEditor.value,
|
|||
|
|
draftValue: value
|
|||
|
|
}
|
|||
|
|
await commitApplicationPreviewEditor(targetMessage)
|
|||
|
|
if (String(targetMessage.assistantName || '').trim() === STEWARD_ASSISTANT_NAME || targetMessage.stewardPlan) {
|
|||
|
|
targetMessage.assistantName = STEWARD_ASSISTANT_NAME
|
|||
|
|
targetMessage.text = buildApplicationPreviewFieldAppliedText(targetMessage, fieldLabel, value)
|
|||
|
|
const nextMeta = Array.isArray(targetMessage.meta) ? targetMessage.meta : []
|
|||
|
|
targetMessage.meta = Array.from(new Set([
|
|||
|
|
STEWARD_ASSISTANT_NAME,
|
|||
|
|
resolveApplicationPreviewMissingFields(targetMessage).length ? '等待补充' : '等待用户确认',
|
|||
|
|
...nextMeta.filter((item) => String(item || '').trim() && item !== STEWARD_ASSISTANT_NAME)
|
|||
|
|
])).slice(0, 4)
|
|||
|
|
}
|
|||
|
|
persistSessionState()
|
|||
|
|
nextTick(scrollToBottom)
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function pushExpenseSceneSelectionPrompt(originalMessage) {
|
|||
|
|
const sourceText = String(originalMessage || '').trim()
|
|||
|
|
if (!sourceText) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
startExpenseSceneSelectionAfterIntentConfirmation(sourceText)
|
|||
|
|
messages.value.push(createMessage('user', '我要报销'))
|
|||
|
|
messages.value.push(createMessage('assistant', buildExpenseSceneSelectionMessage(sourceText), [], {
|
|||
|
|
meta: ['等待选择场景'],
|
|||
|
|
suggestedActions: buildExpenseSceneSelectionActions(sourceText)
|
|||
|
|
}))
|
|||
|
|
nextTick(scrollToBottom)
|
|||
|
|
persistSessionState()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function applySuggestedActionPrefill(action) {
|
|||
|
|
const prefillText = resolveSuggestedActionPrefill(action)
|
|||
|
|
if (!prefillText) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
composerDraft.value = mergeComposerPrefill(composerDraft.value, prefillText)
|
|||
|
|
nextTick(() => {
|
|||
|
|
adjustComposerTextareaHeight()
|
|||
|
|
composerTextareaRef.value?.focus()
|
|||
|
|
})
|
|||
|
|
persistSessionState()
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function handleSuggestedAction(message, action) {
|
|||
|
|
const actionType = String(action?.action_type || '').trim()
|
|||
|
|
if (!actionType || submitting.value || reviewActionBusy.value || sessionSwitchBusy.value) return
|
|||
|
|
if (message?.suggestedActionsLocked) return
|
|||
|
|
if (applySuggestedActionPrefill(action)) return
|
|||
|
|
if (await handleGuidedSuggestedAction(message, action)) return
|
|||
|
|
if (await handleSceneSelectionApplicationGate(message, action)) return
|
|||
|
|
|
|||
|
|
if (actionType === APPLICATION_PREVIEW_FIELD_ACTION_SET) {
|
|||
|
|
await applyApplicationPreviewFieldAction(message, action)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === 'open_application_detail') {
|
|||
|
|
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
|||
|
|
const claimId = String(actionPayload.claim_id || actionPayload.claimId || '').trim()
|
|||
|
|
if (!claimId) {
|
|||
|
|
toast('当前没有可查看的申请单据。')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
await router.push({
|
|||
|
|
name: 'app-document-detail',
|
|||
|
|
params: { requestId: claimId }
|
|||
|
|
})
|
|||
|
|
emit('close')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === 'open_receipt_folder') {
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
await router.push({ name: 'app-receiptFolder' })
|
|||
|
|
emit('close')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === 'continue_upload_with_unlinked_receipts') {
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
|||
|
|
await submitComposer({
|
|||
|
|
rawText: String(actionPayload.raw_text || composerDraft.value || '').trim(),
|
|||
|
|
files: Array.from(attachedFiles.value || []),
|
|||
|
|
skipReceiptFolderUnlinkedPrompt: true
|
|||
|
|
})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === TRAVEL_PLANNING_ACTION_GENERATE) {
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
const sourcePreview = action?.payload?.applicationPreview || action?.payload?.preview || null
|
|||
|
|
const sourceDraftPayload = action?.payload?.draftPayload || action?.payload?.draft_payload || null
|
|||
|
|
const recommendation = buildTravelPlanningRecommendation(sourcePreview, sourceDraftPayload)
|
|||
|
|
if (recommendation) {
|
|||
|
|
messages.value.push(createMessage('user', '生成行程规划'))
|
|||
|
|
messages.value.push(createMessage('assistant', recommendation, [], {
|
|||
|
|
meta: ['行程规划建议']
|
|||
|
|
}))
|
|||
|
|
nextTick(scrollToBottom)
|
|||
|
|
persistSessionState()
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === TRAVEL_PLANNING_ACTION_SKIP) {
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
messages.value.push(createMessage('assistant', '好的,本次先保留申请结果。后续需要规划交通或酒店时,可以继续在这里告诉我。', [], {
|
|||
|
|
meta: ['暂不规划']
|
|||
|
|
}))
|
|||
|
|
nextTick(scrollToBottom)
|
|||
|
|
persistSessionState()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === ASSISTANT_SCOPE_ACTION_SWITCH) {
|
|||
|
|
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
|||
|
|
const targetSessionType = String(actionPayload.session_type || '').trim()
|
|||
|
|
if (!targetSessionType) return
|
|||
|
|
if (targetSessionType === SESSION_TYPE_BUDGET && !canUseBudgetAssistantSession(currentUser.value)) {
|
|||
|
|
toast('目前暂无权限访问预算编制助手')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
const carryText = String(actionPayload.carry_text || '').trim()
|
|||
|
|
const carryFiles = actionPayload.carry_files ? Array.from(attachedFiles.value || []) : []
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
if (String(actionPayload.steward_plan_id || '').trim()) {
|
|||
|
|
const confirmedByText = Boolean(action.confirmedByText)
|
|||
|
|
delete action.confirmedByText
|
|||
|
|
await submitComposerInternal({
|
|||
|
|
rawText: carryText,
|
|||
|
|
userText: action.label || '确定',
|
|||
|
|
pendingText: targetSessionType === SESSION_TYPE_APPLICATION
|
|||
|
|
? '小财管家正在调用申请助手生成申请单核对结果...'
|
|||
|
|
: '小财管家正在调用报销助手整理报销核对结果...',
|
|||
|
|
files: carryFiles,
|
|||
|
|
skipScopeGuard: true,
|
|||
|
|
skipApplicationModelReview: targetSessionType === SESSION_TYPE_APPLICATION,
|
|||
|
|
skipStewardSlotDecision: targetSessionType === SESSION_TYPE_APPLICATION,
|
|||
|
|
skipStewardPlan: true,
|
|||
|
|
skipUserMessage: confirmedByText,
|
|||
|
|
sessionTypeOverride: targetSessionType,
|
|||
|
|
stewardContinuation: {
|
|||
|
|
planId: String(actionPayload.steward_plan_id || '').trim(),
|
|||
|
|
currentTaskId: String(actionPayload.steward_next_task_id || '').trim(),
|
|||
|
|
currentTask: actionPayload.steward_current_task || null,
|
|||
|
|
remainingTasks: Array.isArray(actionPayload.steward_remaining_tasks)
|
|||
|
|
? actionPayload.steward_remaining_tasks
|
|||
|
|
: []
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
await switchSessionType(targetSessionType)
|
|||
|
|
if (carryText) {
|
|||
|
|
composerDraft.value = carryText
|
|||
|
|
}
|
|||
|
|
if (carryFiles.length) {
|
|||
|
|
const fileMergeResult = mergeFilesWithLimit([], carryFiles, MAX_ATTACHMENTS)
|
|||
|
|
attachedFiles.value = fileMergeResult.files
|
|||
|
|
composerFilesExpanded.value = fileMergeResult.files.length > VISIBLE_ATTACHMENT_CHIPS
|
|||
|
|
}
|
|||
|
|
nextTick(() => {
|
|||
|
|
adjustComposerTextareaHeight()
|
|||
|
|
scrollToBottom()
|
|||
|
|
})
|
|||
|
|
persistSessionState()
|
|||
|
|
if (actionPayload.auto_submit && carryText) {
|
|||
|
|
await submitComposer({
|
|||
|
|
rawText: carryText,
|
|||
|
|
userText: action.label || '确认继续处理',
|
|||
|
|
pendingText: '正在按确认内容继续处理...',
|
|||
|
|
files: carryFiles,
|
|||
|
|
skipScopeGuard: true
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType === 'confirm_expense_intent') {
|
|||
|
|
const originalMessage = String(action?.payload?.original_message || message?.text || '').trim()
|
|||
|
|
if (!originalMessage) return
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
pushExpenseSceneSelectionPrompt(originalMessage)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actionType !== 'select_expense_type') {
|
|||
|
|
const fallbackText = String(action?.description || action?.label || '').trim()
|
|||
|
|
if (!fallbackText) return
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
await submitComposer({
|
|||
|
|
rawText: fallbackText,
|
|||
|
|
userText: fallbackText,
|
|||
|
|
pendingText: '正在继续处理...'
|
|||
|
|
})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
|||
|
|
const expenseType = String(actionPayload.expense_type || '').trim()
|
|||
|
|
const expenseTypeLabel = String(actionPayload.expense_type_label || action?.label || '').trim()
|
|||
|
|
const originalMessage = String(actionPayload.original_message || message?.text || '').trim()
|
|||
|
|
if (!expenseTypeLabel || !originalMessage) return
|
|||
|
|
|
|||
|
|
if (!lockSuggestedActionMessage(message, action)) return
|
|||
|
|
await submitComposer({
|
|||
|
|
rawText: `${originalMessage}\n用户选择报销场景:${expenseTypeLabel}`,
|
|||
|
|
userText: `选择${expenseTypeLabel}`,
|
|||
|
|
pendingText: `已选择${expenseTypeLabel},正在按该场景识别...`,
|
|||
|
|
systemGenerated: true,
|
|||
|
|
extraContext: {
|
|||
|
|
draft_claim_id: '',
|
|||
|
|
user_input_text: originalMessage,
|
|||
|
|
expense_scene_selection: {
|
|||
|
|
expense_type: expenseType,
|
|||
|
|
expense_type_label: expenseTypeLabel,
|
|||
|
|
original_message: originalMessage
|
|||
|
|
},
|
|||
|
|
review_form_values: {
|
|||
|
|
expense_type: expenseTypeLabel
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
handleSuggestedAction,
|
|||
|
|
isSuggestedActionSelected,
|
|||
|
|
runShortcut
|
|||
|
|
}
|
|||
|
|
}
|