diff --git a/packages/nc-gui/assets/nc-icons-v2/eye-off.svg b/packages/nc-gui/assets/nc-icons-v2/eye-off.svg index 4a069a7f89..08865c81ba 100644 --- a/packages/nc-gui/assets/nc-icons-v2/eye-off.svg +++ b/packages/nc-gui/assets/nc-icons-v2/eye-off.svg @@ -1,11 +1,4 @@ - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-audio.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-audio.svg new file mode 100644 index 0000000000..62c53a0845 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-audio.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-compressed.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-compressed.svg new file mode 100644 index 0000000000..0151152ad2 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-compressed.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-csv.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-csv.svg new file mode 100644 index 0000000000..8f75e9a1cf --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-csv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-doc.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-doc.svg new file mode 100644 index 0000000000..72e8fdf6cd --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-doc.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-excel.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-excel.svg new file mode 100644 index 0000000000..c52bdaf3a7 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-excel.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-pdf.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-pdf.svg new file mode 100644 index 0000000000..63f44c83cc --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-pdf.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-presentation.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-presentation.svg new file mode 100644 index 0000000000..717cabce28 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-presentation.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-unknown.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-unknown.svg new file mode 100644 index 0000000000..f785c3a12e --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-unknown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-unsupported.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-unsupported.svg new file mode 100644 index 0000000000..f785c3a12e --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-unsupported.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-video.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-video.svg new file mode 100644 index 0000000000..a46566a29b --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-video.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-word.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-word.svg new file mode 100644 index 0000000000..85ce8d94bd --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-word.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons-v2/file-type-zip.svg b/packages/nc-gui/assets/nc-icons-v2/file-type-zip.svg new file mode 100644 index 0000000000..135eec0fff --- /dev/null +++ b/packages/nc-gui/assets/nc-icons-v2/file-type-zip.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons/eye-off.svg b/packages/nc-gui/assets/nc-icons/eye-off.svg index 196a4ed3a0..08865c81ba 100644 --- a/packages/nc-gui/assets/nc-icons/eye-off.svg +++ b/packages/nc-gui/assets/nc-icons/eye-off.svg @@ -1,14 +1,4 @@ - - - - - - - - - - + + + \ No newline at end of file diff --git a/packages/nc-gui/components/ai/PromptWithFields.vue b/packages/nc-gui/components/ai/PromptWithFields.vue index 2f13088161..3b56693836 100644 --- a/packages/nc-gui/components/ai/PromptWithFields.vue +++ b/packages/nc-gui/components/ai/PromptWithFields.vue @@ -21,6 +21,10 @@ const props = withDefaults( autoFocus: true, promptFieldTagClassName: '', suggestionIconClassName: '', + /** + * Use \n to show placeholder as preline + * @example: :placeholder="`Enter prompt here...\n\neg : Categorise this {Notes}`" + */ placeholder: 'Write your prompt here...', }, ) @@ -177,6 +181,7 @@ onMounted(async () => { .tiptap p.is-editor-empty:first-child::before { @apply text-gray-500; content: attr(data-placeholder); + white-space: pre-line; /* Preserve line breaks */ float: left; height: 0; pointer-events: none; diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index 67d9457097..874bc99e41 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/packages/nc-gui/components/cell/SingleSelect.vue @@ -47,8 +47,6 @@ const { $api } = useNuxtApp() const searchVal = ref() -const { getMeta } = useMetas() - const { isUIAllowed, isMetaReadOnly } = useRoles() const { isPg, isMysql } = useBase() diff --git a/packages/nc-gui/components/cell/attachment/index.vue b/packages/nc-gui/components/cell/attachment/index.vue index 480055e001..e3a0351357 100644 --- a/packages/nc-gui/components/cell/attachment/index.vue +++ b/packages/nc-gui/components/cell/attachment/index.vue @@ -57,6 +57,7 @@ const { isReadonly, storedFiles, removeFile, + updateAttachmentTitle, } = useProvideAttachmentCell(updateModelValue) const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly) @@ -150,7 +151,7 @@ const openAttachmentModal = () => { } const open = (e: Event) => { - e.stopPropagation() + e?.stopPropagation() openAttachmentModal() } @@ -220,6 +221,14 @@ const attachmentSize = computed(() => { return 'tiny' } }) + +defineExpose({ + openFilePicker: open, + downloadAttachment, + renameAttachment: renameFile, + removeAttachment: onRemoveFileClick, + updateAttachmentTitle, +}) @@ -252,9 +261,16 @@ const attachmentSize = computed(() => { @click="onFileClick(item)" /> - + - + @@ -419,7 +435,7 @@ const attachmentSize = computed(() => { > - + diff --git a/packages/nc-gui/components/cell/attachment/utils.ts b/packages/nc-gui/components/cell/attachment/utils.ts index e6e4f6c90c..22d46541a2 100644 --- a/packages/nc-gui/components/cell/attachment/utils.ts +++ b/packages/nc-gui/components/cell/attachment/utils.ts @@ -2,16 +2,11 @@ import type { AttachmentReqType, AttachmentType } from 'nocodb-sdk' import { populateUniqueFileName } from 'nocodb-sdk' import DOMPurify from 'isomorphic-dompurify' import RenameFile from './RenameFile.vue' -import MdiPdfBox from '~icons/mdi/pdf-box' -import MdiFileWordOutline from '~icons/mdi/file-word-outline' -import MdiFilePowerpointBox from '~icons/mdi/file-powerpoint-box' -import MdiFileExcelOutline from '~icons/mdi/file-excel-outline' -import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' - -export const getReadableFileSize = (sizeInBytes: number) => { - const i = Math.min(Math.floor(Math.log(sizeInBytes) / Math.log(1024)), 4) - return `${(sizeInBytes / 1024 ** i).toFixed(2) * 1} ${['B', 'kB', 'MB', 'GB', 'TB'][i]}` -} +import MdiPdfBox from '~icons/nc-icons-v2/file-type-pdf' +import MdiFileWordOutline from '~icons/nc-icons-v2/file-type-word' +import MdiFilePowerpointBox from '~icons/nc-icons-v2/file-type-presentation' +import MdiFileExcelOutline from '~icons/nc-icons-v2/file-type-csv' +import IcOutlineInsertDriveFile from '~icons/nc-icons-v2/file-type-unknown' export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( (updateModelValue: (data: string | Record[]) => void) => { @@ -105,7 +100,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( attachments.value.splice(i, 1) selectedVisibleItems.value.splice(i, 1) - updateModelValue(JSON.stringify(attachments.value)) + updateModelValue(attachments.value) } } @@ -248,7 +243,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( if (!data) return newAttachments.push(...data) } - if (newAttachments?.length) updateModelValue(JSON.stringify([...attachments.value, ...newAttachments])) + if (newAttachments?.length) updateModelValue([...attachments.value, ...newAttachments]) } async function uploadViaUrl(url: AttachmentReqType | AttachmentReqType[], returnError = false) { @@ -271,14 +266,18 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( } } + function updateAttachmentTitle(idx: number, title: string) { + attachments.value[idx].title = title + updateModelValue(attachments.value) + } + async function renameFile(attachment: AttachmentType, idx: number, updateSelectedFile?: boolean) { return new Promise((resolve) => { isRenameModalOpen.value = true const { close } = useDialog(RenameFile, { title: attachment.title, onRename: (newTitle: string) => { - attachments.value[idx].title = newTitle - updateModelValue(JSON.stringify(attachments.value)) + updateAttachmentTitle(idx, newTitle) close() if (updateSelectedFile) { @@ -359,9 +358,9 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( await apiPromise().then((res) => { if (res?.path) { - window.open(`${baseURL}/${res.path}`, '_blank') + window.open(`${baseURL}/${res.path}`, '_self') } else if (res?.url) { - window.open(res.url, '_blank') + window.open(res.url, '_self') } else { message.error('Failed to download file') } @@ -440,6 +439,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( videoStream, permissionGranted, isRenameModalOpen, + updateAttachmentTitle, } }, 'useAttachmentCell', diff --git a/packages/nc-gui/components/dashboard/settings/base/index.vue b/packages/nc-gui/components/dashboard/settings/base/index.vue index 9f8c5fb2b8..14ebe767c4 100644 --- a/packages/nc-gui/components/dashboard/settings/base/index.vue +++ b/packages/nc-gui/components/dashboard/settings/base/index.vue @@ -7,9 +7,7 @@ const { isFeatureEnabled } = useBetaFeatureToggle() const router = useRouter() -const activeMenu = ref( - isEeUI && hasPermissionForSnapshots.value ? 'snapshots' : 'visibility', -) +const activeMenu = ref(isEeUI && hasPermissionForSnapshots.value ? 'snapshots' : 'visibility') const selectMenu = (option: string) => { if (!hasPermissionForSnapshots.value && option === 'snapshots') { diff --git a/packages/nc-gui/components/general/WorkspaceIconSelector.vue b/packages/nc-gui/components/general/WorkspaceIconSelector.vue index ff89cf5437..d7b059a2ce 100644 --- a/packages/nc-gui/components/general/WorkspaceIconSelector.vue +++ b/packages/nc-gui/components/general/WorkspaceIconSelector.vue @@ -3,9 +3,9 @@ import type { UploadChangeParam, UploadFile } from 'ant-design-vue' import { Upload } from 'ant-design-vue' import data from 'emoji-mart-vue-fast/data/apple.json' import { EmojiIndex, Picker } from 'emoji-mart-vue-fast/src' -import { WorkspaceIconType } from '#imports' import 'emoji-mart-vue-fast/css/emoji-mart.css' import { PublicAttachmentScope } from 'nocodb-sdk' +import { WorkspaceIconType } from '#imports' interface Props { icon: string | Record @@ -67,6 +67,8 @@ const handleRemoveIcon = (closeDropdown = true) => { vIcon.value = '' vIconType.value = '' + emits('submit') + if (closeDropdown) { isOpen.value = false } diff --git a/packages/nc-gui/components/nc/DropdownSelect.vue b/packages/nc-gui/components/nc/DropdownSelect.vue new file mode 100644 index 0000000000..9bdc258f66 --- /dev/null +++ b/packages/nc-gui/components/nc/DropdownSelect.vue @@ -0,0 +1,43 @@ + + + + + + {{ props.tooltip }} + + + + + + + + {{ item.label }} + + + + + + + + diff --git a/packages/nc-gui/components/nc/EditableText.vue b/packages/nc-gui/components/nc/EditableText.vue new file mode 100644 index 0000000000..cfaad6d865 --- /dev/null +++ b/packages/nc-gui/components/nc/EditableText.vue @@ -0,0 +1,66 @@ + + + + + + + {{ internalText }} + + + + + + + diff --git a/packages/nc-gui/components/nc/List/RecordItem.vue b/packages/nc-gui/components/nc/List/RecordItem.vue new file mode 100644 index 0000000000..13039f65c9 --- /dev/null +++ b/packages/nc-gui/components/nc/List/RecordItem.vue @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + diff --git a/packages/nc-gui/components/nc/List.vue b/packages/nc-gui/components/nc/List/index.vue similarity index 89% rename from packages/nc-gui/components/nc/List.vue rename to packages/nc-gui/components/nc/List/index.vue index 6e9fbc9779..311780889d 100644 --- a/packages/nc-gui/components/nc/List.vue +++ b/packages/nc-gui/components/nc/List/index.vue @@ -5,7 +5,7 @@ export type MultiSelectRawValueType = Array export type RawValueType = string | number | MultiSelectRawValueType -interface ListItem { +export interface NcListItemType { value?: RawValueType label?: string [key: string]: any @@ -14,11 +14,11 @@ interface ListItem { /** * Props interface for the List component */ -interface Props { +export interface NcListProps { /** The currently selected value */ value: RawValueType /** The list of items to display */ - list: ListItem[] + list: NcListItemType[] /** * The key to use for accessing the value from a list item * @default 'value' @@ -38,6 +38,8 @@ interface Props { closeOnSelect?: boolean /** Placeholder text for the search input */ searchInputPlaceholder?: string + /** Show search input box always */ + showSearchAlways?: boolean /** Whether to show the currently selected option */ showSelectedOption?: boolean /** @@ -46,7 +48,7 @@ interface Props { */ itemHeight?: number /** Custom filter function for list items */ - filterOption?: (input: string, option: ListItem, index: Number) => boolean + filterOption?: (input: string, option: NcListItemType, index: Number) => boolean /** * Indicates whether the component allows multiple selections. */ @@ -55,24 +57,31 @@ interface Props { * The minimum number of items required in the list to enable search functionality. */ minItemsForSearch?: number + + containerClassName?: string + + itemClassName?: string } interface Emits { (e: 'update:value', value: RawValueType): void (e: 'update:open', open: boolean): void - (e: 'change', option: ListItem): void + (e: 'change', option: NcListItemType): void } -const props = withDefaults(defineProps(), { +const props = withDefaults(defineProps(), { open: false, closeOnSelect: true, - searchInputPlaceholder: '', + searchInputPlaceholder: 'Search', + showSearchAlways: false, showSelectedOption: true, optionValueKey: 'value', optionLabelKey: 'label', itemHeight: 38, isMultiSelect: false, minItemsForSearch: 4, + containerClassName: '', + itemClassName: '', }) const emits = defineEmits() @@ -85,7 +94,9 @@ const vOpen = useVModel(props, 'open', emits) const { optionValueKey, optionLabelKey } = props -const { closeOnSelect, showSelectedOption } = toRefs(props) +const { closeOnSelect, showSelectedOption, containerClassName, itemClassName } = toRefs(props) + +const slots = useSlots() const listRef = ref() @@ -97,7 +108,9 @@ const activeOptionIndex = ref(-1) const showHoverEffectOnSelectedOption = ref(true) -const isSearchEnabled = computed(() => props.list.length > props.minItemsForSearch) +const isSearchEnabled = computed( + () => props.showSearchAlways || slots.headerExtraLeft || slots.headerExtraRight || props.list.length > props.minItemsForSearch, +) const keyDown = ref(false) @@ -107,7 +120,7 @@ const keyDown = ref(false) * * @returns Filtered list of options * - * @typeparam ListItem - The type of items in the list + * @typeparam NcListItemType - The type of items in the list */ const list = computed(() => { const query = searchQuery.value.toLowerCase() @@ -176,7 +189,7 @@ const handleResetHoverEffect = (clearActiveOption = false, newActiveIndex?: numb * This function is responsible for handling the selection of an option from the list. * It updates the model value, emits a change event, and optionally closes the dropdown. */ -const handleSelectOption = (option: ListItem, index?: number) => { +const handleSelectOption = (option: NcListItemType, index?: number) => { if (!option?.[optionValueKey]) return if (index !== undefined) { @@ -322,12 +335,13 @@ watch( @keydown.enter.prevent="handleSelectOption(list[activeOptionIndex])" > - + + + @@ -345,10 +360,8 @@ watch( +/** + * @description + * Tabbed select component + * + * @example + * + */ + +interface Props { + disabled?: boolean + tooltip?: string + items: { + icon: keyof typeof iconMap + title?: string + value: string + }[] +} + +const props = defineProps() + +const modelValue = defineModel() + + + + + + {{ props.tooltip }} + + + + + + {{ $t(item.title) }} + + + + + + + diff --git a/packages/nc-gui/components/smartsheet/Cell.vue b/packages/nc-gui/components/smartsheet/Cell.vue index e8b476e550..d18dc6942b 100644 --- a/packages/nc-gui/components/smartsheet/Cell.vue +++ b/packages/nc-gui/components/smartsheet/Cell.vue @@ -173,11 +173,17 @@ const currentDate = () => { @keydown.shift.enter.exact="navigate(NavigateDir.PREV, $event)" > - + + + + {{ $t('general.generating') }} + {{ $t('general.generating') }} + + - + { - + diff --git a/packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue b/packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue index 7a96026d26..3596f971d7 100644 --- a/packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue @@ -53,11 +53,13 @@ const fieldTitle = computed(() => { ) }) -const previewOutputRow = ref({ +const defaultRow = { row: {}, oldRow: {}, rowMeta: {}, -}) +} + +const previewOutputRow = ref(defaultRow) const generatingPreview = ref(false) @@ -65,19 +67,9 @@ 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 inputFieldPlaceholder = 'Enter prompt here...\n\n eg : Categorise this {Notes}' -const displayField = computed(() => meta.value?.columns?.find((c) => c?.pv) ?? null) +const displayField = computed(() => meta.value?.columns?.find((c) => c?.pv) || meta.value?.columns?.[0] || null) const sampleRecords = computed< { @@ -158,6 +150,22 @@ const outputColumnIds = computed({ }, }) +const loadViewData = async (selectDefaultRecord = false) => { + if (!formattedData.value.length && !isLoadingViewData.value) { + isLoadingViewData.value = true + + await loadData(undefined, false) + + await ncDelay(250) + + isLoadingViewData.value = false + } + + if (selectDefaultRecord && sampleRecords.value.length) { + selectedRecordPk.value = sampleRecords.value[0].value + } +} + const removeFromOutputFieldOptions = (id: string) => { outputColumnIds.value = outputColumnIds.value.filter((op) => op !== id) } @@ -199,19 +207,18 @@ enum ExpansionPanelKeys { output = 'output', } -const expansionPanel = ref([ExpansionPanelKeys.output]) +const expansionInputPanel = ref([]) -const handleUpdateExpansionPanel = (key: ExpansionPanelKeys) => { - if (expansionPanel.value.includes(key)) { - expansionPanel.value = expansionPanel.value.filter((k) => k !== key) +const handleUpdateExpansionInputPanel = () => { + if (expansionInputPanel.value.includes(ExpansionPanelKeys.input)) { + expansionInputPanel.value = [] } else { - if (key === ExpansionPanelKeys.input && !inputColumns.value.length) { - return - } - expansionPanel.value.push(key) + expansionInputPanel.value = [ExpansionPanelKeys.input] } } +const expansionOutputPanel = ref([ExpansionPanelKeys.output]) + // provide the following to override the default behavior and enable input fields like in form provide(ActiveCellInj, ref(true)) provide(IsFormInj, ref(true)) @@ -219,10 +226,14 @@ provide(IsFormInj, ref(true)) watch(isOpenConfigModal, (newValue) => { if (newValue) { isAiButtonConfigModalOpen.value = true + loadViewData(true) } else { setTimeout(() => { isAiButtonConfigModalOpen.value = false }, 500) + + isOpenSelectOutputFieldDropdown.value = false + isOpenSelectRecordDropdown.value = false } }) @@ -232,18 +243,6 @@ watch(isOpenSelectRecordDropdown, (newValue) => { } }) -watch( - () => inputColumns.value.length, - (newValue) => { - if (newValue) return - - handleUpdateExpansionPanel(ExpansionPanelKeys.input) - }, - { - immediate: true, - }, -) - const previewPanelDom = ref() const isPreviewPanelOnScrollTop = ref(false) @@ -259,6 +258,12 @@ const checkScrollTopMoreThanZero = () => { return false } +const handleResetOutput = () => { + isAlreadyGenerated.value = false + + previewOutputRow.value = { row: {}, oldRow: {}, rowMeta: {} } +} + watch( [() => outputColumnIds.value.length, () => vModel.value.formula_raw?.length], () => { @@ -329,7 +334,6 @@ onBeforeUnmount(() => { { - - - + + + - {{ $t('labels.configuration') }} + {{ $t('general.configure') }} { - + + - - Input Prompt - - + + Input Prompt + + Include at least one field in your prompt. Optionally, specify how the field's data should guide the AI's + response and the format for the output. + + + @@ -398,11 +407,17 @@ onBeforeUnmount(() => { - - - Select fields to generate data - - + + + Choose Output Fields To Be Generated by AI + + Choose the fields where the AI-generated data will be applied. + + @@ -419,9 +434,14 @@ onBeforeUnmount(() => { option-label-key="title" option-value-key="id" :close-on-select="false" + class="!w-auto" is-multi-select + show-search-always + container-class-name="!max-h-[171px]" > + + @@ -431,22 +451,27 @@ onBeforeUnmount(() => { {{ option?.title }} - + + + {{ outputColumnIds.length }} + {{ $t(`objects.${outputColumnIds?.length === 1 ? 'field' : 'fields'}`) }} + - + - - + + {{ op.title }} @@ -460,313 +485,369 @@ onBeforeUnmount(() => { - - - {{ $t('labels.preview') }} + + + Test Data Generation + + + + + + + + + + + {{ aiError }} + + {{ aiError }} + + + - - Select sample record - - - - - - - - - - - - Select record - - - - - - - Loading records - - - - - - - - + + Input fields + + + + + + + + + + + + + + + + + + + + + + - Select Record - + + + + + + + + Loading records + + + + + - - - - + - - - - - - - {{ - !vModel.formula_raw - ? 'Prompt required for AI Button' - : !outputColumnIds.length - ? 'At least one output field is required for preview' - : !selectedRecordPk - ? 'Select sample record first' - : '' - }} - - - - - - - + - - {{ aiLoading && generatingPreview ? 'Test Generating' : 'Test Generate' }} - - - - - - - - - - - - {{ aiError }} - - {{ aiError }} - + + No input fields selected + + + + + + + + + + {{ field?.title }} + + {{ field?.title }} + + + + + + + + + + + + + + - - - - - - - - - Input fields - - - - {{ inputColumns.length }} + + + + Preview checklist + + - - - - - - - - - - - - - - - - - - {{ field?.title }} - - {{ field?.title }} - + - - Use at least 1 Field in your Input prompt + + + - - - - - - - - - - - - - - Output fields - - - {{ outputColumnIds.length }} - - - - - - - - - - - - - - - - - - {{ field?.title }} - - {{ field?.title }} - + - - Choose at least 1 Output Field + + + - - - - - + + + Select sample record + + + + + + + + + - + + {{ + aiLoading && generatingPreview + ? isAlreadyGenerated + ? 'Re-generating data' + : 'Generating data' + : isAlreadyGenerated + ? 'Re-generate data' + : 'Generate data' + }} + + + + + + + + + + + + + + + No output fields selected + + + + + + + + + + {{ field?.title }} + + {{ field?.title }} + + + + + + + + + + + + + + - - + + @@ -806,20 +887,30 @@ onBeforeUnmount(() => { .nc-ai-button-config-left-section { @apply mx-auto p-6 w-full max-w-[568px]; + width: min(100%, 568px); } .nc-ai-button-config-right-section { - @apply mx-auto p-4 w-full max-w-[576px] flex flex-col gap-4; + @apply mx-auto px-6 w-full max-w-[576px] flex flex-col; + + width: min(100%, 576px); + + .nc-is-already-generated { + box-shadow: 0px 12px 16px -4px rgba(75, 23, 123, 0.12), 0px 4px 6px -2px rgba(75, 23, 123, 0.08); + } } .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); +.nc-ai-button-test-generate-wrapper { + @apply relative; - &:disabled { - @apply !text-nc-content-purple-light !hover:(text-nc-content-purple-light bg-nc-bg-purple-light); + &:before { + @apply content-[''] h-[24px] block border-r-1 absolute -top-[24px] border-current; + } + &:after { + @apply content-[''] h-[24px] block border-r-1 absolute -bottom-[24px] border-current; } } @@ -831,7 +922,7 @@ onBeforeUnmount(() => { } :deep(.ant-collapse-content-box) { - @apply !px-0 !pb-0 !pt-3; + @apply !p-4; } :deep(.ant-collapse-item) { diff --git a/packages/nc-gui/components/smartsheet/expanded-form/Sidebar/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/Sidebar/index.vue index 9a324c59e3..d0ff7144f7 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/Sidebar/index.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/Sidebar/index.vue @@ -1,12 +1,27 @@ + + + + + Fields + + + + + diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue index cf63f87d58..dec44ec27c 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue @@ -1,17 +1,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ showHiddenFields ? `Hide ${hiddenFields.length} hidden` : `Show ${hiddenFields.length} hidden` }} + {{ hiddenFields.length > 1 ? `fields` : `field` }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/MiniColumnsWrapper.vue b/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/MiniColumnsWrapper.vue new file mode 100644 index 0000000000..b3f0ed633b --- /dev/null +++ b/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/MiniColumnsWrapper.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/index.vue new file mode 100644 index 0000000000..26181bb4ef --- /dev/null +++ b/packages/nc-gui/components/smartsheet/expanded-form/presentors/Fields/index.vue @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + {{ $t('general.reload') }} + + + {}"> + + + {{ $t('labels.copyRecordURL') }} + + + + + + + Delete record + + + + + + + + + {{ newRecordSubmitBtnText ?? isNew ? 'Create Record' : 'Save Record' }} + + + + + + + + + + + + + + diff --git a/packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue b/packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue index 430f68ab1d..f400aa8b59 100644 --- a/packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue +++ b/packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue @@ -1,5 +1,7 @@