From 818d9344e9a934c4d18ba54fae581b378725feb9 Mon Sep 17 00:00:00 2001 From: "Mert E." Date: Mon, 9 Dec 2024 11:15:36 +0300 Subject: [PATCH] chore: preps (#9994) * chore: preps Signed-off-by: mertmit * test: fix unit Signed-off-by: mertmit --------- Signed-off-by: mertmit --- .../nc-gui/components/ai/PromptWithFields.vue | 83 ++++++++-- packages/nc-gui/components/ai/Settings.vue | 36 ++++- packages/nc-gui/components/cell/AI.vue | 53 +++++-- packages/nc-gui/components/cell/TextArea.vue | 96 ++++++++--- .../components/cell/attachment/index.vue | 2 +- .../components/general/IntegrationIcon.vue | 2 +- packages/nc-gui/components/general/Loader.vue | 6 +- packages/nc-gui/components/nc/Button.vue | 9 +- .../nc-gui/components/nc/List/RecordItem.vue | 2 +- packages/nc-gui/components/nc/Switch.vue | 4 +- .../nc-gui/components/smartsheet/Cell.vue | 82 +++++++--- .../nc-gui/components/smartsheet/Gallery.vue | 2 +- .../nc-gui/components/smartsheet/Kanban.vue | 2 +- .../components/smartsheet/PlainCell.vue | 7 + .../smartsheet/column/AiButtonOptions.vue | 17 +- .../smartsheet/column/ButtonOptions.vue | 4 +- .../smartsheet/column/EditOrAdd.vue | 34 +++- .../smartsheet/column/LongTextOptions.vue | 109 ++++++++++++- .../column/UITypesOptionsWithSearch.vue | 4 +- .../components/smartsheet/details/Fields.vue | 4 + .../smartsheet/expanded-form/index.vue | 4 + .../smartsheet/grid/GroupByTable.vue | 1 + .../smartsheet/grid/InfiniteTable.vue | 16 +- .../components/smartsheet/grid/Table.vue | 16 +- .../components/smartsheet/header/Cell.vue | 11 +- .../components/smartsheet/header/CellIcon.ts | 4 +- .../components/smartsheet/header/Menu.vue | 27 +++- .../smartsheet/header/VirtualCell.vue | 7 +- .../smartsheet/toolbar/ViewActionMenu.vue | 4 + .../nc-gui/components/virtual-cell/Button.vue | 2 +- .../components/virtual-cell/HasMany.vue | 4 +- .../components/virtual-cell/ManyToMany.vue | 4 +- .../virtual-cell/components/ListItem.vue | 4 +- .../workspace/integrations/ConnectionsTab.vue | 6 - .../integrations/IntegrationsTab.vue | 3 + .../integrations/forms/EditOrAddCommon.vue | 8 +- .../integrations/forms/EditOrAddDatabase.vue | 12 +- .../workspace/project/AiCreateProject.vue | 31 ++-- .../composables/useColumnCreateStore.ts | 6 +- packages/nc-gui/composables/useData.ts | 3 +- .../nc-gui/composables/useInfiniteData.ts | 3 +- .../composables/useIntegrationsStore.ts | 63 ++++++-- .../composables/useMultiSelect/index.ts | 11 +- packages/nc-gui/composables/useNocoAi.ts | 38 ++--- .../nc-gui/helpers/parsers/parserHelpers.ts | 12 +- .../tiptapExtensions/mention/FieldList.vue | 18 ++- packages/nc-gui/nuxt.config.ts | 4 + packages/nc-gui/utils/cell.ts | 4 +- packages/nc-gui/utils/columnUtils.ts | 58 +++++-- packages/nc-gui/utils/commonUtils.ts | 44 +++++ packages/nocodb-sdk/src/lib/Api.ts | 3 - packages/nocodb-sdk/src/lib/UITypes.ts | 12 +- packages/nocodb-sdk/src/lib/globals.ts | 2 + .../nocodb-sdk/src/lib/helperFunctions.ts | 16 ++ packages/nocodb-sdk/src/lib/index.ts | 1 + packages/nocodb/src/db/BaseModelSqlv2.ts | 88 ++++++++-- packages/nocodb/src/db/conditionV2.ts | 19 +++ .../src/db/formulav2/formulaQueryBuilderv2.ts | 42 +++++ packages/nocodb/src/db/sortV2.ts | 29 +++- .../meta/migrations/XcMigrationSourcev2.ts | 4 + .../meta/migrations/v2/nc_069_ai_prompt.ts | 29 ++++ packages/nocodb/src/models/AIColumn.ts | 48 ++++++ packages/nocodb/src/models/Column.ts | 113 +++++++++++++ packages/nocodb/src/models/Integration.ts | 58 +++++++ packages/nocodb/src/models/LongTextColumn.ts | 113 +++++++++++++ packages/nocodb/src/models/index.ts | 2 + .../jobs/export-import/duplicate.processor.ts | 9 +- .../jobs/jobs/export-import/export.service.ts | 32 +++- .../jobs/jobs/export-import/import.service.ts | 45 +++++- .../nocodb/src/services/columns.service.ts | 106 ++++++++++++- packages/nocodb/src/utils/dataConversion.ts | 150 ++++++++++++++++++ packages/nocodb/src/utils/globals.ts | 7 +- 72 files changed, 1636 insertions(+), 278 deletions(-) create mode 100644 packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts create mode 100644 packages/nocodb/src/models/AIColumn.ts create mode 100644 packages/nocodb/src/models/LongTextColumn.ts create mode 100644 packages/nocodb/src/utils/dataConversion.ts diff --git a/packages/nc-gui/components/ai/PromptWithFields.vue b/packages/nc-gui/components/ai/PromptWithFields.vue index 3b56693836..219f6cd597 100644 --- a/packages/nc-gui/components/ai/PromptWithFields.vue +++ b/packages/nc-gui/components/ai/PromptWithFields.vue @@ -3,6 +3,7 @@ import Placeholder from '@tiptap/extension-placeholder' import StarterKit from '@tiptap/starter-kit' import Mention from '@tiptap/extension-mention' import { EditorContent, useEditor } from '@tiptap/vue-3' +import tippy from 'tippy.js' import { type ColumnType, UITypes } from 'nocodb-sdk' import FieldList from '~/helpers/tiptapExtensions/mention/FieldList' import suggestion from '~/helpers/tiptapExtensions/mention/suggestion.ts' @@ -15,6 +16,7 @@ const props = withDefaults( promptFieldTagClassName?: string suggestionIconClassName?: string placeholder?: string + readOnly?: boolean }>(), { options: () => [], @@ -26,6 +28,7 @@ const props = withDefaults( * @example: :placeholder="`Enter prompt here...\n\neg : Categorise this {Notes}`" */ placeholder: 'Write your prompt here...', + readOnly: false, }, ) @@ -38,7 +41,9 @@ const vModel = computed({ }, }) -const { autoFocus } = toRefs(props) +const { autoFocus, readOnly } = toRefs(props) + +const debouncedLoadMentionFieldTagTooltip = useDebounceFn(loadMentionFieldTagTooltip, 1000) const editor = useEditor({ content: vModel.value, @@ -55,18 +60,25 @@ const editor = useEditor({ ...suggestion(FieldList), items: ({ query }) => { if (query.length === 0) return props.options ?? [] - return props.options?.filter((o) => o.title?.toLowerCase()?.includes(query.toLowerCase())) ?? [] + return ( + props.options?.filter( + (o) => + o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), + ) ?? [] + ) }, char: '{', allowSpaces: true, }, renderHTML: ({ node }) => { + const matchedOption = props.options?.find((option) => option.title === node.attrs.id) + const isAttachment = matchedOption?.uidt === UITypes.Attachment return [ 'span', { - class: `prompt-field-tag ${ - props.options?.find((option) => option.title === node.attrs.id)?.uidt === UITypes.Attachment ? '!bg-green-200' : '' - } ${props.promptFieldTagClassName}`, + 'class': `prompt-field-tag ${isAttachment ? '!bg-green-200' : ''} ${props.promptFieldTagClassName}`, + 'style': 'max-width: 100px; white-space: nowrap; overflow: hidden; display: inline-block; text-overflow: ellipsis;', // Enforces truncation + 'data-tooltip': node.attrs.id, // Tooltip content }, `${node.attrs.id}`, ] @@ -93,8 +105,10 @@ const editor = useEditor({ text = text.trim() vModel.value = text + + debouncedLoadMentionFieldTagTooltip() }, - editable: true, + editable: !readOnly.value, autofocus: autoFocus.value, editorProps: { scrollThreshold: 100 }, }) @@ -141,15 +155,60 @@ onMounted(async () => { }, 100) } }) + +const tooltipInstances: any[] = [] + +function loadMentionFieldTagTooltip() { + document.querySelectorAll('.nc-ai-prompt-with-fields .prompt-field-tag').forEach((el) => { + const tooltip = Object.values(el.attributes).find((attr) => attr.name === 'data-tooltip') + if (!tooltip || el.scrollWidth <= el.clientWidth) return + // Show tooltip only on truncate + const instance = tippy(el, { + content: `
${tooltip.value}
`, + placement: 'top', + allowHTML: true, + arrow: true, + animation: 'fade', + duration: 0, + maxWidth: '200px', + }) + + tooltipInstances.push(instance) + }) +} + +onMounted(() => { + debouncedLoadMentionFieldTagTooltip() +}) + +onBeforeUnmount(() => { + tooltipInstances.forEach((instance) => instance?.destroy()) + tooltipInstances.length = 0 +}) - + diff --git a/packages/nc-gui/components/cell/AI.vue b/packages/nc-gui/components/cell/AI.vue index aa5ea1e2c6..ff2e8bbfe5 100644 --- a/packages/nc-gui/components/cell/AI.vue +++ b/packages/nc-gui/components/cell/AI.vue @@ -13,6 +13,10 @@ const { generateRows, generatingRows, generatingColumnRows, aiIntegrations } = u const { row } = useSmartsheetRowStoreOrThrow() +const { isUIAllowed } = useRoles() + +const isPublic = inject(IsPublicInj, ref(false)) + const meta = inject(MetaInj, ref()) const column = inject(ColumnInj) as Ref< @@ -40,9 +44,7 @@ const isAiEdited = ref(false) const isFieldAiIntegrationAvailable = computed(() => { const fkIntegrationId = column.value?.colOptions?.fk_integration_id - if (!fkIntegrationId) return false - - return ncIsArrayIncludes(aiIntegrations.value, fkIntegrationId, 'id') + return !!fkIntegrationId }) const pk = computed(() => { @@ -58,7 +60,7 @@ const generate = async () => { ncIsString(column.value.colOptions?.output_column_ids) && column.value.colOptions.output_column_ids.split(',').length > 1 ? column.value.colOptions.output_column_ids.split(',') : [] - const outputColumns = outputColumnIds.map((id) => meta.value?.columnsById[id]) + const outputColumns = outputColumnIds.map((id) => meta.value?.columnsById?.[id]).filter(Boolean) generatingRows.value.push(pk.value) generatingColumnRows.value.push(column.value.id) @@ -76,11 +78,18 @@ const generate = async () => { } } else { const obj: AIRecordType = resRow[column.value.title!] + if (obj && typeof obj === 'object') { vModel.value = obj setTimeout(() => { isAiEdited.value = false }, 100) + } else { + vModel.value = { + ...(ncIsObject(vModel.value) ? vModel.value : {}), + isStale: false, + value: resRow[column.value.title!], + } } } } @@ -99,10 +108,16 @@ const isLoading = computed(() => { }) const handleSave = () => { + vModel.value = { ...vModel.value } + emits('save') } const debouncedSave = useDebounceFn(handleSave, 1000) + +const isDisabledAiButton = computed(() => { + return !isFieldAiIntegrationAvailable.value || isLoading.value || isPublic.value || !isUIAllowed('dataEdit') +}) -
+
@@ -679,7 +723,7 @@ textarea:focus { diff --git a/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue b/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue index 9c8adf6993..cc2dc7d4e0 100644 --- a/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue +++ b/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue @@ -20,7 +20,7 @@ const filteredOptions = computed( () => options.value?.filter( (c) => - !(c.name === 'AIButton' && !isFeatureEnabled(FEATURE_FLAG.AI_FEATURES)) && + !((c.name === 'AIButton' || c.name === 'AIPrompt') && !isFeatureEnabled(FEATURE_FLAG.AI_FEATURES)) && (c.name.toLowerCase().includes(searchQuery.value.toLowerCase()) || (UITypesName[c.name] && UITypesName[c.name].toLowerCase().includes(searchQuery.value.toLowerCase()))), ) ?? [], @@ -126,7 +126,7 @@ watch( 'hover:bg-gray-100 cursor-pointer': !isDisabledUIType(option.name), 'bg-gray-100 nc-column-list-option-active': activeFieldIndex === index && !isDisabledUIType(option.name), '!text-gray-400 cursor-not-allowed': isDisabledUIType(option.name), - '!text-nc-content-purple-dark': option.name === 'AIButton', + '!text-nc-content-purple-dark': option.name === 'AIButton' || option.name === 'AIPrompt', }, ]" :data-testid="option.name" diff --git a/packages/nc-gui/components/smartsheet/details/Fields.vue b/packages/nc-gui/components/smartsheet/details/Fields.vue index 9dbd53d930..f6dafbe49c 100644 --- a/packages/nc-gui/components/smartsheet/details/Fields.vue +++ b/packages/nc-gui/components/smartsheet/details/Fields.vue @@ -1037,6 +1037,10 @@ const onClickCopyFieldUrl = async (field: ColumnType) => { await copy(field.id!) isFieldIdCopied.value = true + + await ncDelay(5000) + + isFieldIdCopied.value = false } const keys = useMagicKeys() diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue index dec44ec27c..f92d9fb778 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue @@ -327,6 +327,10 @@ const copyRecordUrl = async () => { ) isRecordLinkCopied.value = true + + await ncDelay(5000) + + isRecordLinkCopied.value = false } const saveChanges = async () => { diff --git a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue index a8cd49ace6..a593c281c2 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue @@ -276,6 +276,7 @@ async function deleteSelectedRowsWrapper() {