mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
924 lines
33 KiB
924 lines
33 KiB
<script setup lang="ts"> |
|
import { UITypes, isVirtualCol } from 'nocodb-sdk' |
|
import { type ColumnType } from 'nocodb-sdk' |
|
import { generateUniqueColumnName } from '~/helpers/parsers/parserHelpers' |
|
|
|
const props = defineProps<{ |
|
value: any |
|
submitBtnLabel: { |
|
label: string |
|
loadingLabel: string |
|
} |
|
saving: boolean |
|
}>() |
|
|
|
const emit = defineEmits(['update:value', 'navigateToIntegrations', 'onSubmit']) |
|
|
|
const vModel = useVModel(props, 'value', emit) |
|
|
|
const { submitBtnLabel, saving } = toRefs(props) |
|
|
|
const meta = inject(MetaInj, ref()) |
|
|
|
const workspaceStore = useWorkspace() |
|
const { activeWorkspaceId } = storeToRefs(workspaceStore) |
|
|
|
const { |
|
isAiButtonConfigModalOpen, |
|
isEdit, |
|
validateInfos, |
|
column, |
|
loadData, |
|
formattedData, |
|
disableSubmitBtn, |
|
tableExplorerColumns, |
|
} = useColumnCreateStoreOrThrow() |
|
|
|
const { aiIntegrationAvailable, aiLoading, aiError, generateRows } = useNocoAi() |
|
|
|
const isOpenConfigModal = ref<boolean>(false) |
|
|
|
const isOpenSelectOutputFieldDropdown = ref<boolean>(false) |
|
|
|
const isOpenSelectRecordDropdown = ref<boolean>(false) |
|
|
|
const fieldTitle = computed(() => { |
|
return ( |
|
vModel.value.title || |
|
generateUniqueColumnName({ |
|
formState: vModel.value, |
|
tableExplorerColumns: tableExplorerColumns?.value, |
|
metaColumns: meta.value?.columns || [], |
|
}) |
|
) |
|
}) |
|
|
|
const previewOutputRow = ref<Row>({ |
|
row: {}, |
|
oldRow: {}, |
|
rowMeta: {}, |
|
}) |
|
|
|
const generatingPreview = ref(false) |
|
|
|
const isAlreadyGenerated = ref(false) |
|
|
|
const isLoadingViewData = ref(false) |
|
|
|
const loadViewData = async () => { |
|
if (!formattedData.value.length && !isLoadingViewData.value) { |
|
isLoadingViewData.value = true |
|
|
|
await loadData(undefined, false) |
|
|
|
await ncDelay(250) |
|
|
|
isLoadingViewData.value = false |
|
} |
|
} |
|
|
|
const displayField = computed(() => meta.value?.columns?.find((c) => c?.pv) ?? null) |
|
|
|
const sampleRecords = computed< |
|
{ |
|
label: any |
|
value: any |
|
row: any |
|
}[] |
|
>(() => { |
|
return (formattedData.value || []) |
|
.map((row) => { |
|
const pk = extractPkFromRow(unref(row.row), meta.value?.columns || []) |
|
|
|
const displayValue = row.row?.[displayField.value?.title] |
|
|
|
return { |
|
label: displayValue, |
|
value: pk, |
|
row, |
|
} |
|
}) |
|
.filter((r) => !!(r.label && r.value)) |
|
}) |
|
|
|
const selectedRecordPk = ref('') |
|
|
|
const selectedRecord = computed(() => { |
|
return ( |
|
sampleRecords.value.find((r) => r.value === selectedRecordPk.value) || { |
|
row: { |
|
row: {}, |
|
oldRow: {}, |
|
rowMeta: { new: true }, |
|
}, |
|
label: '', |
|
value: '', |
|
} |
|
) |
|
}) |
|
|
|
// AI options |
|
const availableFields = computed(() => { |
|
if (!meta.value?.columns) return [] |
|
return meta.value.columns.filter( |
|
(c) => |
|
c.title && |
|
!c.system && |
|
![UITypes.ID, UITypes.Button, UITypes.Links].includes(c.uidt) && |
|
(isEdit.value ? column.value?.id !== c.id : true), |
|
) |
|
}) |
|
|
|
const inputColumns = computed(() => { |
|
return availableFields.value.filter((f) => { |
|
return vModel.value.formula_raw?.includes(`{${f.title}}`) |
|
}) |
|
}) |
|
|
|
const outputFieldOptions = computed(() => { |
|
if (!meta.value?.columns) return [] |
|
return meta.value.columns.filter( |
|
(c) => |
|
!c.system && |
|
!c.pk && |
|
c.id !== column.value?.id && |
|
![UITypes.Attachment, UITypes.Button, UITypes.Links].includes(c.uidt) && |
|
!isReadOnlyVirtualCell(c), |
|
) |
|
}) |
|
|
|
const outputColumnIds = computed({ |
|
get: () => { |
|
if (!vModel.value?.output_column_ids?.length) return [] |
|
const colIds = vModel.value.output_column_ids?.split(',') || [] |
|
return colIds |
|
}, |
|
set: (val) => { |
|
vModel.value.output_column_ids = val.join(',') |
|
}, |
|
}) |
|
|
|
const removeFromOutputFieldOptions = (id: string) => { |
|
outputColumnIds.value = outputColumnIds.value.filter((op) => op !== id) |
|
} |
|
|
|
const cellIcon = (column: ColumnType) => |
|
h(isVirtualCol(column) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), { |
|
columnMeta: column, |
|
}) |
|
|
|
const generate = async () => { |
|
if (!selectedRecordPk.value || !outputColumnIds.value.length) return |
|
|
|
generatingPreview.value = true |
|
|
|
const res = await generateRows( |
|
meta.value.id!, |
|
{ |
|
title: vModel.value?.title, |
|
prompt_raw: vModel.value.formula_raw, |
|
fk_integration_id: vModel.value.fk_integration_id, |
|
uidt: UITypes.Button, |
|
output_column_ids: outputColumnIds.value.join(','), |
|
}, |
|
[selectedRecordPk.value], |
|
false, |
|
) |
|
|
|
if (res?.length) { |
|
previewOutputRow.value.row = res[0] |
|
|
|
isAlreadyGenerated.value = true |
|
} |
|
|
|
generatingPreview.value = false |
|
} |
|
|
|
enum ExpansionPanelKeys { |
|
input = 'input', |
|
output = 'output', |
|
} |
|
|
|
const expansionPanel = ref<ExpansionPanelKeys[]>([ExpansionPanelKeys.output]) |
|
|
|
const handleUpdateExpansionPanel = (key: ExpansionPanelKeys) => { |
|
if (expansionPanel.value.includes(key)) { |
|
expansionPanel.value = expansionPanel.value.filter((k) => k !== key) |
|
} else { |
|
if (key === ExpansionPanelKeys.input && !inputColumns.value.length) { |
|
return |
|
} |
|
expansionPanel.value.push(key) |
|
} |
|
} |
|
|
|
// provide the following to override the default behavior and enable input fields like in form |
|
provide(ActiveCellInj, ref(true)) |
|
provide(IsFormInj, ref(true)) |
|
|
|
watch(isOpenConfigModal, (newValue) => { |
|
if (newValue) { |
|
isAiButtonConfigModalOpen.value = true |
|
} else { |
|
setTimeout(() => { |
|
isAiButtonConfigModalOpen.value = false |
|
}, 500) |
|
} |
|
}) |
|
|
|
watch(isOpenSelectRecordDropdown, (newValue) => { |
|
if (newValue) { |
|
loadViewData() |
|
} |
|
}) |
|
|
|
watch( |
|
() => inputColumns.value.length, |
|
(newValue) => { |
|
if (newValue) return |
|
|
|
handleUpdateExpansionPanel(ExpansionPanelKeys.input) |
|
}, |
|
{ |
|
immediate: true, |
|
}, |
|
) |
|
|
|
const previewPanelDom = ref<HTMLElement>() |
|
|
|
const isPreviewPanelOnScrollTop = ref(false) |
|
|
|
const checkScrollTopMoreThanZero = () => { |
|
if (previewPanelDom.value) { |
|
if (previewPanelDom.value.scrollTop > 0) { |
|
isPreviewPanelOnScrollTop.value = true |
|
} else { |
|
isPreviewPanelOnScrollTop.value = false |
|
} |
|
} |
|
return false |
|
} |
|
|
|
watch( |
|
[() => outputColumnIds.value.length, () => vModel.value.formula_raw?.length], |
|
() => { |
|
if (!vModel.value.formula_raw || !outputColumnIds.value.length) { |
|
disableSubmitBtn.value = true |
|
} else { |
|
disableSubmitBtn.value = false |
|
} |
|
}, |
|
{ |
|
immediate: true, |
|
}, |
|
) |
|
|
|
onMounted(() => { |
|
aiError.value = '' |
|
if (!vModel.value.formula_raw || !outputColumnIds.value.length) { |
|
disableSubmitBtn.value = true |
|
} else { |
|
disableSubmitBtn.value = false |
|
} |
|
}) |
|
|
|
onBeforeUnmount(() => { |
|
disableSubmitBtn.value = false |
|
aiError.value = '' |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div class="relative flex flex-col gap-4"> |
|
<AiIntegrationNotFound v-if="!aiIntegrationAvailable" /> |
|
<template v-else-if="!!aiError"> </template> |
|
<template v-else> |
|
<NcButton type="secondary" size="small" theme="ai" @click.stop="isOpenConfigModal = true"> |
|
<div class="flex items-center justify-center gap-2"> |
|
<GeneralIcon icon="ncSettings" class="text-[14px] !text-current" /> |
|
{{ $t('labels.configureAiButton') }} |
|
</div> |
|
</NcButton> |
|
</template> |
|
|
|
<NcModal |
|
v-model:visible="isOpenConfigModal" |
|
class="nc-ai-button-config-modal" |
|
:show-separator="false" |
|
size="lg" |
|
wrap-class-name="nc-ai-button-config-modal-wrapper" |
|
nc-modal-class-name="!p-0" |
|
> |
|
<div class="h-full flex flex-col"> |
|
<div class="w-full px-4 py-3 flex gap-2 border-b-1 border-nc-border-gray-medium"> |
|
<div class="flex-1 flex items-center gap-3 text-xl font-semibold"> |
|
<GeneralIcon icon="cellAiButton" class="flex-none h-6 w-6" /> |
|
|
|
{{ fieldTitle }} |
|
</div> |
|
|
|
<!-- Todo: add docs link --> |
|
<NcButton size="small" type="secondary" @click.stop="navigateTo('/', { open: navigateToBlankTargetOpenOption })"> |
|
<template #icon> |
|
<GeneralIcon icon="externalLink" class="text-gray-600" /> |
|
</template> |
|
|
|
{{ $t('activity.goToDocs') }} |
|
</NcButton> |
|
|
|
<NcButton |
|
size="small" |
|
type="primary" |
|
theme="ai" |
|
:disabled="disableSubmitBtn || saving" |
|
:label="submitBtnLabel.label" |
|
:loading-label="submitBtnLabel.loadingLabel" |
|
:loading="saving" |
|
@click.stop="emit('onSubmit')" |
|
> |
|
{{ submitBtnLabel.label }} |
|
<template #loading> |
|
{{ submitBtnLabel.loadingLabel }} |
|
</template> |
|
</NcButton> |
|
<NcButton size="small" type="text" @click.stop="isOpenConfigModal = false"> |
|
<GeneralIcon icon="close" class="text-gray-600" /> |
|
</NcButton> |
|
</div> |
|
|
|
<div class="h-[calc(100%_-_58px)]"> |
|
<div class="h-full flex"> |
|
<!-- Left side --> |
|
<div class="h-full w-1/2 nc-scrollbar-thin"> |
|
<a-form |
|
v-model="vModel" |
|
no-style |
|
layout="vertical" |
|
class="nc-ai-button-config-left-section flex flex-col gap-6 h-full" |
|
@submit.prevent |
|
> |
|
<div class="flex items-center"> |
|
<div class="text-base text-nc-content-gray font-bold flex-1"> |
|
{{ $t('labels.configuration') }} |
|
</div> |
|
<div class="-my-1.5"> |
|
<AiSettings |
|
v-model:fk-integration-id="vModel.fk_integration_id" |
|
v-model:model="vModel.model" |
|
v-model:randomness="vModel.randomness" |
|
:workspace-id="activeWorkspaceId" |
|
:show-tooltip="false" |
|
placement="bottomRight" |
|
> |
|
<NcButton size="xs" theme="ai" class="!px-1" type="text"> |
|
<GeneralIcon icon="settings" /> |
|
</NcButton> |
|
</AiSettings> |
|
</div> |
|
</div> |
|
|
|
<a-form-item class="!my-0" v-bind="validateInfos.formula_raw"> |
|
<template #label> |
|
<span> Input Prompt </span> |
|
</template> |
|
<div class="nc-prompt-input-wrapper bg-nc-bg-gray-light rounded-lg w-full"> |
|
<AiPromptWithFields |
|
v-model="vModel.formula_raw" |
|
:options="availableFields" |
|
placeholder="Enter prompt here..." |
|
prompt-field-tag-class-name="!bg-nc-bg-gray-medium !text-nc-content-gray" |
|
/> |
|
<div class="rounded-b-lg flex items-center gap-2 p-1"> |
|
<GeneralIcon icon="info" class="!text-nc-content-purple-medium h-4 w-4" /> |
|
<span class="text-xs text-nc-content-gray-subtle2" |
|
>Mention fields using curly braces, e.g. |
|
<span class="text-nc-content-purple-dark">{Field name}</span>.</span |
|
> |
|
</div> |
|
</div> |
|
</a-form-item> |
|
|
|
<a-form-item v-bind="validateInfos.output_column_ids" class="!my-0"> |
|
<div class="flex items-center"> |
|
<span class="flex-1"> Select fields to generate data </span> |
|
|
|
<NcDropdown v-model:visible="isOpenSelectOutputFieldDropdown" placement="bottomRight"> |
|
<NcButton size="small" type="secondary" @click.stop> |
|
<div class="flex items-center gap-2"> |
|
<GeneralIcon icon="plus" class="!text-current" /> |
|
Select fields |
|
</div> |
|
</NcButton> |
|
|
|
<template #overlay> |
|
<NcList |
|
v-model:value="outputColumnIds" |
|
v-model:open="isOpenSelectOutputFieldDropdown" |
|
:list="outputFieldOptions" |
|
search-input-placeholder="Search" |
|
option-label-key="title" |
|
option-value-key="id" |
|
:close-on-select="false" |
|
is-multi-select |
|
> |
|
<template #listItem="{ option, isSelected }"> |
|
<div class="inline-flex items-center gap-2 flex-1 truncate"> |
|
<component :is="cellIcon(option)" class="!mx-0" /> |
|
<NcTooltip class="truncate flex-1" show-on-truncate-only> |
|
<template #title> |
|
{{ option?.title }} |
|
</template> |
|
{{ option?.title }} |
|
</NcTooltip> |
|
</div> |
|
<NcCheckbox :checked="isSelected()" /> |
|
</template> |
|
</NcList> |
|
</template> |
|
</NcDropdown> |
|
</div> |
|
<div v-if="outputFieldOptions.length" class="flex flex-wrap gap-2 mt-2"> |
|
<template v-for="op in outputFieldOptions"> |
|
<a-tag v-if="outputColumnIds.includes(op.id)" :key="op.id" class="nc-ai-button-output-field"> |
|
<div class="flex flex-row items-center gap-1 py-[3px] text-sm"> |
|
<component :is="cellIcon(op)" class="!mx-0" /> |
|
<span>{{ op.title }}</span> |
|
<div class="flex items-center p-0.5 mt-0.5"> |
|
<GeneralIcon |
|
icon="close" |
|
class="h-4 w-4 cursor-pointer opacity-80" |
|
@click="removeFromOutputFieldOptions(op.id)" |
|
/> |
|
</div> |
|
</div> |
|
</a-tag> |
|
</template> |
|
</div> |
|
</a-form-item> |
|
</a-form> |
|
</div> |
|
<!-- Right side --> |
|
<div |
|
ref="previewPanelDom" |
|
class="h-full w-1/2 bg-nc-bg-gray-extralight nc-scrollbar-thin flex flex-col relative" |
|
@scroll.passive="checkScrollTopMoreThanZero" |
|
> |
|
<div |
|
class="nc-ai-button-config-right-section !pt-6 sticky top-0 bg-nc-bg-gray-extralight z-10" |
|
:class="{ |
|
'border-b-1 border-nc-border-gray-medium': isPreviewPanelOnScrollTop, |
|
}" |
|
> |
|
<div class="text-base text-nc-content-gray font-bold"> |
|
{{ $t('labels.preview') }} |
|
</div> |
|
<a-form-item class="!mb-0 !mt-2"> |
|
<div class="mb-2 text-sm text-nc-content-gray-subtle2">Select sample record</div> |
|
<div class="flex items-center relative rounded-lg border-1 border-purple-200 bg-purple-50 h-8"> |
|
<NcDropdown |
|
v-model:visible="isOpenSelectRecordDropdown" |
|
placement="bottomLeft" |
|
overlay-class-name="!min-w-64" |
|
> |
|
<div |
|
class="absolute left-0 top-0 flex-1 flex items-center gap-2 px-2 cursor-pointer h-8 rounded-lg rounded-r-none bg-white border-1 border-purple-200 transition-all -mt-[1px] -ml-[1px]" |
|
:class="{ |
|
'w-[calc(100%_-_132.5px)]': !(aiLoading && generatingPreview), |
|
'w-[calc(100%_-_145.5px)]': aiLoading && generatingPreview, |
|
'!rounded-r-lg shadow-selected-ai border-nc-border-purple z-11': isOpenSelectRecordDropdown, |
|
'shadow-default hover:shadow-hover': !isOpenSelectRecordDropdown, |
|
}" |
|
> |
|
<NcTooltip |
|
v-if="selectedRecord?.label" |
|
class="truncate flex-1 text-nc-content-purple-dark font-semibold" |
|
show-on-truncate-only |
|
:disabled="isOpenSelectRecordDropdown" |
|
> |
|
<template #title> |
|
<LazySmartsheetPlainCell v-model="selectedRecord.label" :column="displayField" /> |
|
</template> |
|
<LazySmartsheetPlainCell v-model="selectedRecord.label" :column="displayField" /> |
|
</NcTooltip> |
|
|
|
<div v-else class="flex-1 text-nc-content-gray-muted">- Select record -</div> |
|
<GeneralIcon |
|
icon="chevronDown" |
|
class="flex-none opacity-60" |
|
:class="{ |
|
'transform rotate-180': isOpenSelectRecordDropdown, |
|
}" |
|
/> |
|
</div> |
|
|
|
<template #overlay> |
|
<div |
|
v-if="isLoadingViewData" |
|
class="w-full relative flex flex-col items-center justify-center gap-2 min-h-25 text-nc-content-brand" |
|
> |
|
<GeneralLoader size="large" class="flex-none" /> |
|
Loading records |
|
</div> |
|
<NcList |
|
v-else |
|
v-model:value="selectedRecordPk" |
|
v-model:open="isOpenSelectRecordDropdown" |
|
:list="sampleRecords" |
|
> |
|
<template #listItem="{ option, isSelected }"> |
|
<div class="inline-flex items-center gap-2 flex-1 truncate"> |
|
<NcTooltip class="truncate flex-1" show-on-truncate-only> |
|
<template #title> |
|
<div> |
|
<LazySmartsheetPlainCell v-model="option.label" :column="displayField" /> |
|
</div> |
|
</template> |
|
<LazySmartsheetPlainCell v-model="option.label" :column="displayField" /> |
|
</NcTooltip> |
|
|
|
<GeneralIcon |
|
v-if="isSelected()" |
|
id="nc-selected-item-icon" |
|
icon="check" |
|
class="flex-none text-primary w-4 h-4" |
|
/> |
|
</div> |
|
</template> |
|
</NcList> |
|
</template> |
|
</NcDropdown> |
|
<NcTooltip |
|
:disabled="!!(selectedRecordPk && outputColumnIds.length && vModel.formula_raw)" |
|
class="absolute right-0 top-0" |
|
> |
|
<template #title> |
|
{{ |
|
!vModel.formula_raw |
|
? 'Prompt required for AI Button' |
|
: !outputColumnIds.length |
|
? 'At least one output field is required for preview' |
|
: !selectedRecordPk |
|
? 'Select sample record first' |
|
: '' |
|
}} |
|
</template> |
|
<NcButton |
|
size="small" |
|
type="secondary" |
|
class="nc-ai-button-test-generate" |
|
:disabled="aiLoading || !selectedRecordPk || !outputColumnIds.length || !vModel.formula_raw" |
|
:loading="aiLoading && generatingPreview" |
|
@click.stop="generate" |
|
> |
|
<template #icon> |
|
<GeneralIcon icon="ncAutoAwesome" class="h-4 w-4" /> |
|
</template> |
|
<template #loadingIcon> |
|
<GeneralLoader class="!text-current" size="regular" /> |
|
</template> |
|
<div class="flex items-center gap-2"> |
|
{{ aiLoading && generatingPreview ? 'Test Generating' : 'Test Generate' }} |
|
</div> |
|
</NcButton> |
|
</NcTooltip> |
|
</div> |
|
</a-form-item> |
|
|
|
<div v-if="aiError" class="py-3 pl-3 pr-2 flex items-center gap-3 bg-nc-bg-red-light rounded-lg"> |
|
<GeneralIcon icon="ncInfoSolid" class="flex-none !text-nc-content-red-dark w-4 h-4" /> |
|
|
|
<div class="text-sm text-nc-content-gray-subtle flex-1 max-w-[calc(100%_-_24px)]"> |
|
<NcTooltip class="truncate" show-on-truncate-only> |
|
<template #title> |
|
{{ aiError }} |
|
</template> |
|
{{ aiError }} |
|
</NcTooltip> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<a-collapse v-model:active-key="expansionPanel" ghost class="flex-1 flex flex-col"> |
|
<template #expandIcon> </template> |
|
<a-collapse-panel |
|
:key="ExpansionPanelKeys.input" |
|
collapsible="disabled" |
|
class="nc-ai-button-config-right-section" |
|
> |
|
<template #header> |
|
<div class="flex"> |
|
<div |
|
class="text-sm text-nc-content-gray-subtle2 font-bold flex items-center gap-2.5 min-h-7" |
|
@click="handleUpdateExpansionPanel(ExpansionPanelKeys.input)" |
|
> |
|
Input fields |
|
|
|
<template v-if="inputColumns.length"> |
|
<a-tag class="!rounded-md !bg-nc-bg-brand !text-nc-content-brand !border-none !mx-0"> |
|
{{ inputColumns.length }}</a-tag |
|
> |
|
|
|
<NcButton size="xs" type="text" class="hover:!bg-nc-bg-gray-dark !px-1"> |
|
<GeneralIcon |
|
icon="arrowRight" |
|
class="transform" |
|
:class="{ |
|
'rotate-270': expansionPanel.includes(ExpansionPanelKeys.input), |
|
}" |
|
/> |
|
</NcButton> |
|
</template> |
|
</div> |
|
</div> |
|
</template> |
|
|
|
<div class="flex flex-col gap-4"> |
|
<LazySmartsheetRow :row="selectedRecord.row"> |
|
<template v-for="field in inputColumns"> |
|
<a-form-item |
|
v-if="field.title" |
|
:key="`${field.id}-${generatingPreview}`" |
|
:name="field.title" |
|
class="!my-0 nc-input-required-error" |
|
> |
|
<div class="flex items-center gap-2 text-nc-content-gray-subtle2 mb-2"> |
|
<component :is="cellIcon(field)" class="!mx-0" /> |
|
<NcTooltip class="truncate flex-1" show-on-truncate-only> |
|
<template #title> |
|
{{ field?.title }} |
|
</template> |
|
{{ field?.title }} |
|
</NcTooltip> |
|
</div> |
|
|
|
<LazySmartsheetDivDataCell |
|
class="relative flex items-center min-h-8 children:h-full" |
|
:class="{ |
|
'!select-text nc-system-field': isReadOnlyVirtualCell(field), |
|
'!select-text nc-readonly-div-data-cell': !isReadOnlyVirtualCell(field), |
|
}" |
|
> |
|
<LazySmartsheetVirtualCell |
|
v-if="isVirtualCol(field)" |
|
:model-value="selectedRecord?.row?.row?.[field.title]" |
|
class="mt-0 nc-input nc-cell" |
|
:class="[`nc-form-input-${field.title?.replaceAll(' ', '')}`, { readonly: field?.read_only }]" |
|
:column="field" |
|
:read-only="true" |
|
/> |
|
|
|
<LazySmartsheetCell |
|
v-else |
|
:model-value="selectedRecord?.row?.row?.[field.title]" |
|
class="nc-input truncate" |
|
:class="[`nc-form-input-${field.title?.replaceAll(' ', '')}`, { readonly: field?.read_only }]" |
|
:column="field" |
|
:edit-enabled="true" |
|
:read-only="true" |
|
/> |
|
</LazySmartsheetDivDataCell> |
|
</a-form-item> |
|
</template> |
|
</LazySmartsheetRow> |
|
</div> |
|
</a-collapse-panel> |
|
<a-collapse-panel |
|
:key="ExpansionPanelKeys.output" |
|
collapsible="disabled" |
|
class="nc-ai-button-config-right-section nc-output-field-collapse-panel flex-1" |
|
> |
|
<template #header> |
|
<div class="flex"> |
|
<div |
|
class="text-sm text-nc-content-gray-subtle2 font-bold flex items-center gap-2.5 min-h-7" |
|
@click="handleUpdateExpansionPanel(ExpansionPanelKeys.output)" |
|
> |
|
Output fields |
|
|
|
<a-tag |
|
v-if="outputColumnIds.length" |
|
class="!rounded-md !bg-nc-bg-brand !text-nc-content-brand !border-none !mx-0" |
|
> |
|
{{ outputColumnIds.length }}</a-tag |
|
> |
|
<NcButton size="xs" type="text" class="hover:!bg-nc-bg-gray-dark !px-1"> |
|
<GeneralIcon |
|
icon="arrowRight" |
|
class="transform" |
|
:class="{ |
|
'rotate-270': expansionPanel.includes(ExpansionPanelKeys.output), |
|
}" |
|
/> |
|
</NcButton> |
|
</div> |
|
</div> |
|
</template> |
|
<div v-if="!outputColumnIds.length" class="flex-1 flex items-center justify-center"> |
|
<GeneralIcon icon="ncAutoAwesome" class="h-[177px] w-[177px] !text-purple-100" /> |
|
</div> |
|
<div v-else class="flex flex-col gap-4"> |
|
<LazySmartsheetRow :row="previewOutputRow"> |
|
<template v-for="field in outputFieldOptions"> |
|
<a-form-item |
|
v-if="field.title && outputColumnIds.includes(field.id)" |
|
:key="field.id" |
|
:name="field.title" |
|
class="!my-0 nc-input-required-error" |
|
> |
|
<div class="flex items-center gap-2 text-nc-content-gray-subtle2 mb-2"> |
|
<component :is="cellIcon(field)" class="!mx-0" /> |
|
<NcTooltip class="truncate flex-1" show-on-truncate-only> |
|
<template #title> |
|
{{ field?.title }} |
|
</template> |
|
{{ field?.title }} |
|
</NcTooltip> |
|
</div> |
|
|
|
<LazySmartsheetDivDataCell |
|
class="relative min-h-8 flex items-center children:h-full" |
|
:class="{ |
|
'!select-text nc-system-field': isReadOnlyVirtualCell(field), |
|
'!select-text nc-readonly-div-data-cell': !isReadOnlyVirtualCell(field), |
|
}" |
|
> |
|
<LazySmartsheetVirtualCell |
|
v-if="isVirtualCol(field)" |
|
:model-value="previewOutputRow.row[field.title]" |
|
class="mt-0 nc-input nc-cell" |
|
:class="[`nc-form-input-${field.title?.replaceAll(' ', '')}`, { readonly: field?.read_only }]" |
|
:column="field" |
|
:read-only="true" |
|
/> |
|
|
|
<LazySmartsheetCell |
|
v-else |
|
v-model="previewOutputRow.row[field.title]" |
|
class="nc-input truncate" |
|
:class="[`nc-form-input-${field.title?.replaceAll(' ', '')}`, { readonly: field?.read_only }]" |
|
:column="field" |
|
:edit-enabled="true" |
|
:read-only="true" |
|
/> |
|
</LazySmartsheetDivDataCell> |
|
</a-form-item> |
|
</template> |
|
</LazySmartsheetRow> |
|
</div> |
|
</a-collapse-panel> |
|
</a-collapse> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</NcModal> |
|
</div> |
|
</template> |
|
|
|
<style lang="scss"> |
|
.nc-ai-button-config-modal-wrapper { |
|
@apply !z-1050; |
|
|
|
.ant-modal-content { |
|
@apply overflow-hidden; |
|
} |
|
} |
|
</style> |
|
|
|
<style scoped lang="scss"> |
|
:deep(.ant-form-item-label > label) { |
|
@apply !text-sm !text-nc-content-gray flex; |
|
|
|
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { |
|
@apply content-[''] m-0; |
|
} |
|
} |
|
|
|
.nc-prompt-input-wrapper { |
|
@apply border-1 border-nc-border-gray-medium; |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
.nc-ai-button-options-preview { |
|
@apply rounded-lg border-1 border-nc-border-gray-medium; |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
.nc-ai-button-config-left-section { |
|
@apply mx-auto p-6 w-full max-w-[568px]; |
|
} |
|
.nc-ai-button-config-right-section { |
|
@apply mx-auto p-4 w-full max-w-[576px] flex flex-col gap-4; |
|
} |
|
|
|
.nc-ai-button-output-field { |
|
@apply cursor-pointer !rounded-md !bg-nc-bg-gray-medium !text-nc-content-gray hover:!bg-nc-bg-gray-dark !border-none !mx-0; |
|
} |
|
|
|
.nc-ai-button-test-generate { |
|
@apply !rounded-l-none -m-[1px] border-l-0 border-purple-200 !bg-nc-bg-purple-light !text-nc-content-purple-dark hover:(!bg-nc-bg-purple-dark); |
|
|
|
&:disabled { |
|
@apply !text-nc-content-purple-light !hover:(text-nc-content-purple-light bg-nc-bg-purple-light); |
|
} |
|
} |
|
|
|
:deep(.ant-collapse-header) { |
|
@apply !p-0 flex items-center !cursor-default children:first:flex; |
|
} |
|
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow) { |
|
@apply !right-0; |
|
} |
|
|
|
:deep(.ant-collapse-content-box) { |
|
@apply !px-0 !pb-0 !pt-3; |
|
} |
|
|
|
:deep(.ant-collapse-item) { |
|
&.nc-output-field-collapse-panel { |
|
.ant-collapse-content { |
|
@apply flex-1 flex flex-col; |
|
.ant-collapse-content-box { |
|
@apply flex-1 flex flex-col gap-4; |
|
} |
|
} |
|
} |
|
} |
|
|
|
:deep(.ant-select-selector) { |
|
@apply !xs:(h-full); |
|
} |
|
|
|
.nc-data-cell { |
|
@apply !rounded-lg; |
|
transition: all 0.3s; |
|
|
|
&.nc-readonly-div-data-cell, |
|
&.nc-system-field { |
|
@apply !border-gray-200; |
|
|
|
.nc-cell, |
|
.nc-virtual-cell { |
|
@apply text-nc-content-purple-dark; |
|
} |
|
} |
|
|
|
&.nc-readonly-div-data-cell:focus-within, |
|
&.nc-system-field:focus-within { |
|
@apply !border-gray-200; |
|
} |
|
|
|
&:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) { |
|
@apply !shadow-selected-ai; |
|
} |
|
|
|
&:has(.nc-virtual-cell-qrcode .nc-qrcode-container), |
|
&:has(.nc-virtual-cell-barcode .nc-barcode-container) { |
|
@apply !border-none px-0 !rounded-none; |
|
:deep(.nc-virtual-cell-qrcode), |
|
:deep(.nc-virtual-cell-barcode) { |
|
@apply px-0; |
|
& > div { |
|
@apply !px-0; |
|
} |
|
.barcode-wrapper { |
|
@apply ml-0; |
|
} |
|
} |
|
:deep(.nc-virtual-cell-qrcode) { |
|
img { |
|
@apply !h-[84px] border-1 border-solid border-gray-200 rounded; |
|
} |
|
} |
|
:deep(.nc-virtual-cell-barcode) { |
|
.nc-barcode-container { |
|
@apply border-1 rounded-lg border-gray-200 h-[64px] max-w-full p-2; |
|
svg { |
|
@apply !h-full; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
.nc-data-cell:focus-within { |
|
@apply !border-1 !border-purple-500; |
|
} |
|
:deep(.nc-system-field input) { |
|
@apply bg-transparent; |
|
} |
|
:deep(.nc-readonly-div-data-cell input) { |
|
@apply bg-transparent; |
|
} |
|
:deep(.nc-readonly-div-data-cell textarea) { |
|
@apply bg-transparent; |
|
} |
|
:deep(.nc-data-cell .nc-cell .nc-cell-field) { |
|
@apply px-2; |
|
} |
|
:deep(.nc-data-cell .nc-virtual-cell .nc-cell-field) { |
|
@apply px-2; |
|
} |
|
:deep(.nc-data-cell .nc-cell-field.nc-lookup-cell .nc-cell-field) { |
|
@apply px-0; |
|
} |
|
</style>
|
|
|