-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Nc feat/prompt #9956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nc feat/prompt #9956
Conversation
📝 Walkthrough📝 WalkthroughWalkthroughThe changes in this pull request enhance several components within the Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Uffizzi Preview |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
🧹 Outside diff range and nitpick comments (54)
packages/nc-gui/components/nc/Switch.vue (1)
45-47
: LGTM! Consider adding ARIA attributes for better accessibility.The addition of
opacity-60
class for disabled states follows best practices by providing clear visual feedback. The implementation is consistent across both placement options and correctly pairs withcursor-not-allowed
.Consider enhancing accessibility by adding ARIA attributes to the clickable spans:
<span v-if="placement === 'right' && $slots.default" class="pr-2" + :aria-disabled="disabled" + role="button" :class="[ contentWrapperClass, { 'cursor-not-allowed opacity-60': disabled, 'cursor-pointer': !disabled, }, ]" @click="onChange(!checked, true)" >Apply the same changes to the left placement span as well.
Also applies to: 72-74
packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue (1)
Line range hint
666-681
: Consider using v-focus directive for better reusability.While the current implementation of auto-focus using ref callback works, consider creating a reusable v-focus directive for consistency across the application.
Here's how you could implement it:
+ // In a shared directives file + export const vFocus = { + mounted: (el) => el.focus() + } + // In the component + directives: { + focus: vFocus + } - :ref=" - (el) => { - el?.$el?.focus() - } - " + v-focusThis approach would:
- Make the focus behavior reusable across components
- Reduce inline callback complexity
- Follow Vue's best practices for DOM manipulation
packages/nc-gui/components/general/Loader.vue (3)
4-7
: LGTM! Consider adding JSDoc comments.The interface is well-structured with appropriate types. The optional props provide good flexibility.
Consider adding JSDoc comments to document the purpose and usage of each prop:
export interface GeneralLoaderProps { + /** Size of the loader. Can be predefined sizes or a custom number in pixels */ size?: 'small' | 'medium' | 'large' | 'xlarge' | 'regular' | number + /** Additional CSS classes to apply to the loader */ loaderClass?: string }
Line range hint
9-27
: Consider handling numeric size in getFontSize function.While the function handles all string literal cases, it doesn't account for when
size
is a number. The number case is only handled in the template, which could lead to inconsistent behavior.Consider updating the function to handle numeric sizes:
function getFontSize() { const { size = 'medium' } = props + if (typeof size === 'number') { + return `text-[${size}px]` + } + switch (size) { case 'small': return 'text-xs' case 'medium':
Line range hint
29-32
: Fix syntax error in class string concatenation.There's an extra closing curly brace in the class string that could cause styling issues.
Apply this fix:
const indicator = h(LoadingOutlined, { - class: `!${getFontSize()} flex flex-row items-center !bg-inherit !hover:bg-inherit !text-inherit ${props.loaderClass || ''}}`, + class: `!${getFontSize()} flex flex-row items-center !bg-inherit !hover:bg-inherit !text-inherit ${props.loaderClass || ''}`, style: { ...(typeof props.size === 'number' && props.size ? { fontSize: `${props.size}px` } : {}) }, spin: true, })packages/nc-gui/components/smartsheet/header/Menu.vue (3)
52-61
: Consider enhancing type safetyWhile the computed property is well-structured, consider defining an interface for the return type instead of using an inline type.
+interface ColumnValidation { + isInvalid: boolean + tooltip: string +} -const columnInvalid = computed<{ isInvalid: boolean; tooltip: string }>(() => { +const columnInvalid = computed<ColumnValidation>(() => {
431-434
: Extract magic number to a named constantThe 5000ms delay should be extracted to a named constant for better maintainability.
+const COPY_FEEDBACK_DURATION_MS = 5000 + const (field: ColumnType) => { await copy(field.id!) isFieldIdCopied.value = true - await ncDelay(5000) + await ncDelay(COPY_FEEDBACK_DURATION_MS) isFieldIdCopied.value = false }
451-451
: Consider using more specific gap sizingThe generic
gap-1
utility class could be replaced with a more specific value to ensure consistent spacing across different screen sizes.- <div class="flex gap-1 items-center" @dblclick.stop> + <div class="flex gap-x-2 items-center" @dblclick.stop>Also applies to: 461-461, 464-464
packages/nc-gui/components/workspace/project/AiCreateProject.vue (3)
150-162
: Consider using a more robust prompt construction method.The current string concatenation approach could lead to malformed prompts if the optional fields contain special characters or formatting that could affect the prompt structure.
Consider using an array to collect the parts and then joining them:
- let prompt = `${aiFormState.value.prompt}` - - // Append optional information if provided - if (aiFormState.value.organization?.trim()) { - prompt += ` | Organization: ${aiFormState.value.organization}` - } - if (aiFormState.value.industry?.trim()) { - prompt += ` | Industry: ${aiFormState.value.industry}` - } - if (aiFormState.value.audience?.trim()) { - prompt += ` | Audience: ${aiFormState.value.audience}` - } + const promptParts = [aiFormState.value.prompt.trim()] + + if (aiFormState.value.organization?.trim()) { + promptParts.push(`Organization: ${aiFormState.value.organization.trim()}`) + } + if (aiFormState.value.industry?.trim()) { + promptParts.push(`Industry: ${aiFormState.value.industry.trim()}`) + } + if (aiFormState.value.audience?.trim()) { + promptParts.push(`Audience: ${aiFormState.value.audience.trim()}`) + } + + const prompt = promptParts.join(' | ')
Line range hint
164-207
: Ensure timer cleanup on component unmount.The
timerId
interval might continue running if the component is unmounted during the loading state, potentially causing memory leaks.Add cleanup logic in the
onBeforeUnmount
lifecycle hook:+onBeforeUnmount(() => { + if (timerId) { + clearInterval(timerId) + } +})
Line range hint
385-397
: Enhance accessibility for interactive elements.The collapse panels and buttons could benefit from improved accessibility attributes.
Add ARIA attributes to improve screen reader support:
<a-collapse-panel v-for="table in predictedSchema.tables" :key="table.title" collapsible="disabled" + :aria-label="`Table: ${table.title}`" > <template #header> <div class="w-full flex items-center px-4 py-2" + role="button" + :aria-expanded="previewExpansionPanel.includes(table.title)" @click="handleUpdatePreviewExpansionPanel(table.title, !viewsGrouped[table.title].length)" >packages/nc-gui/utils/commonUtils.ts (1)
74-93
: LGTM! Consider a minor optimization.The implementation is well-documented and handles edge cases gracefully. However, we could optimize it slightly by caching the lowercase values.
Here's an optimized version that avoids calling
toLowerCase()
twice when both inputs are defined:export const ncSearchCompare = (source?: string, query?: string): boolean => { - return (source || '').toLowerCase().includes((query || '').toLowerCase()) + const normalizedSource = (source || '').toLowerCase() + const normalizedQuery = (query || '').toLowerCase() + return normalizedSource.includes(normalizedQuery) }packages/nc-gui/components/nc/List/RecordItem.vue (2)
Line range hint
39-49
: Consider enhancing error handling in attachments parsing.The current implementation silently fails when JSON parsing errors occur. Consider logging these errors or handling specific error types for better debugging and maintenance.
const attachments: ComputedRef<Attachment[]> = computed(() => { try { if (attachmentColumn.value?.title && currentRow.value.row[attachmentColumn.value.title]) { return typeof currentRow.value.row[attachmentColumn.value.title] === 'string' ? JSON.parse(currentRow.value.row[attachmentColumn.value.title]) : currentRow.value.row[attachmentColumn.value.title] } return [] } catch (e) { + console.warn(`Failed to parse attachments for column ${attachmentColumn.value?.title}:`, e) return [] } })
Line range hint
144-168
: Consider extracting the column rendering logic into a separate component.The nested template logic for rendering columns is complex and could be extracted into a dedicated component to improve maintainability and reusability.
Consider creating a new component like
ListItemColumn.vue
to handle the column rendering logic. This would:
- Improve code organization
- Make the template easier to understand
- Enable better testing of the column rendering logic
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (4)
155-158
: Consider reducing the copy confirmation delayThe 5-second delay for the copy confirmation might be longer than necessary for good UX. Consider reducing it to 2-3 seconds, which is typically sufficient for users to notice the confirmation while maintaining responsiveness.
- await ncDelay(5000) + await ncDelay(2000)
Line range hint
196-207
: Move implementation note to component documentationThe detailed note about v-if vs v-show implementation should be moved to component-level documentation or a separate docs file, as it's more of a general implementation guideline than runtime code.
Consider creating a
CONTRIBUTING.md
or similar documentation file for such implementation guidelines.
Line range hint
78-116
: Enhance error handling in changeLockTypeThe error handling could be more specific to help with debugging and user feedback.
try { view.value.lock_type = type await $api.dbView.update(view.value.id as string, { lock_type: type, }) message.success(`Successfully Switched to ${type} view`) } catch (e: any) { - message.error(await extractSdkResponseErrorMsg(e)) + const errorMsg = await extractSdkResponseErrorMsg(e) + message.error(`Failed to change view lock type: ${errorMsg}`) + // Revert the optimistic update + view.value.lock_type = lockType.value }
Line range hint
78-116
: Extract lock-related messages to constantsConsider extracting the lock-related messages and magic strings to constants for better maintainability and reusability.
const LOCK_MESSAGES = { ALREADY_IN_STATE: (type: string) => `Already in ${type} view`, SWITCH_SUCCESS: (type: string) => `Successfully Switched to ${type} view`, DEFAULT_VIEW_ERROR: 'Not allowed to change default view', } as constpackages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue (2)
Line range hint
71-79
: Replace setTimeout with Vue's nextTick mechanism.The current implementation uses
setTimeout
for input focus, which is noted as a TODO. Consider using Vue's built-in mechanisms.nextTick(() => { - // todo: replace setTimeout and follow better approach - setTimeout(() => { - const input = form.value?.$el?.querySelector('input[type=text]') - input?.setSelectionRange(0, formState.value.title.length) - input?.focus() - }, 500) + const input = form.value?.$el?.querySelector('input[type=text]') + if (input) { + input.setSelectionRange(0, formState.value.title.length) + input.focus() + } })
Line range hint
35-46
: Enhance error handling in the submit function.The current error handling only logs to console. Consider implementing proper error feedback to users.
try { if (pageMode.value === IntegrationsPageMode.ADD) { await saveIntegration(formState.value) } else { await updateIntegration({ id: activeIntegration.value?.id, ...formState.value, 10000 }) } } catch (e) { - console.error(e) + const errorMessage = e instanceof Error ? e.message : 'Failed to save integration' + // Assuming you have a notification system + useNotification.error({ + title: 'Error', + message: errorMessage + }) + throw e // Re-throw to prevent closing the form on error } finally { isLoading.value = false }packages/nc-gui/components/cell/TextArea.vue (1)
Line range hint
423-446
: Refactor duplicated code to improve maintainabilityThe code displaying the "Edited by" message is duplicated in both the tooltip title and the display text. Refactoring this logic into a computed property or a method would reduce code duplication and enhance maintainability.
For example, create a computed property:
const editedByText = computed(() => { if (user?.id === props.aiMeta?.lastModifiedBy) { return 'you' } const editor = idUserMap[props.aiMeta?.lastModifiedBy] return editor?.display_name || editor?.email || 'unknown user' })Then update the template:
<NcTooltip v-if="isAiEdited" class="text-nc-content-green-dark flex-1 truncate" show-on-truncate-only> <template #title>Edited by you</template> Edited by you </NcTooltip> <NcTooltip v-else-if="props.aiMeta?.lastModifiedBy" class="text-nc-content-green-dark flex-1 truncate" show-on-truncate-only> <template #title>Edited by {{ editedByText }}</template> Edited by {{ editedByText }} </NcTooltip>packages/nc-gui/composables/useIntegrationsStore.ts (3)
300-309
: Simplify object construction using object spreadWhen adding a new AI integration, you can simplify the object by spreading
response
if it contains only necessary properties.Apply this diff to simplify the code:
if (response.type === IntegrationsType.Ai) { - aiIntegrations.value.push({ - id: response.id, - title: response.title, - is_default: response.is_default, - type: response.type, - sub_type: response.sub_type, - }) + aiIntegrations.value.push({ + id: response.id, + ...response, + }) }Ensure that
response
does not contain any sensitive or unnecessary data before spreading it.
Line range hint
447-496
: DeclareintegrationsInitialized
andallIntegrations
before useThe variables
integrationsInitialized
andallIntegrations
are used but not declared within the scope of this composable, which will lead toReferenceError
.Declare these variables at the appropriate scope:
+ const integrationsInitialized = ref(false) + const allIntegrations = ref<IntegrationItemType[]>([])Ensure that these variables are reactive to maintain reactivity in your application.
Line range hint
447-496
: Handle undefinedicon
when neither condition is metIf
di.meta.icon
is neither iniconMap
nor a valid URL,icon
may remain undefined, leading to potential rendering issues.Ensure
icon
always has a valid value:if (di.meta.icon) { // existing logic } else { icon = iconMap.puzzle || h('DefaultIconComponent') }Alternatively, set a default icon component if
iconMap.puzzle
is undefined.packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue (3)
Line range hint
89-94
: Clean up method signature and enhance error handlingThe
_e
parameter is unused and should be removed. Additionally, consider adding error handling for invalid indices.- selectItem(index, _e) { + selectItem(index) { const item = this.items[index] + if (index < 0 || index >= this.items.length) { + console.warn('Invalid index provided to selectItem') + return + } if (item) { this.command({ id: item.title, }) } }
102-102
: Optimize template structure and z-index managementThe template has some redundancy and a hardcoded z-index value.
Consider these improvements:
- Extract the title to a computed property to avoid repetition
- Consider using CSS variables for z-index management
+ computed: { + getItemTitle() { + return (item) => item?.title || '' + } + } <NcTooltip class="truncate" show-on-truncate-only - :tooltip-style="{ zIndex: '10000' }" + :tooltip-style="{ zIndex: 'var(--nc-tooltip-z-index, 10000)' }" > <template #title> - {{ item?.title || '' }} + {{ getItemTitle(item) }} </template> - {{ item?.title || '' }} + {{ getItemTitle(item) }} </NcTooltip>Also applies to: 110-110, 114-119
Line range hint
1-133
: Consider enhancing component architectureWhile the changes improve the UI/UX, consider these architectural improvements:
- Extract keyboard handling logic into a separate composable for better reusability
- Add proper cleanup in component destruction to prevent memory leaks
- Consider using Vue's provide/inject for z-index management across components
Would you like assistance in implementing any of these architectural improvements?
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
41-45
: Correct typo in variable nameisFieldAddedInPromt
The variable
isFieldAddedInPromt
contains a typo. It should beisFieldAddedInPrompt
to reflect the correct spelling.Apply this diff to fix the typo:
const isPreviewEnabled = computed(() => { - const isFieldAddedInPromt = availableFields.value.some((f) => { + const isFieldAddedInPrompt = availableFields.value.some((f) => { return vModel.value.prompt_raw?.includes(`{${f.title}}`) }) - return isFieldAddedInPromt + return isFieldAddedInPrompt })
108-109
: Avoid unnecessary type assertionThe type assertion
(column?.value?.colOptions as Record<string, any>)
may be unnecessary and could mask potentialundefined
values. Consider accessingprompt_raw
directly with optional chaining.Apply this diff to simplify the code:
- vModel.value.prompt_raw = (column?.value?.colOptions as Record<string, any>)?.prompt_raw || '' + vModel.value.prompt_raw = column?.value?.colOptions?.prompt_raw || ''packages/nc-gui/components/smartsheet/calendar/MonthView.vue (3)
Line range hint
186-260
: Optimize thefindAvailableLane
andoccupyLane
functionsThe
findAvailableLane
andoccupyLane
functions use nested loops which could affect performance with a large number of records. Consider optimizing these functions to improve performance in large datasets.
Line range hint
429-511
: Fix incorrect date calculations inonResize
methodIn the
onResize
method, when resizing an event, the calculation ofnewEndDate
andnewStartDate
may lead to incorrect dates if the user drags beyond the limits. Ensure that date boundaries are correctly handled to prevent invalid date assignments.
Line range hint
933-939
: EnsurefieldStyles[field.id]
exists before accessingIn the template, when using
fieldStyles[field.id]
, there is no check to ensurefieldStyles[field.id]
exists. This could lead to runtime errors iffieldStyles
does not contain thefield.id
. Consider adding a default value or a check before accessing properties.Apply this diff to add a default value:
<LazySmartsheetPlainCell v-model="record.row[field!.title!]" class="text-xs" - :bold="fieldStyles[field.id].bold" - :italic="fieldStyles[field.id].italic" - :underline="fieldStyles[field.id].underline" + :bold="fieldStyles[field.id]?.bold" + :italic="fieldStyles[field.id]?.italic" + :underline="fieldStyles[field.id]?.underline" />packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (2)
Line range hint
53-57
: InitializevModel.value.meta
before assignmentWhen setting
richMode
in the computed property,vModel.value.meta
might beundefined
. AssigningvModel.value.meta.richMode = value
without checking can cause errors.Apply this diff to fix the issue:
set: (value) => { + if (!vModel.value.meta) vModel.value.meta = {} vModel.value.meta.richMode = value },
Line range hint
687-693
: Simplify the conditional classesThe class bindings in the
nc-ai-button-test-generate-wrapper
can be simplified for readability.Apply this diff to simplify the class bindings:
:class="{ 'text-nc-border-gray-dark': !(selectedRecordPk && outputColumnIds.length && inputColumns.length), 'text-nc-content-purple-dark': selectedRecordPk && outputColumnIds.length && inputColumns.length, }"packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (1)
1065-1078
: Simplify the conditional rendering of iconsThe
:is
binding in thecomponent
has nested ternary operators which can be complex to read. Consider simplifying or extracting the logic for better readability.packages/nc-gui/components/smartsheet/Kanban.vue (1)
1146-1146
: Incorrect icon used in context menuIn the context menu, the icon for expanding a record has been changed to
iconMap.maximize
. Ensure that this icon accurately represents the action and is consistent with the UI guidelines.packages/nocodb/src/services/columns.service.ts (1)
2266-2288
: Ensure prompt is properly assigned whenprompt_raw
is missingCurrently, if
colBody.prompt_raw
is undefined,prompt
remains an empty string, leading tocolBody.prompt
being set to an empty string. Consider settingcolBody.prompt
only whenprompt_raw
is provided to prevent unintended empty prompts.packages/nocodb/src/db/BaseModelSqlv2.ts (1)
6506-6508
: Remove unused parameters inhandleRichTextMentions
methodThe parameters
_prevData
,_newData
, and_req
are unused in thehandleRichTextMentions
method. Consider removing them to clean up the code.Apply this diff to clean up the method signature:
- public async handleRichTextMentions( - _prevData, - _newData: Record<string, any> | Array<Record<string, any>>, - _req, - ) { + public async handleRichTextMentions() { return; }packages/nc-gui/components/smartsheet/header/CellIcon.ts (1)
23-24
: LGTM! Consider adding type safety for iconMapThe addition of AI column type handling is implemented correctly. However, consider adding type safety for
iconMap.cellAi
to ensure it exists at compile time.+ // Add at the top of the file with other type definitions + interface IconMap { + cellAi: Component; + cellSystemKey: Component; + // ... other icon types + } + + const iconMap: IconMap = { + // ... icon mappings + }packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue (1)
23-25
: LGTM! Consider extracting AI types to a constantThe feature flag check correctly handles both AI-related types. Consider extracting these types to a constant for better maintainability.
+ const AI_TYPES = ['AIButton', 'AIPrompt'] as const; + const filteredOptions = computed( () => options.value?.filter( (c) => - !((c.name === 'AIButton' || c.name === 'AIPrompt') && !isFeatureEnabled(FEATURE_FLAG.AI_FEATURES)) && + !(AI_TYPES.includes(c.name) && !isFeatureEnabled(FEATURE_FLAG.AI_FEATURES)) && (c.name.toLowerCase().includes(searchQuery.value.toLowerCase()) || (UITypesName[c.name] && UITypesName[c.name].toLowerCase().includes(searchQuery.value.toLowerCase()))), ) ?? [], )packages/nc-gui/components/cell/AI.vue (1)
131-139
: Simplify tooltip disabled conditionThe tooltip disabled condition
isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')
is complex and might be confusing.Consider using the existing computed property:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex"> + <NcTooltip :disabled="!isDisabledAiButton" class="flex">packages/nocodb-sdk/src/lib/helperFunctions.ts (1)
231-245
: Consider adding debug logging for parse/stringify failuresThe functions silently handle errors, which might make debugging difficult in production.
Consider adding debug logging:
export function parseHelper(v: any): any { try { return typeof v === 'string' ? JSON.parse(v) : v - } catch { + } catch (e) { + console.debug('Failed to parse JSON:', e) return v } } export function stringifyHelper(v: any): string { try { return JSON.stringify(v) - } catch { + } catch (e) { + console.debug('Failed to stringify value:', e) return v } }packages/nc-gui/components/ai/PromptWithFields.vue (3)
63-68
: Consider optimizing the filter condition.The filter condition could be simplified for better readability and performance.
Consider this optimization:
-return ( - props.options?.filter( - (o) => - o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), - ) ?? [] -) +const lowerQuery = query.toLowerCase() +return props.options?.filter((o) => { + const lowerTitle = o.title?.toLowerCase() + return lowerTitle?.includes(lowerQuery) || `${lowerTitle}}` === lowerQuery +}) ?? []
159-174
: Consider debounce timing adjustment.The tooltip implementation is good, but the debounce timing (1000ms) might be too long for a smooth user experience.
Consider reducing the debounce timing:
-const debouncedLoadMentionFieldTagTooltip = useDebounceFn(loadMentionFieldTagTooltip, 1000) +const debouncedLoadMentionFieldTagTooltip = useDebounceFn(loadMentionFieldTagTooltip, 300)
226-228
: Consider browser compatibility.The margin-right on paragraphs might need a fallback for older browsers.
Consider adding:
& > p { @apply mr-3; + margin-right: 0.75rem; /* Fallback for browsers that don't support @apply */ }
packages/nc-gui/components/ai/Settings.vue (2)
36-36
: Consider type safety improvements.The type casting of the response could be more robust.
Consider adding interface and type guard:
interface AIModel { value: string label: string } function isAIModelArray(data: unknown): data is AIModel[] { return Array.isArray(data) && data.every(item => typeof item === 'object' && item !== null && 'value' in item && typeof item.value === 'string' && 'label' in item && typeof item.label === 'string' ) } // Then use it: const response = await $api.integrations.endpoint(...) if (isAIModelArray(response)) { availableModels.value = response } else { console.error('Invalid response format') availableModels.value = [] }Also applies to: 57-61
83-86
: Consider error handling improvement.The model initialization logic could benefit from better error handling.
Consider adding error state handling:
if (!vModel.value || !availableModels.value.length) { + try { await onIntegrationChange() + } catch (error) { + console.error('Failed to initialize model:', error) + // Consider emitting an error event or showing user feedback + } }packages/nc-gui/components/smartsheet/header/Cell.vue (1)
46-55
: Consider using theisAIPromptCol
helper functionThe logic for identifying AI prompt columns could be simplified by using the newly added
isAIPromptCol
helper function from UITypes.if (column.value.uidt === UITypes.LongText) { if (parseProp(column.value?.meta)?.richMode) { return UITypesName.RichText } - if (parseProp(column.value?.meta)?.[LongTextAiMetaProp]) { + if (isAIPromptCol(column.value)) { return UITypesName.AIPrompt } }packages/nocodb-sdk/src/lib/UITypes.ts (1)
191-197
: Improve type safety inisAIPromptCol
The function uses type assertion
(col as any)?.meta
which bypasses TypeScript's type checking. Consider using a more type-safe approach.export function isAIPromptCol( col: | ColumnReqType | ColumnType ) { - return col.uidt === UITypes.LongText && parseHelper((col as any)?.meta)?.[LongTextAiMetaProp]; + return col.uidt === UITypes.LongText && 'meta' in col && parseHelper(col.meta)?.[LongTextAiMetaProp]; }packages/nc-gui/components/smartsheet/Cell.vue (1)
150-176
: Well-structured computed property for cell type determination.The
cellType
computed property provides a clean and maintainable way to determine cell types. The checks are properly ordered from specific to general cases.Consider extracting the type checks into a separate utility function to improve reusability and testability:
+const getCellType = (column: ColumnType, abstractType: any) => { + if (isAI(column)) return 'ai' + if (isTextArea(column)) return 'textarea' + // ... rest of the checks + return 'text' +} const cellType = computed(() => { - if (isAI(column.value)) return 'ai' - if (isTextArea(column.value)) return 'textarea' - // ... rest of the checks - return 'text' + return getCellType(column.value, abstractType.value) })packages/nc-gui/composables/useColumnCreateStore.ts (1)
Line range hint
308-321
: Improved error handling with user feedback.The error handling now provides clear feedback to users when validation fails, enhancing the user experience.
Consider extracting the error handling logic into a separate function for better maintainability:
+const handleValidationErrors = (e: any) => { + const errorMsgs = e.errorFields + ?.map((e: any) => e.errors?.join(', ')) + .filter(Boolean) + .join(', ') + + if (errorMsgs) { + message.error(errorMsgs) + return false + } + + if (!fromKanbanStack?.value || (fromKanbanStack.value && !e.outOfDate)) { + message.error(t('msg.error.formValidationFailed')) + return false + } + return true +} const addOrUpdate = async (onSuccess: () => Promise<void>, columnPosition?: Pick<ColumnReqType, 'column_order'>) => { try { if (!(await validate())) return } catch (e: any) { - const errorMsgs = e.errorFields - ?.map((e: any) => e.errors?.join(', ')) - .filter(Boolean) - .join(', ') - - if (errorMsgs) { - message.error(errorMsgs) - return - } - - if (!fromKanbanStack?.value || (fromKanbanStack.value && !e.outOfDate)) { - message.error(t('msg.error.formValidationFailed')) - return - } + if (!handleValidationErrors(e)) return } // ... rest of the function }packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts (1)
408-408
: Consider using let for mutable arrayThe comments array is being mutated with
push
operations, but it's declared as aconst
. While this works because array mutation methods don't reassign the reference, usinglet
would be more semantically correct for a mutable array.-const comments: Comment[] = []; +let comments: Comment[] = [];Also applies to: 411-415
packages/nc-gui/composables/useMultiSelect/index.ts (2)
292-298
: Consider adding type safety for AI record value.The AI content handling logic is well-structured, but consider adding type checking to ensure the value exists in the AIRecordType.
Consider this safer implementation:
if (parseProp(columnObj.meta)?.[LongTextAiMetaProp] === true) { const aiCell: AIRecordType = (columnObj.title && rowObj.row[columnObj.title]) || null if (aiCell) { + if ('value' in aiCell) { textToCopy = aiCell.value + } } }
299-300
: Fix indentation in the else block.The indentation in the else block is inconsistent with the surrounding code style.
Apply this fix:
} else { - textToCopy = `"${textToCopy.replace(/"/g, '\\"')}"` + textToCopy = `"${textToCopy.replace(/"/g, '\\"')}"` }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (73)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/dashboard/settings/base/index.vue
(0 hunks)packages/nc-gui/components/feed/View.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/calendar/MonthView.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/Sidebar/Audits.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(5 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(11 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(6 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(5 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (2)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
- packages/nc-gui/components/dashboard/settings/base/index.vue
✅ Files skipped from review due to trivial changes (3)
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/feed/View.vue
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (80)
packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue (1)
661-662
: LGTM: Button styling improvements enhance visual consistency.
The changes to b 8000 utton size and padding improve the visual consistency with other UI elements.
packages/nc-gui/components/smartsheet/header/Menu.vue (1)
48-49
: LGTM: Clean AI integration
The injection of aiIntegrations
follows Vue's composition API best practices.
packages/nc-gui/components/nc/Button.vue (2)
4-4
: LGTM! Well-structured prop addition.
The new loaderSize
prop is properly typed and maintains consistency with existing defaults.
Also applies to: 23-23, 37-37
53-53
: LGTM! Proper reactive props handling.
The loaderSize
prop is correctly destructured using toRefs
to maintain reactivity.
packages/nc-gui/components/virtual-cell/Button.vue (1)
213-213
: Verify design system consistency for button border radius
The change from rounded-lg
to rounded-md
reduces the button's corner radius. While this is a valid styling update, please ensure it aligns with the design system and maintains visual consistency with other UI components.
✅ Verification successful
Change to rounded-md is consistent with design system
Based on the codebase analysis, the change from rounded-lg
to rounded-md
in Button.vue aligns with the design system's border radius usage patterns. The codebase shows:
rounded-md
is commonly used for interactive elements like buttons, especially in compact UI components- Most button-like components use
rounded-md
for consistency:- Virtual cell buttons (Button.vue, AI.vue)
- Action buttons in toolbars and menus
- Form controls and interactive elements
The change maintains visual consistency with other similar UI components in the application.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent border radius usage across components
# Look for other instances of rounded-lg or rounded-md in Vue components
echo "Checking border radius consistency across components..."
rg -t vue "rounded-(lg|md)" packages/nc-gui/components/
Length of output: 206
Script:
#!/bin/bash
# Search for rounded-lg or rounded-md in all files under packages/nc-gui
# Using ripgrep without file type filter since .vue extension is not recognized
rg "rounded-(lg|md)" packages/nc-gui/
Length of output: 66785
packages/nc-gui/components/virtual-cell/ManyToMany.vue (1)
148-149
: LGTM! Icon and styling changes align with design system.
The changes improve UI consistency by:
- Using the standardized "maximize" icon
- Adding explicit sizing classes (h-3 w-3) for better layout control
Let's verify the icon usage pattern across the codebase:
✅ Verification successful
Icon usage pattern is consistent across the codebase
The verification confirms that the change from "expand" to "maximize" icon and the addition of explicit sizing classes (h-3 w-3) aligns with the codebase's icon usage patterns:
- The "maximize" icon is consistently used for expansion/maximize actions across components
- Icon sizing classes (h-3 w-3, h-4 w-4) are commonly applied for consistent dimensions
- The styling approach with hover states and text colors follows the established pattern
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent usage of maximize/expand icons and their sizing
# Check for any remaining "expand" icons that might need updating
rg -A 2 'icon="expand"' --type vue
# Check for consistent sizing classes with maximize icons
rg -A 2 'icon="maximize"' --type vue
# Check for any similar expand/maximize functionality to ensure consistent patterns
ast-grep --pattern 'icon="maximize"'
Length of output: 65846
Script:
#!/bin/bash
# Let's try a different approach to check icon usage patterns in Vue files
# Search for icon-related files and patterns
fd -e vue -x rg -l "icon=" {} \;
# Search for specific icon names and their classes
fd -e vue -x rg -A 2 -B 2 'icon="\w+"' {} \;
Length of output: 69485
packages/nc-gui/components/nc/List/RecordItem.vue (1)
145-145
: Verify the empty row check behavior across different column types.
The change to use currentRow
instead of the destructured row
prop is correct as it maintains reactivity. However, we should ensure that isRowEmpty
handles all column types correctly.
Let's verify the implementation and usage of isRowEmpty
:
✅ Verification successful
The change to use currentRow
is correct and consistent with the codebase
The verification reveals that:
isRowEmpty
is a utility function defined inpackages/nc-gui/utils/dataUtils.ts
that takes a record and column as parameters- The change from destructured
row
tocurrentRow
is correct ascurrentRow
is properly defined as a reactive ref from props - The implementation is consistent with other components in the codebase that use the same pattern (Gallery, Kanban, Calendar views)
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for isRowEmpty implementation and its usages
ast-grep --pattern 'function isRowEmpty($_) {
$$$
}'
# Also check for any related tests
fd -e spec.ts -e spec.js -e test.ts -e test.js | xargs rg 'isRowEmpty'
Length of output: 127
Script:
#!/bin/bash
# Let's try different approaches to find the isRowEmpty implementation
# Search for isRowEmpty method in Vue files
rg -U "isRowEmpty.*?\{.*?\}" -t vue --multiline
# Search for any usage of isRowEmpty
rg "isRowEmpty" -t vue -A 2 -B 2
# Look for the specific component and its methods
rg -U "export default.*?methods.*?\}" -t vue --multiline packages/nc-gui/components/nc/List/RecordItem.vue
Length of output: 275
Script:
#!/bin/bash
# Let's try without file type specification since 'vue' is not recognized
# Search for isRowEmpty method implementation
rg -U "isRowEmpty.*?\{.*?\}" --multiline
# Search for any usage of isRowEmpty with context
rg "isRowEmpty" -A 2 -B 2
# Look at the specific component file
cat packages/nc-gui/components/nc/List/RecordItem.vue
Length of output: 20153
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (2)
Line range hint 1-450
: Implementation looks solid overall
The view locking mechanism is well-implemented with proper error handling, user feedback, and security checks. The code maintains good separation of concerns and follows Vue.js best practices.
Line range hint 78-116
: Verify lock type changes across views
The lock type changes might affect other components or views that depend on this state.
✅ Verification successful
Lock type changes are properly handled across the codebase
The lock type changes in ViewActionMenu.vue
are well-integrated with other components:
- The change is reflected in the UI through multiple components (
ViewInfo.vue
,LockType.vue
,LockedViewFooter.vue
) that properly react to lock type updates - The state is managed consistently through the views store and API updates
- Proper validations are in place for different lock types (personal, locked, collaborative)
- The changes are properly persisted through
$api.dbView.update
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for components that might be affected by lock type changes
rg -l "lock_type" --type vue
# Search for lock type usage patterns
ast-grep --pattern 'view.lock_type = $_'
Length of output: 99
Script:
#!/bin/bash
# Search for lock_type usage in all files
rg -l "lock_type"
# Search for specific lock type related patterns
rg -A 3 "lock_type.*="
# Search for components that might be using lock_type in their template or script
rg -A 3 "lock_type" --type-add 'vue:*.vue' --type vue
Length of output: 17834
packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue (2)
2-2
: LGTM! Import statement follows TypeScript best practices.
The change to use import type
for type-only imports is a good practice as it ensures better tree-shaking and clearer code intentions.
2-2
: Verify the impact of removing loadAiIntegrations.
The AI summary indicates that loadAiIntegrations
function call was removed. Let's verify if this affects the AI integration functionality.
✅ Verification successful
The removal of loadAiIntegrations is safe
The removal of loadAiIntegrations
function appears to be safe. The AI integration functionality is still properly handled through the type system and integration store:
- AI integrations are defined in the type system via
OpenAI
in theintegrationType
record - The integration type is properly imported through
IntegrationCategoryType
andSyncDataType
from#imports
- The component uses the standard integration store hooks for managing integrations
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any remaining references to loadAiIntegrations
rg "loadAiIntegrations" --type vue
# Search for AI integration related code to understand the new flow
ast-grep --pattern 'if ($condition) {
$$$
integrationType === "AI"
$$$
}'
Length of output: 144
Script:
#!/bin/bash
# Search for any remaining references to loadAiIntegrations with proper file extension
rg "loadAiIntegrations"
# Search for AI integration related code to understand the new flow
rg "integrationType.*AI" -A 5 -B 5
Length of output: 883
Script:
#!/bin/bash
# Let's check the file content to understand the changes
cat "packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue"
# Search for OpenAI related integration code
rg "OpenAI" -A 5 -B 5
# Search for AI-related imports
rg "import.*AI"
Length of output: 97203
Script:
#!/bin/bash
# Let's check for any AI integration related code in the component
rg "AI" packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
# Let's check for any AI integration related code in the store
rg "AI" packages/nc-gui/composables/useIntegrationsStore.ts
# Let's check for any AI integration related imports in the codebase
rg "import.*AI" packages/nc-gui/components/workspace/integrations/
Length of output: 350
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue (1)
41-43
: Verify dynamic integration loading implementation
The addition of loadDynamicIntegrations
suggests a shift towards dynamic integration loading. This could impact the initialization flow and state management.
Let's verify the implementation and usage:
✅ Verification successful
Dynamic integration loading implementation verified
The implementation of loadDynamicIntegrations
is correctly integrated into the codebase:
- It's properly defined in the integration store and used in the IntegrationsTab component
- It's called during component mount to initialize dynamic integrations
- The function:
- Loads integrations only once (tracked by integrationsInitialized)
- Fetches dynamic integrations from the API
- Sorts them by order
- Adds them to allIntegrations with proper icon handling
- Updates the UI via integrationsRefreshKey
The implementation follows a proper initialization flow and state management pattern.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for the implementation and other usages of loadDynamicIntegrations
ast-grep --pattern 'loadDynamicIntegrations = $_'
# Search for related integration loading patterns
rg -A 5 'loadDynamicIntegrations'
Length of output: 65767
Script:
#!/bin/bash
# Search for loadDynamicIntegrations implementation and usage
rg -A 5 "loadDynamicIntegrations" --type vue --type ts --type js
# Search for integration store implementation
ast-grep --pattern 'const useIntegrationStore = $_'
Length of output: 65822
Script:
#!/bin/bash
# Search for loadDynamicIntegrations implementation and usage with better file type handling
rg -t js -t ts -t vue "loadDynamicIntegrations"
# Search for integration store definition
rg -t js -t ts -t vue "useIntegrationStore"
# Search for files related to integration store
fd -e ts -e js -e vue "integration" packages/nc-gui
Length of output: 486
Script:
#!/bin/bash
# Let's examine the integration store implementation
cat packages/nc-gui/composables/useIntegrationsStore.ts
# And check the component implementation
cat packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
Length of output: 35922
packages/nc-gui/components/cell/TextArea.vue (1)
Line range hint 712-721
: Check browser compatibility for the :has
pseudo-class
The :has
CSS pseudo-class is not widely supported in all browsers, notably missing support in Firefox. This may lead to inconsistent styling across different user environments. Consider using alternative selectors or JavaScript solutions to achieve the desired effect.
Refer to browser compatibility information: MDN Web Docs - :has() selector
packages/nc-gui/composables/useIntegrationsStore.ts (5)
191-192
: Event emission aligns with analytics tracking
The addition of $e('a:integration:delete')
ensures that integration deletion events are properly tracked for analytics purposes.
287-292
: Event emissions correctly distinguish between modes
Emi
8000
tting events based on the mode
parameter ensures accurate analytics tracking for integration creation and duplication.
198-201
: 🛠️ Refactor suggestion
Avoid mutating reactive state directly within array methods
Modifying aiIntegrations.value
directly within the filter
method may not trigger reactivity. To maintain reactivity, ensure that you are assigning a new array.
Apply this diff to avoid direct mutation:
if (integration.type === IntegrationsType.Ai) {
- aiIntegrations.value = aiIntegrations.value.filter((i) => i.id !== integration.id)
+ aiIntegrations.value = [...aiIntegrations.value.filter((i) => i.id !== integration.id)]
}
Likely invalid or redundant comment.
93-94
: Ensure useNocoAi
is imported and aiIntegrations
is reactive
The destructuring of { aiIntegrations }
from useNocoAi()
requires that useNocoAi
is properly imported and that aiIntegrations
is a reactive reference to ensure reactivity is maintained throughout the application.
Run the following script to verify the import and reactivity:
✅ Verification successful
useNocoAi
is properly implemented with reactive aiIntegrations
The implementation is correct:
useNocoAi
is a shared composable that exportsaiIntegrations
as a reactive refaiIntegrations
is properly used inuseIntegrationsStore.ts
with.value
access- The reactivity chain is maintained throughout the codebase
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that `useNocoAi` is imported and `aiIntegrations` is reactive.
# Check for the import statement
rg -A1 "import.*useNocoAi" packages/nc-gui/composables/useIntegrationsStore.ts
# Check the declaration of `aiIntegrations` in `useNocoAi`
rg -A5 "function useNocoAi" -g "*.ts" | rg "aiIntegrations\s*=\s*ref|reactive"
Length of output: 161
Script:
#!/bin/bash
# Let's try a different approach to verify the import and reactivity
# First, let's check all imports in the file
echo "=== Checking imports ==="
rg "^import" packages/nc-gui/composables/useIntegrationsStore.ts
# Then, let's find the useNocoAi definition
echo -e "\n=== Finding useNocoAi definition ==="
fd useNocoAi.ts
rg -l "export.*useNocoAi"
# Let's also check the content of useIntegrationsStore.ts around aiIntegrations usage
echo -e "\n=== Checking aiIntegrations context ==="
rg -C 3 "aiIntegrations" packages/nc-gui/composables/useIntegrationsStore.ts
Length of output: 1955
Script:
#!/bin/bash
# Let's check the useNocoAi.ts file to verify the reactivity implementation
echo "=== Checking useNocoAi implementation ==="
cat packages/nc-gui/composables/useNocoAi.ts
Length of output: 9190
541-541
: Ensure loadDynamicIntegrations
is called appropriately
Now that loadDynamicIntegrations
replaces the previous onMounted
hook, ensure it is called during component initialization to load dynamic integrations.
Check where loadDynamicIntegrations
is invoked to confirm it's integrated properly.
✅ Verification successful
Based on the analysis of the code, I can now generate the final response:
loadDynamicIntegrations
is properly called during initialization
The function is correctly called in the onMounted
hook of the IntegrationsTab component, ensuring dynamic integrations are loaded when the component is mounted:
onMounted(() => {
loadDynamicIntegrations()
// ...
})
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that `loadDynamicIntegrations` is called during initialization.
rg -A2 "loadDynamicIntegrations\(\)" packages/nc-gui/**/*.vue
Length of output: 437
Script:
#!/bin/bash
# Let's try a broader search for loadDynamicIntegrations without parentheses
# and also check its usage in .ts/.js files since it might be called from there
rg "loadDynamicIntegrations" packages/nc-gui/
Length of output: 421
Script:
#!/bin/bash
# Let's also check where this function is used through ast-grep
# to catch any method invocations
ast-grep --pattern 'loadDynamicIntegrations($_)'
Length of output: 3408
Script:
#!/bin/bash
# Let's check the file content where loadDynamicIntegrations is defined
# to understand its context better
cat packages/nc-gui/composables/useIntegrationsStore.ts
Length of output: 15350
Script:
#!/bin/bash
# Let's check the IntegrationsTab.vue where loadDynamicIntegrations is called
cat packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
Length of output: 20571
packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue (1)
59-64
: 🛠️ Refactor suggestion
Review the timeout implementation for '}' key handler
The setTimeout implementation has several potential issues:
- The 250ms delay seems arbitrary and its purpose is not documented
- The timeout is not cleared if another key is pressed during the delay, which could lead to race conditions
- The special handling of the '}' character is not explained
Consider implementing it this way:
+ // Store timeout reference
+ data() {
+ return {
+ selectedIndex: 0,
+ selectionTimeout: null,
+ }
+ }
if (event.key === '}') {
+ // Clear any existing timeout
+ if (this.selectionTimeout) {
+ clearTimeout(this.selectionTimeout)
+ }
- setTimeout(() => {
+ this.selectionTimeout = setTimeout(() => {
this.selectItem(this.selectedIndex)
+ this.selectionTimeout = null
}, 250)
}
✅ Verification successful
Let me try another search pattern to find timeout usage in the codebase.
Let me try one more search to find any similar patterns in the codebase.
Let me try one final search with a different pattern.
Now I have enough context to provide a final response about the timeout implementation.
The timeout implementation for '}' key is intentional and safe
The timeout in the }
key handler is part of a field selection component that handles various keyboard interactions for a mention/suggestion list. The 250ms delay appears to be intentional to:
- Allow the
}
character to be processed first before triggering the selection - Avoid race conditions with other key handlers since:
- Each key handler returns a boolean indicating if it handled the event
- The
}
handler is the only one using setTimeout and returns false - Other immediate handlers (Tab, Enter, Arrow keys) return true to prevent further processing
The current implementation is safe because:
- The component maintains a single
selectedIndex
in its data - The timeout only triggers
selectItem()
with the currentselectedIndex
- No other handlers modify the selection during the timeout period
While the suggested refactoring would add extra safety, it's not essential in this case since there are no observable race conditions in the current implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for similar timeout patterns in other key handlers
rg -A 5 "setTimeout.*selectItem"
Length of output: 65571
Script:
#!/bin/bash
# Search for timeout patterns in the codebase
rg -A 5 "setTimeout.*select" packages/nc-gui/
# Also search for any other timeouts in the same file
rg -A 5 "setTimeout" packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
Length of output: 65667
Script:
#!/bin/bash
# Search for keyboard event handlers in the same file
ast-grep --pattern 'if (event.key === $_) {
$$$
}' packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
Length of output: 117
Script:
#!/bin/bash
# Search for the entire file content to understand the context
cat packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
Length of output: 2803
packages/nocodb/src/models/AIColumn.ts (1)
5-48
: Implementation of AIColumn
class is well-structured
The AIColumn
class correctly extends LongTextColumn
, incorporating AI-specific properties and methods such as fk_integration_id
, model
, prompt
, and prompt_raw
. The static methods insert
and update
are appropriately designed for handling AI columns.
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (1)
9-113
: LongTextColumn
abstract class is properly defined
The abstract class LongTextColumn
provides a solid foundation for long text columns with necessary properties and methods like _insert
, read
, and _update
. The use of caching and error handling enhances performance and reliability.
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
6-70
: SQL queries are safely parameterized and handle multiple databases
The functions convertAIRecordTypeToValue
and convertValueToAIRecordType
correctly construct parameterized SQL queries for different database types, mitigating SQL injection risks and ensuring compatibility.
packages/nocodb/src/db/sortV2.ts (1)
202-231
: Sorting logic for AI prompt columns is correctly implemented
The addition handles sorting of AI prompt columns by extracting the 'value' field from the JSON data, with appropriate SQL expressions for each supported database client.
packages/nc-gui/utils/columnUtils.ts (1)
269-318
: isColumnInvalid
function accurately validates AI columns
The updated function effectively checks for AI integrations and properly sets the invalid state and tooltips based on the presence of fk_integration_id
and the isReadOnly
flag.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (2)
Line range hint 251-269
: Handle AIPrompt
type correctly in onSelectType
When uidt
is AIPrompt
, the onSelectType
function sets formState.value.uidt
to UITypes.LongText
and adds meta properties. Ensure that this transformation aligns with the application's logic and that AIPrompt
is handled consistently throughout the codebase.
602-604
: Use consistent naming for AI prompt detection
The function isAiPromptSelectOption
checks if uidt
equals UITypes.LongText
and isAIPromptCol(formState.value)
. Ensure that this check is consistent with other parts of the code that handle AI prompts.
packages/nocodb/src/db/conditionV2.ts (1)
596-613
: Correctly handling AI prompt columns in query conditions
The code appropriately adjusts the field extraction for AI prompt columns by handling different database clients and extracting the 'value' property from JSON.
packages/nocodb/src/models/Column.ts (6)
452-469
: Properly inserting AI options for LongText columns
The code correctly checks for AI prompt metadata and inserts AI column options when the column is of type LongText
with AI prompt enabled.
573-577
: Fetching AI column options for LongText columns
The code appropriately retrieves AI column options for LongText
columns with AI prompt metadata set to true.
1101-1106
: Handling deletion of AI LongText column metadata
The code correctly sets the meta table and cache scope names for AI LongText
columns during deletion, ensuring associated metadata is properly cleaned up.
1353-1369
: Removing AI column options during update
The code appropriately deletes AI column options and clears the related cache when updating a LongText
column, maintaining data integrity.
1957-1970
: Adding AI options in bulk column insertion
The code properly handles bulk insertion of AI column options for LongText
columns with AI prompts, ensuring all necessary properties are included.
2037-2044
: Performing bulk insert for AI LongText columns
The code correctly inserts AI column options into the metadata table during a bulk insert operation for LongText
columns with AI prompts.
packages/nocodb/src/services/columns.service.ts (2)
71-74
: Importing AI data conversion utilities
The import statements correctly include AI data conversion functions needed for handling AI prompt columns.
1562-1630
: Handling data conversion for AI prompt columns during update
The code correctly manages data conversion when toggling AI prompt functionality on a LongText
column, ensuring that data is converted appropriately based on the column's AI status.
packages/nocodb/src/db/BaseModelSqlv2.ts (1)
13-19
: Imports are appropriately added and utilized
The newly imported isAIPromptCol
and LongTextAiMetaProp
are necessary for handling AI prompt columns and are correctly used in the codebase.
packages/nc-gui/components/general/IntegrationIcon.vue (1)
5-5
: size
prop is correctly made optional with a default
The size
prop is now optional and has a default value of 'sm'
, improving the component's flexibility and usability.
packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts (1)
4-27
: Migration file correctly defines the new table
The up
and down
methods properly create and drop the COL_LONG_TEXT
table with appropriate columns and indices.
packages/nocodb-sdk/src/lib/index.ts (1)
15-15
: Exporting isAIPromptCol
enhances SDK functionality
Adding isAIPromptCol
to the exports allows other modules to identify AI prompt columns, improving code reusability.
packages/nocodb/src/models/index.ts (1)
51-52
: New models LongTextColumn
and AIColumn
are properly exported
Exporting LongTextColumn
and AIColumn
makes them available for use throughout the application.
packages/nc-gui/components/smartsheet/expanded-form/Sidebar/Audits.vue (1)
86-91
: LGTM! Improved component formatting
The multi-line formatting of GeneralUserIcon improves code readability while maintaining functionality.
packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue (1)
129-131
: LGTM! Consistent styling for AI components
The styling is consistently applied to both AI-related components using the existing color scheme.
packages/nc-gui/components/cell/AI.vue (3)
16-19
: LGTM: Good security practice with role-based access control
The addition of role-based access control and public status checking enhances security for AI feature access.
111-112
: LGTM: Proper Vue reactivity handling
Creating a new object reference using spread operator ensures proper reactivity in Vue.
47-47
: Consider additional validation for i
67ED
ntegration ID
The simplified check return !!fkIntegrationId
might miss edge cases where the ID exists but is invalid.
Consider adding validation to ensure the integration ID is valid:
- return !!fkIntegrationId
+ return !!fkIntegrationId && aiIntegrations.some(integration => integration.id === fkIntegrationId)
packages/nocodb/src/utils/globals.ts (1)
15-15
: LGTM! Verify the impact of enum changes.
The reclassification of AI prompt columns as long text columns is consistent across both enums. The versioning suffix is maintained for backward compatibility.
Let's verify the usage of these new enum values and ensure no references to the old ones remain:
Also applies to: 148-148
✅ Verification successful
Let me verify the migration files to ensure the transition from AI to long text columns was handled properly.
Let me check the content of the other AI-related migration file to ensure there are no conflicts.
Enum changes have been properly implemented with no legacy references
Based on the verification:
- No references to
COL_AI
exist in the codebase COL_LONG_TEXT
is consistently used across models and migrations- Migration
nc_069_ai_prompt.ts
properly creates the table usingMetaTable.COL_LONG_TEXT
- No conflicting schema definitions or migrations were found
- The AI button migration (
nc_066_ai_button.ts
) handles a separate concern and doesn't interfere
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining references to COL_AI
# and verify the adoption of COL_LONG_TEXT
# Check for any remaining COL_AI references
rg "COL_AI"
# Check adoption of new COL_LONG_TEXT
rg "COL_LONG_TEXT"
Length of output: 1531
Script:
#!/bin/bash
# Check migration files for any AI to long text transition
rg -A 10 "nc_069_ai_prompt" packages/nocodb/src/meta/migrations/v2/
Length of output: 67
Script:
#!/bin/bash
# Let's check the migration file directly and any references to column type transitions
cat packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
# Also check for any other potential migration files related to this change
fd "nc_.*ai.*" packages/nocodb/src/meta/migrations/
Length of output: 963
Script:
#!/bin/bash
# Check the content of the AI button migration file
cat packages/nocodb/src/meta/migrations/v2/nc_066_ai_button.ts
# Also verify there are no direct references to AI columns in the schema
rg -A 5 "createTable.*AI" packages/nocodb/src/meta/migrations/
Length of output: 683
packages/nc-gui/components/ai/PromptWithFields.vue (3)
Line range hint 19-31
: LGTM! ReadOnly prop implementation.
The readOnly prop is properly implemented with a default value of false.
74-81
: LGTM! Enhanced field tag rendering.
Good implementation of conditional styling for attachment fields and proper setup for tooltips.
185-202
: LGTM! Proper disabled state handling.
The button's disabled state and icon opacity are correctly managed based on the readOnly prop.
packages/nc-gui/components/ai/Settings.vue (2)
10-15
: LGTM! Proper early return implementation.
The isEditColumn prop is well implemented with proper type safety and early return in the mounted hook.
Also applies to: 24-25, 70-72
215-221
: LGTM! Proper scoped styling.
The scoped styling for select components is well implemented with proper deep selector usage.
packages/nc-gui/composables/useNocoAi.ts (1)
20-21
: LGTM: Type change improves flexibility
The change to Partial<IntegrationType>[]
is a good improvement as it better represents the actual data structure where not all integration properties may be available.
packages/nocodb-sdk/src/lib/UITypes.ts (1)
94-94
: LGTM: AIPrompt type addition
The addition of the AIPrompt type to UITypesName is clean and follows the existing pattern.
packages/nc-gui/components/virtual-cell/components/ListItem.vue (1)
162-162
: LGTM: Icon component standardization
The change from MaximizeIcon
to GeneralIcon
with the "maximize" icon maintains the same functionality while standardizing icon usage across components.
packages/nc-gui/components/smartsheet/header/VirtualCell.vue (1)
133-136
: LGTM: Added AI button type handling
The addition of AI button type handling in the columnTypeName
computed property is well-implemented and follows the existing pattern for special column types.
packages/nc-gui/components/smartsheet/PlainCell.vue (2)
3-3
: LGTM: Added AI-related imports
The addition of AIRecordType
and isAIPromptCol
imports provides proper typing and type checking for AI prompt columns.
Also applies to: 10-10
207-211
: LGTM: Added AI prompt column handling
The modification to getTextAreaValue
properly handles AI prompt columns by extracting the value property from AIRecordType
.
packages/nc-gui/components/smartsheet/Cell.vue (1)
196-196
: Improved template structure with consistent cell type handling.
The template changes effectively utilize the new cellType
computed property for conditional rendering.
Let's verify that all cell types are properly handled:
Also applies to: 212-263
✅ Verification successful
Let me gather more information about the cell type handling in the component.
Let me try a different approach to verify the cell type handling.
Cell type handling is properly implemented and consistent.
The verification confirms that the cellType
computed property in Cell.vue comprehensively handles all UI types from the SDK:
- All basic types (text, number, boolean) are covered
- All specialized types (date, time, attachment, etc.) are handled
- Each cell type has a corresponding lazy-loaded component
- The conditional rendering in the template matches the computed cell types
- The AI cell type is properly integrated with the existing cell type system
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that all UITypes have corresponding cell components
# and that no cell types are missing in the cellType computed property
# Search for all UITypes definitions
rg "UITypes\." packages/nocodb-sdk/src/lib/UITypes.ts
# Search for all cell components
fd "Cell.*\.vue" packages/nc-gui/components/cell/
Length of output: 4843
Script:
#!/bin/bash
# Check the cellType computed property implementation and cell type handling in Cell.vue
ast-grep --pattern 'computed: {
$$$
cellType() {
$$$
}
$$$
}'
# Also check for conditional rendering based on cellType
rg -A 2 'v-if.*cellType' packages/nc-gui/components/smartsheet/Cell.vue
Length of output: 149
Script:
#!/bin/bash
# Search for the content of Cell.vue to analyze cellType handling
cat packages/nc-gui/components/smartsheet/Cell.vue
# Also check for any other cell type related components
fd ".*Cell.*\.vue" packages/nc-gui/components/smartsheet/
Length of output: 11430
packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts (1)
58-58
: Migration properly integrated following established patterns.
The AI prompt migration is correctly added following the existing migration structure and naming conventions.
Let's verify the migration sequence and implementation:
Also applies to: 125-125, 249-250
packages/nc-gui/composables/useColumnCreateStore.ts (1)
3-3
: Enhanced AI mode detection with proper type checking.
The isAiMode
computed property now correctly handles AI prompt columns using the imported utility function.
Also applies to: 113-116
packages/nc-gui/components/cell/attachment/index.vue (1)
463-463
: LGTM: Icon update for consistency
The icon change from expand
to maximize
aligns with similar updates in other components.
packages/nc-gui/helpers/parsers/parserHelpers.ts (1)
481-484
: LGTM: AI-specific column naming logic
The changes add proper handling for AI button and AI prompt column types, following the existing patterns and maintaining code consistency.
Also applies to: 486-490
packages/nocodb/src/models/Integration.ts (2)
454-457
: LGTM: Defensive initialization of config.models
Good addition of null check and array initialization to prevent potential runtime errors.
474-500
: Verify the cleanup of integration associations
The code adds cleanup of button and long text associations during integration deletion. Let's verify this handles all cases correctly.
Also applies to: 522-547
✅ Verification successful
Based on the search results, I can now provide a final response:
Integration cleanup implementation is complete and correct
The code correctly handles the cleanup of integration associations by:
- Nullifying
fk_integration_id
andmodel
fields in both button and long text columns - Using proper workspace scoping with
this.fk_workspace_id ? this.fk_workspace_id : RootScopes.WORKSPACE
- Ensuring atomic updates through
ncMeta.metaUpdate
with proper where conditions
The implementation is thorough and handles all known integration references in the schema, including:
- Button columns (MetaTable.COL_BUTTON)
- Long text columns (MetaTable.COL_LONG_TEXT)
- AI columns (MetaTable.COL_AI)
- Sources (MetaTable.SOURCES)
- Integration store (MetaTable.INTEGRATION_STORE)
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for potential orphaned references after integration deletion
# Check for any remaining references to deleted integrations in buttons
ast-grep --pattern 'fk_integration_id = $_'
# Check for any remaining references to deleted integrations in long texts
rg -A 5 'fk_integration_id'
Length of output: 65755
Script:
#!/bin/bash
# Let's search for integration references in the codebase
rg -A 5 "fk_integration_id" --type ts
# Also check for any other tables that might reference integrations
ast-grep --pattern 'MetaTable.$_ = $_' --lang typescript
Length of output: 26508
packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts (2)
4-9
: LGTM! Import organization looks good.
The imports are well-organized and properly grouped, with the new isAIPromptCol
import added alongside related functions.
443-443
: Verify AI prompt column handling in other duplication scenarios.
The condition !isVirtualCol(destColumn) && !isAIPromptCol(destColumn)
ensures that AI prompt columns are handled correctly during duplication. However, we should verify that AI prompt columns are properly handled in other duplication scenarios.
✅ Verification successful
Let me analyze the findings and generate additional verification scripts to check the implementation details of AI prompt column handling in different scenarios.
Based on the analysis of the code, I can now generate the final response:
AI prompt columns are properly handled across duplication and import scenarios
The codebase shows consistent handling of AI prompt columns:
- In duplication: AI prompt columns are excluded from column updates along with virtual columns (
!isVirtualCol(destColumn) && !isAIPromptCol(destColumn)
) - In import: AI prompt columns have special workspace and integration handling
- The implementation is consistent across the export-import module
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for other places where AI prompt columns might need special handling
# Test: Search for other column duplication or handling logic
ast-grep --pattern 'if ($cond) {
$$$
isVirtualCol($col)
$$$
}'
# Test: Search for AI prompt column handling in other files
rg "isAIPromptCol"
Length of output: 3619
Script:
#!/bin/bash
# Check implementation details of AI prompt column handling in import and duplication scenarios
ast-grep --pattern 'if ($cond) {
$$$
isAIPromptCol($col)
$$$
}'
# Check for column type validation and processing
rg -A 5 "isAIPromptCol\(.*\)" packages/nocodb/src/modules/jobs/jobs/export-import/
Length of output: 2723
packages/nc-gui/components/smartsheet/Gallery.vue (1)
376-376
: LGTM! Icon change improves UI consistency.
Changed from expand
to maximize
icon for better visual consistency with other expand/maximize actions across the application.
packages/nc-gui/components/smartsheet/expanded-form/index.vue (1)
33
A3DB
0-333
: LGTM: Improved user feedback for URL copy action
The addition of a 5-second delay provides better user feedback after copying the URL, giving users sufficient time to see the confirmation state.
packages/nc-gui/composables/useData.ts (2)
Line range hint 146-160
: Enhanced column options handling for export
Good improvements in handling column options:
- Added support for
fk_model_id
mapping - Preserved workspace and integration values during export
- Added proper handling of
output_column_ids
by mapping throughidMap
Line range hint 180-181
: Improved special column type handling
Good additions for handling special column types:
- Skip formula processing for button columns
- Added support for AI prompt columns in updates
Also applies to: 289-289
packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts (1)
628-636
: LGTM: Added proper handling of AI prompt columns
Good addition of AI prompt column handling during export. The code properly attempts to serialize the value as JSON while gracefully falling back to the original value if serialization fails.
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (4)
182-182
: LGTM: Added AI prompt column filtering
The addition of !isAIPromptCol(a)
to the filter condition is correct, ensuring AI prompt columns are excluded from the initial table creation phase, as they require special handling.
1113-1114
: LGTM: Added AI prompt column check in referenced columns
The addition of isAIPromptCol(a)
to the referenced columns check is correct, ensuring AI prompt columns are properly handled during the import process.
1281-1287
: LGTM: Added workspace validation for AI prompt columns
The workspace validation ensures that AI prompt columns are properly scoped to their workspace, with appropriate nulling of workspace and integration IDs when mismatched.
Line range hint 1288-1300
: LGTM: Added AI prompt column creation logic
The implementation correctly handles the creation of AI prompt columns with their associated properties (integration ID, model, prompt raw).
packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue (2)
997-998
: LGTM: Enhanced AI column type checking
The condition now correctly handles both AI prompt columns and AI buttons, improving the accuracy of AI-related functionality detection.
1021-1022
: LGTM: Added AI button output column handling
The implementation correctly extracts output column IDs for AI buttons, ensuring proper handling of AI-generated content.
packages/nc-gui/components/smartsheet/grid/Table.vue (2)
855-856
: LGTM: Enhanced AI column type checking
The condition now correctly handles both AI prompt columns and AI buttons, maintaining consistency with the InfiniteTable implementation.
Line range hint 879-884
: LGTM: Added AI button output column handling
The implementation correctly extracts output column IDs for AI buttons, maintaining consistency with the InfiniteTable implementation.
packages/nc-gui/composables/useMultiSelect/index.ts (1)
6-6
: LGTM: Type import is correctly placed.
The AIRecordType
import is appropriately grouped with other types from the 'nocodb-sdk' package.
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
Outdated
Show resolved
Hide resolved
<GeneralIcon icon="refresh" :class="{ 'animate-infinite animate-spin': isAiGenerating }" /> | ||
<span class="text-sm font-bold"> {{ isAiGenerating ? 'Re-generating...' : 'Re-generate' }} </span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the usage of Tailwind CSS classes
The class animate-infinite
is not a valid Tailwind CSS utility. The animate-spin
class already provides an infinite spin animation. Remove animate-infinite
to prevent any unexpected behavior.
Apply this diff to correct the class:
<GeneralIcon icon="refresh" :class="{
- 'animate-infinite animate-spin': isAiGenerating
+ 'animate-spin': isAiGenerating
}" />
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<GeneralIcon icon="refresh" :class="{ 'animate-infinite animate-spin': isAiGenerating }" /> | |
<span class="text-sm font-bold"> {{ isAiGenerating ? 'Re-generating...' : 'Re-generate' }} </span> | |
<GeneralIcon icon="refresh" :class="{ 'animate-spin': isAiGenerating }" /> | |
<span class="text-sm font-bold"> {{ isAiGenerating ? 'Re-generating...' : 'Re-generate' }} </span> |
value?: string; | ||
lastModifiedBy?: string; | ||
lastModifiedTime?: string; | ||
isStale?: string; | ||
} = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the type of isStale
to boolean
The isStale
property is used as a boolean in the code. Currently, it's typed as string?
, which can lead to type mismatches. It should be typed as boolean?
to reflect its usage.
Apply this diff to fix the type:
const obj: {
value?: string;
lastModifiedBy?: string;
lastModifiedTime?: string;
- isStale?: string;
+ isStale?: boolean;
} = {};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
value?: string; | |
lastModifiedBy?: string; | |
lastModifiedTime?: string; | |
isStale?: string; | |
} = {}; | |
value?: string; | |
lastModifiedBy?: string; | |
lastModifiedTime?: string; | |
isStale?: boolean; | |
} = {}; |
8c6e440
to
0a49d91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (20)
packages/nc-gui/utils/commonUtils.ts (1)
91-93
: Consider memoization for performance optimizationIf this function is used frequently with the same inputs (e.g., in tight loops or frequent re-renders), consider memoizing the results to avoid redundant string operations.
+const searchCache = new Map<string, Map<string, boolean>>(); + export const ncSearchCompare = (source?: string, query?: string): boolean => { - return (source || '').toLowerCase().includes((query || '').toLowerCase()) + const normalizedSource = source || ''; + const normalizedQuery = query || ''; + + // Check cache + const queryCache = searchCache.get(normalizedSource); + if (queryCache?.has(normalizedQuery)) { + return queryCache.get(normalizedQuery)!; + } + + // Compute result + const result = normalizedSource.toLowerCase().includes(normalizedQuery.toLowerCase()); + + // Cache result + if (!searchCache.has(normalizedSource)) { + searchCache.set(normalizedSource, new Map()); + } + searchCache.get(normalizedSource)!.set(normalizedQuery, result); + + // Prevent unbounded cache growth + if (searchCache.size > 1000) { + const firstKey = searchCache.keys().next().value; + searchCache.delete(firstKey); + } + + return result;packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
: Consider optimizing the copy confirmation UXThe 5-second delay for the copy confirmation might be too long and could confuse users. Additionally, error handling should be added for the copy operation.
Consider applying these improvements:
const () => { - await copy(view.value!.id!) - isViewIdCopied.value = true - - await ncDelay(5000) - - isViewIdCopied.value = false + try { + await copy(view.value!.id!) + isViewIdCopied.value = true + await ncDelay(2000) // Reduce to 2 seconds for better UX + isViewIdCopied.value = false + } catch (e: any) { + message.error(t('msg.error.copyToClipboardError')) + } }packages/nc-gui/components/smartsheet/calendar/MonthView.vue (5)
Line range hint
236-583
: Consider refactoring recordsToDisplay for better maintainabilityThe computed property is quite complex and handles multiple responsibilities. Consider breaking it down into smaller, more focused functions:
- Lane calculation logic
- Single-day event handling
- Multi-day event handling
Example refactor for lane calculation:
+ const calculateLaneForDate = (dateKey: string, duration: number = 1, lanes: boolean[], maxLanes: number): number => { + for (let i = 0; i < maxLanes; i++) { + if (!lanes[i]) { + let isAvailable = true + for (let j = 0; j < duration; j++) { + const checkDate = dayjs(dateKey).add(j, 'day').format('YYYY-MM-DD') + if (recordsInDay[checkDate]?.lanes[i]) { + isAvailable = false + break + } + } + if (isAvailable) return i + } + } + return -1 + }
Line range hint
752-752
: Address TODO comment for drag element selectionThe TODO comment indicates a potential improvement in drag element selection logic.
Would you like me to help implement the drag element selection logic or create a GitHub issue to track this task?
Line range hint
584-751
: Add error boundaries for drag event handlingThe drag event handlers could benefit from try-catch blocks to gracefully handle edge cases and prevent potential crashes.
Example implementation:
const MouseEvent) => { if (!isUIAllowed('dataEdit') || !dragRecord.value) return + try { calculateNewRow(event, false) + } catch (error) { + console.error('Error during drag operation:', error) + stopDrag(event) + } }
Line range hint
933-940
: Optimize template rendering for fields loopThe v-for loop for fields could impact performance with large datasets. Consider using virtual scrolling or pagination for better performance.
Consider using a virtual scroller component:
+ import { VirtualScroller } from '@vueuse/components' + - <template v-for="field in fields" :key="field.id"> + <VirtualScroller + :items="fields" + :item-height="20" + > + <template #item="{ item: field }"> <LazySmartsheetPlainCell v-if="!isRowEmpty(record, field!)" v-model="record.row[field!.title!]" class="text-xs" :bold="fieldStyles[field.id].bold" :column="field" :italic="fieldStyles[field.id].italic" :underline="fieldStyles[field.id].underline" /> + </template> + </VirtualScroller>
Line range hint
841-932
: Enhance accessibility for interactive calendar elementsThe calendar grid and interactive elements could benefit from additional ARIA attributes for better accessibility.
Add necessary ARIA attributes:
<div ref="calendarGridContainer" + role="grid" + aria-label="Calendar Month View" :class="{ 'grid-rows-5': calendarData.weeks.length === 5, 'grid-rows-6': calendarData.weeks.length === 6, 'grid-rows-7': calendarData.weeks.length === 7, }" class="grid" style="height: calc(100% - 1.59rem)" @drop="dropEvent" > <div v-for="week in calendarData.weeks" :key="week.weekIndex" + role="row" :class="calendarData.gridClass" data-testid="nc-calendar-month-week" >packages/nc-gui/components/workspace/project/AiCreateProject.vue (1)
Line range hint
164-199
: Improve timer cleanup to prevent memory leaksWhile the error handling and state management are solid, the timer cleanup could be improved to prevent potential memory leaks in edge cases.
Consider adding cleanup in component unmount:
+ onBeforeUnmount(() => { + if (timerId) { + clearInterval(timerId) + timerId = undefined + } + })Also, consider adding a safety check before setting new intervals:
- timerId = setInterval(displayCharByChar, 40) + if (timerId) { + clearInterval(timerId) + } + timerId = setInterval(displayCharByChar, 40)packages/nc-gui/components/cell/AI.vue (1)
81-92
: Consider removing the arbitrary timeout.The 100ms timeout for resetting
isAiEdited
seems arbitrary and could be replaced with a more deterministic approach, such as using Vue's nextTick or handling the state change in the proper lifecycle hook.- setTimeout(() => { - isAiEdited.value = false - }, 100) + nextTick(() => { + isAiEdited.value = false + })packages/nc-gui/components/ai/PromptWithFields.vue (3)
Line range hint
19-31
: Add TypeScript documentation for the readOnly prop.Consider adding JSDoc documentation to describe the purpose and usage of the
readOnly
prop.defineProps<{ modelValue: string options?: ColumnType[] autoFocus?: boolean promptFieldTagClassName?: string suggestionIconClassName?: string placeholder?: string + /** When true, the prompt editor becomes non-editable */ readOnly?: boolean }>()
63-68
: Optimize the filter implementation.The current implementation makes multiple calls to
toLowerCase()
and uses nested optional chaining. Consider optimizing it for better performance.- return ( - props.options?.filter( - (o) => - o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), - ) ?? [] - ) + const lowercaseQuery = query.toLowerCase() + return props.options?.filter((o) => { + const title = o.title?.toLowerCase() + return title ? (title.includes(lowercaseQuery) || `${title}}` === lowercaseQuery) : false + }) ?? []
185-202
: Enhance button accessibility and UX.The button's disabled state could be more accessible and user-friendly.
<NcButton size="xs" type="text" class="nc-prompt-with-field-suggestion-btn !px-1" + :class="{ 'cursor-not-allowed': readOnly }" + :aria-label="readOnly ? 'Field suggestion disabled in read-only mode' : 'Add field suggestion'" :disabled="readOnly" @click.stop="newFieldSuggestionNode" >packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: Avoid using 'this' in static methodsUsing
this
inside static methods can be confusing becausethis
refers to the class itself, not an instance. Consider using the class nameLongTextColumn
instead for clarity.🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.(lint/complexity/noThisInStatic)
85-85
: Avoid using 'this' in static methodsUsing
this
inside static methods can be confusing becausethis
refers to the class itself, not an instance. Consider using the class nameLongTextColumn
instead for clarity.🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.(lint/complexity/noThisInStatic)
packages/nocodb/src/models/AIColumn.ts (2)
26-26
: Avoid using 'this' in static methodsUsing
this
inside static methods can be confusing because it refers to the class rather than an instance. Replacethis
with the class nameAIColumn
for better clarity.🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.(lint/complexity/noThisInStatic)
40-40
: Avoid using 'this' in static methodsUsing
this
inside static methods can be confusing because it refers to the class rather than an instance. Replacethis
with the class nameAIColumn
for better clarity.🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.(lint/complexity/noThisInStatic)
packages/nc-gui/utils/columnUtils.ts (1)
290-296
: Refactor duplicated logic in 'isColumnInvalid' functionThe logic for determining the invalid state of AI-related columns in both the
Button
andLongText
cases is duplicated. Consider extracting this logic into a helper function to improve maintainability and reduce code duplication.Also applies to: 303-309
packages/nocodb/src/services/columns.service.ts (2)
1562-1629
: LGTM! Consider enhancing error messages for better debugging.The AI prompt column handling implementation is well-structured and handles both conversion scenarios correctly. The column alias substitution is properly implemented with error checking.
Consider enhancing the error message in the field not found check to include more context:
- NcError.badRequest(`Field '${p1}' not found`); + NcError.badRequest(`Field '${p1}' not found in table '${table.title}'`);
2266-2288
: Consider extracting common prompt processing logic.The prompt processing logic is duplicated between columnAdd and columnUpdate methods. Consider extracting it into a shared utility function for better maintainability.
Example refactor:
+ private async processAIPrompt(context: NcContext, table: Model, promptRaw: string): Promise<string> { + if (!promptRaw) return ''; + + await table.getColumns(context); + + return promptRaw.replace(/{(.*?)}/g, (match, p1) => { + const column = table.columns.find((c) => c.title === p1); + + if (!column) { + NcError.badRequest(`Field '${p1}' not found in table '${table.title}'`); + } + + return `{${column.id}}`; + }); + } // In columnUpdate and columnAdd: - prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => { - const column = table.columns.find((c) => c.title === p1); - if (!column) { - NcError.badRequest(`Field '${p1}' not found`); - } - return `{${column.id}}`; - }); + prompt = await this.processAIPrompt(context, table, colBody.prompt_raw);packages/nocodb/src/db/BaseModelSqlv2.ts (1)
8635-8653
: Add error handling for non-array JSON valuesThe method
_convertJsonType
assumes thatd[col.id]
is either a string or an array. If it's an object, the code may not handle it correctly.if (d[col.id] && typeof d[col.id] === 'string') { try { d[col.id] = JSON.parse(d[col.id]); } catch {} } + else if (d[col.id] && typeof d[col.id] === 'object' && !Array.isArray(d[col.id])) { + // Handle object case if necessary + // Possibly validate or process the object + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (72)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/dashboard/settings/base/index.vue
(0 hunks)packages/nc-gui/components/feed/View.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/calendar/MonthView.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(5 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(5 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (2)
- packages/nc-gui/components/dashboard/settings/base/index.vue
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (41)
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/models/index.ts
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/components/ai/Settings.vue
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/components/feed/View.vue
- packages/nocodb/src/db/sortV2.ts
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nocodb/src/models/Integration.ts
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nc-gui/components/general/Loader.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
👮 Files not reviewed due to content moderation or server errors (8)
- packages/nc-gui/composables/useNocoAi.ts
- packages/nc-gui/composables/useIntegrationsStore.ts
- packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nc-gui/components/smartsheet/details/Fields.vue
- packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
- packages/nc-gui/components/smartsheet/grid/Table.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (38)
packages/nc-gui/utils/commonUtils.ts (1)
74-93
: Implementation looks good!
The function is well-documented with comprehensive examples and handles edge cases appropriately. Note that there's already an existing review comment about adding test coverage.
packages/nc-gui/components/cell/TextArea.vue (6)
Line range hint 408-421
: LGTM! Well-structured warning UI
The warning UI for stale AI content is well-implemented with clear messaging and proper styling.
526-527
: Remove redundant animation class
The animate-infinite
class is redundant as animate-spin
already provides an infinite animation.
629-630
: Remove redundant animation class
The animate-infinite
class is redundant as animate-spin
already provides an infinite animation.
Line range hint 637-648
: LGTM! Clear and well-structured warning modal
The warning modal implementation follows good UI practices with clear messaging and proper visual hierarchy.
Line range hint 708-724
: LGTM! Well-organized styles
The styles for AI-specific cases are well-organized and follow the project's conventions.
100-103
:
Remove non-null assertions to prevent potential runtime errors
The use of non-null assertions (!
) in meta.value!.columns!
may lead to runtime errors if meta.value
or columns
are undefined
.
Apply this diff to handle possible null values:
const rowId = computed(() => {
- return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
+ if (!meta.value?.columns || !currentRow.value?.row) {
+ return null
+ }
+ return extractPkFromRow(currentRow.value.row, meta.value.columns)
})
Likely invalid or redundant comment.
packages/nc-gui/components/workspace/project/AiCreateProject.vue (1)
31-32
: LGTM: Event tracking implementation
The event emitter is properly integrated to track AI base generation events with the constructed prompt as payload.
Also applies to: 163-163
packages/nc-gui/components/cell/AI.vue (6)
16-18
: LGTM: Clean implementation of permission management.
The addition of role-based permissions and public status checking enhances the component's security model.
63-63
: LGTM: Good defensive programming.
The addition of filter(Boolean)
prevents potential issues with undefined values in the output columns array.
111-112
: LGTM: Proper handling of reactivity.
Creating a new object with the spread operator ensures proper reactivity in Vue's change detection system.
118-120
: LGTM: Well-structured button state management.
The new computed property effectively consolidates multiple conditions for button state management, improving code maintainability.
Line range hint 160-187
: LGTM: Well-structured styling with accessibility considerations.
The styling changes follow Tailwind conventions and properly handle focus states for accessibility.
47-47
: Verify the simplified AI integration check.
The change from checking array inclusion to a simple truthiness check of fkIntegrationId
might skip validation of whether the integration actually exists in aiIntegrations
.
✅ Verification successful
Validation of AI integration ID is handled elsewhere in the codebase
The simplified check in AI.vue
is correct as the validation of fkIntegrationId
against aiIntegrations
is properly handled in other parts of the codebase:
columnUtils.ts
validates integration existence usingncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')
useNocoAi.ts
composable providesisAiIntegrationAvailableInList
function for validationSettings.vue
ensures valid integration ID assignment from available integrations
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any validation of fk_integration_id against aiIntegrations
rg -A 5 "fk_integration_id.*aiIntegrations" --type vue
Length of output: 84
Script:
#!/bin/bash
# Search for any validation of fk_integration_id in the codebase
rg -A 5 "fkIntegrationId.*aiIntegrations"
# Also search for the pattern without camelCase
rg -A 5 "fk_integration_id.*aiIntegrations"
# Search for any usage of aiIntegrations array
rg -A 5 "aiIntegrations.*\["
Length of output: 2451
packages/nc-gui/components/ai/PromptWithFields.vue (1)
213-213
: Consider improving button positioning for long content.
The absolute positioning of the suggestion button might cause overlap issues with long content. Consider using a flex container with proper spacing instead.
Also applies to: 226-228
packages/nocodb/src/utils/dataConversion.ts (1)
1-150
: Implementation looks good
The data conversion functions for handling AI record types across different database sources are well-implemented and correctly handle various SQL dialects.
packages/nc-gui/components/smartsheet/Cell.vue (1)
150-176
: Well-structured 'cellType' computed property
The introduction of the cellType
computed property simplifies the template rendering logic and enhances maintainability by centralizing the cell type determination.
packages/nc-gui/components/smartsheet/expanded-form/index.vue (1)
330-333
: LGTM: Added delay for better UX
The addition of a 5-second delay before resetting isRecordLinkCopied
improves user feedback after copying the URL.
packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts (2)
5-5
: LGTM: Added import for AI metadata property
The import of LongTextAiMetaProp
supports the new AI-enabled LongText field functionality.
832-872
: LGTM: Added database-specific handling for AI-enabled LongText fields
The implementation correctly handles AI-enabled LongText fields across different database types:
- PostgreSQL: Uses
TRIM('"' FROM (??::jsonb->>'value'))
- MySQL: Uses
JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))
- SQLite: Uses
json_extract(??, '$.value')
- MSSQL: Uses
JSON_VALUE(??, '$.value')
The code gracefully falls back to returning the column name directly when the field is not AI-enabled.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (5)
186-188
: LGTM: Added condition to hide AI prompt option in edit mode
The hideAiPromptOnEdit
condition correctly prevents the selection of AI prompt type when editing existing columns.
Line range hint 251-269
: LGTM: Enhanced onSelectType to handle AI prompt columns
The function now correctly handles AI prompt columns by:
- Setting UIType to LongText
- Configuring the appropriate metadata with LongTextAiMetaProp
602-604
: LGTM: Added helper function for AI prompt detection
The isAiPromptSelectOption
function provides a clean way to detect AI prompt columns in the UI.
1072-1078
: LGTM: Updated icon selection logic for AI-related columns
The icon selection logic now properly handles both AI button and AI prompt column types, improving the UI consistency.
1139-1143
: LGTM: Added LongText options component with navigation support
The component now includes navigation to integrations for AI-related functionality.
packages/nocodb/src/db/conditionV2.ts (1)
596-612
: LGTM! Database-specific JSON value extraction for AI prompts
The implementation correctly handles JSON value extraction for AI prompt columns across different database types:
- PostgreSQL: Uses
jsonb->>'value'
with TRIM for proper JSON text extraction - MySQL: Uses
JSON_UNQUOTE(JSON_EXTRACT())
for proper JSON string handling - SQLite: Uses
json_extract()
for basic JSON path extraction - MSSQL: Uses
JSON_VALUE()
for direct value access
packages/nocodb/src/models/Column.ts (5)
452-469
: LGTM! AI column insertion for LongText columns
The implementation correctly handles AI column insertion when the column is marked as an AI prompt through the LongTextAiMetaProp
metadata property.
573-577
: LGTM! AI column options retrieval
Proper handling of AI column options retrieval for LongText columns marked as AI prompts.
Line range hint 1244-1295
: LGTM! Workspace-aware integration handling
The implementation correctly handles workspace and integration IDs for both button and AI prompt columns:
- Clears workspace-specific data when importing across different workspaces
- Maintains data integrity by nullifying integration-specific fields
1957-1970
: LGTM! Bulk AI column insertion
The implementation correctly handles bulk insertion of AI columns with proper metadata and integration handling.
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect. It should check for the actual column ID to detect whether the prompt includes the deleted column ID.
Apply this diff to fix the placeholder matching:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (3)
182-182
: LGTM! Filter out AI prompt columns during table creation
Correctly excludes AI prompt columns from initial table creation as they require special handling.
1113-1114
: LGTM! Include AI prompt columns in referenced column set
Properly includes AI prompt columns in the referenced column set for processing after table creation.
1281-1295
: LGTM! Workspace-aware AI prompt column import
The implementation correctly:
- Handles workspace-specific data during import
- Clears integration settings when importing across workspaces
- Preserves column metadata and prompt configuration
packages/nocodb/src/db/BaseModelSqlv2.ts (3)
5721-5723
: 🛠️ Refactor suggestion
Consider using Promise.all with insertDatas.map
The current implementation may not properly handle asynchronous operations in the map function. Consider using Promise.all to properly await all async operations.
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
10062-10104
: Verify AI prompt column handling
The new AI prompt column handling logic looks good but should be verified for edge cases:
- Ensure proper handling when cookie.system is undefined
- Verify behavior when value is null/undefined
✅ Verification successful
Based on the code analysis, I can now provide a definitive response about the AI prompt column handling:
AI prompt column handling is properly implemented with null safety
The implementation in BaseModelSqlv2.ts handles both edge cases correctly:
- For
cookie.system
being undefined:
- The code uses optional chaining with
cookie?.system
- When undefined, it falls into the
else
block which has proper null-safe handling using optional chaining for all properties (cookie?.user?.id
)
- For
value
being null/undefined:
- The check
if (data[column.column_name])
ensures the code block only executes when there's a value - When the value is null/undefined, the code skips the block entirely, preserving the null value
- When value is an object, it safely extracts the
value
property withvalue = value.value
The implementation follows a defensive programming approach with proper null checks and optional chaining throughout the code path.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for AI prompt column usage to verify edge cases
ast-grep --pattern $'isAIPromptCol($_) && $_.column_name'
Length of output: 58
Script:
#!/bin/bash
# Search for isAIPromptCol function definition and usage
ast-grep --pattern 'function isAIPromptCol'
# Search for AI prompt column related code
rg "isAIPromptCol" -A 5 -B 5
Length of output: 29979
10107-10137
: Verify isStale flag handling for AI columns
The isStale flag handling for AI columns looks good but has a potential issue:
- The referencedColumnIds extraction using regex may miss some edge cases
- Consider validating the column IDs before using them
✅ Verification successful
Based on the gathered information, I can now provide the final response:
The regex pattern for AI column references is properly implemented and validated
The implementation is safe and handles edge cases appropriately:
- The pattern
/{(.*?)}/g
is used consistently across the codebase - Column IDs are validated before use:
- In
columns.service.ts
, there's explicit validation that checks if referenced columns exist - Error handling is in place with
NcError.badRequest
when column is not found
- In
- The prompt_raw field is properly processed with column title to ID conversion
- The implementation includes proper error storage and handling through the
error
field in AIColumn model
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for AI column references to verify pattern matching
rg -A 5 "colOptions.prompt?.match"
Length of output: 36
Script:
#!/bin/bash
# Search for AI column prompt pattern matching with broader context
rg -A 10 "prompt\?\.match"
# Search for AI column related code
ast-grep --pattern 'const referencedColumnIds = $_?.match(/{(.*?)}/g)'
# Look for other AI column related patterns
rg -A 10 "isAIPromptCol"
Length of output: 32793
Script:
#!/bin/bash
# Search for prompt pattern matching implementations
ast-grep --pattern 'prompt = $_?.replace(/{(.*?)}/g, $_)'
# Look for other prompt pattern matching implementations
rg -A 5 "prompt.*replace.*{.*?}"
# Search for prompt validation or processing
rg -A 5 "prompt_raw"
Length of output: 9907
packages/nc-gui/components/smartsheet/Kanban.vue (1)
1146-1146
: Icon update enhances consistency
The use of iconMap.maximize
for the expand record action aligns with the application's iconography updates, ensuring a consistent user experience.
let prompt = `${aiFormState.value.prompt}` | ||
|
||
// Append optional information if provided | ||
if (aiFormState.value.organization?.trim()) { | ||
prompt += ` | Organization: ${aiFormState.value.organization}` | ||
} | ||
if (aiFormState.value.industry?.trim()) { | ||
prompt += ` | Industry: ${aiFormState.value.industry}` | ||
} | ||
if (aiFormState.value.audience?.trim()) { | ||
prompt += ` | Audience: ${aiFormState.value.audience}` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add input sanitization for prompt construction
While the prompt construction logic handles optional fields correctly, it's recommended to sanitize user inputs to prevent potential prompt injection attacks or unexpected behavior.
Consider adding sanitization:
- let prompt = `${aiFormState.value.prompt}`
+ const sanitizeInput = (input) => input.replace(/[|]/g, '').trim()
+ let prompt = sanitizeInput(aiFormState.value.prompt)
if (aiFormState.value.organization?.trim()) {
- prompt += ` | Organization: ${aiFormState.value.organization}`
+ prompt += ` | Organization: ${sanitizeInput(aiFormState.value.organization)}`
}
if (aiFormState.value.industry?.trim()) {
- prompt += ` | Industry: ${aiFormState.value.industry}`
+ prompt += ` | Industry: ${sanitizeInput(aiFormState.value.industry)}`
}
if (aiFormState.value.audience?.trim()) {
- prompt += ` | Audience: ${aiFormState.value.audience}`
+ prompt += ` | Audience: ${sanitizeInput(aiFormState.value.audience)}`
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let prompt = `${aiFormState.value.prompt}` | |
// Append optional information if provided | |
if (aiFormState.value.organization?.trim()) { | |
prompt += ` | Organization: ${aiFormState.value.organization}` | |
} | |
if (aiFormState.value.industry?.trim()) { | |
prompt += ` | Industry: ${aiFormState.value.industry}` | |
} | |
if (aiFormState.value.audience?.trim()) { | |
prompt += ` | Audience: ${aiFormState.value.audience}` | |
} | |
const sanitizeInput = (input) => input.replace(/[|]/g, '').trim() | |
let prompt = sanitizeInput(aiFormState.value.prompt) | |
// Append optional information if provided | |
if (aiFormState.value.organization?.trim()) { | |
prompt += ` | Organization: ${sanitizeInput(aiFormState.value.organization)}` | |
} | |
if (aiFormState.value.industry?.trim()) { | |
prompt += ` | Industry: ${sanitizeInput(aiFormState.value.industry)}` | |
} | |
if (aiFormState.value.audience?.trim()) { | |
prompt += ` | Audience: ${sanitizeInput(aiFormState.value.audience)}` | |
} |
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 | ||
tippy(el, { | ||
content: `<div class="tooltip text-xs">${tooltip.value}</div>`, | ||
placement: 'top', | ||
allowHTML: true, | ||
arrow: true, | ||
animation: 'fade', | ||
duration: 0, | ||
maxWidth: '200px', | ||
}) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tooltip cleanup on component unmount.
The tooltips are properly initialized, but they should be cleaned up when the component is unmounted to prevent memory leaks.
+ 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
- tippy(el, {
+ const instance = tippy(el, {
content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
placement: 'top',
allowHTML: true,
arrow: true,
animation: 'fade',
duration: 0,
maxWidth: '200px',
})
+ tooltipInstances.push(instance)
})
}
+ onBeforeUnmount(() => {
+ tooltipInstances.forEach(instance => instance.destroy())
+ tooltipInstances.length = 0
+ })
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
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 | |
tippy(el, { | |
content: `<div class="tooltip text-xs">${tooltip.value}</div>`, | |
placement: 'top', | |
allowHTML: true, | |
arrow: true, | |
animation: 'fade', | |
duration: 0, | |
maxWidth: '200px', | |
}) | |
}) | |
} | |
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: `<div class="tooltip text-xs">${tooltip.value}</div>`, | |
placement: 'top', | |
allowHTML: true, | |
arrow: true, | |
animation: 'fade', | |
duration: 0, | |
maxWidth: '200px', | |
}) | |
tooltipInstances.push(instance) | |
}) | |
} | |
onBeforeUnmount(() => { | |
tooltipInstances.forEach(instance => instance.destroy()) | |
tooltipInstances.length = 0 | |
}) |
9692ad1
to
5559fe2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Outside diff range and nitpick comments (18)
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (1)
687-688
: Consider refactoring repeated conditional styling logicThe conditional styling logic is repeated multiple times with similar patterns. This could be refactored into a computed property or helper function to improve maintainability.
Consider refactoring like this:
+ const getStatusClass = (condition: boolean) => ({ + 'bg-nc-bg-green-dark text-nc-content-green-dark': condition, + 'bg-nc-bg-red-dark text-nc-content-red-dark': !condition + }) - class="text-nc-border-gray-dark': !(selectedRecordPk && outputColumnIds.length && inputColumns.length), - 'text-nc-content-purple-dark': !!(selectedRecordPk && outputColumnIds.length && inputColumns.length)" + :class="getStatusClass(selectedRecordPk && outputColumnIds.length && inputColumns.length)"This would make the code more maintainable and reduce duplication.
Also applies to: 698-702, 711-715, 724-728
packages/nc-gui/components/smartsheet/header/Menu.vue (4)
48-61
: Enhance error handling for AI integrations.The
columnInvalid
computed property handles validation but could benefit from additional error handling for edge cases.Consider adding type validation and default values:
const columnInvalid = computed<{ isInvalid: boolean; tooltip: string }>(() => { if (!column?.value) { return { isInvalid: false, - tooltip: '', + tooltip: 'Column not initialized', } } + if (!aiIntegrations?.value) { + return { + isInvalid: false, + tooltip: 'AI integrations not available', + } + } return isColumnInvalid(column.value, aiIntegrations.value, isPublic.value || !isUIAllowed('dataEdit')) })
431-434
: Improve delay handling in copy operation.The current implementation uses a fixed delay which might not be ideal for all scenarios.
Consider making the delay configurable and using
try-finally
:-await ncDelay(5000) -isFieldIdCopied.value = false +try { + await ncDelay(import.meta.env.VITE_COPY_FEEDBACK_DELAY || 5000) +} finally { + isFieldIdCopied.value = false +}
451-451
: Enhance accessibility for invalid column state.The UI changes for invalid column state could benefit from improved accessibility.
Add ARIA attributes for better screen reader support:
-<GeneralIcon v-if="columnInvalid.isInvalid && !isExpandedForm" class="text-red-300 w-3.5 h-3.5" icon="alertTriangle" /> +<GeneralIcon + v-if="columnInvalid.isInvalid && !isExpandedForm" + class="text-red-300 w-3.5 h-3.5" + icon="alertTriangle" + role="img" + :aria-label="$t(columnInvalid.tooltip)" +/>Also applies to: 461-461, 464-464
Line range hint
1-24
: Consider extracting type definitions.The component has several complex prop and computed property types that could benefit from being extracted into separate type definitions.
Consider creating a separate types file:
// types/menu.types.ts export interface ColumnInvalidState { isInvalid: boolean tooltip: string } export interface MenuProps { virtual?: boolean isOpen: boolean isHiddenCol?: boolean }packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
130-144
: Simplify thehandleDisableSubmitBtn
logicThe current implementation has nested conditions that can be simplified.
Consider this more concise implementation:
const handleDisableSubmitBtn = () => { - if (!isEnabledGenerateText.value) { - if (disableSubmitBtn.value) { - disableSubmitBtn.value = false - } - return - } - - if (isPreviewEnabled.value) { - disableSubmitBtn.value = false - } else { - disableSubmitBtn.value = true - } + disableSubmitBtn.value = isEnabledGenerateText.value && !isPreviewEnabled.value }
156-311
: Enhance accessibility for interactive elementsWhile the UI structure is good, some interactive elements could benefit from improved accessibility.
Consider these improvements:
- Add
aria-label
to switches:-<NcSwitch v-model:checked="richMode" :disabled="isEnabledGenerateText"> +<NcSwitch + v-model:checked="richMode" + :disabled="isEnabledGenerateText" + :aria-label="$t('labels.enableRichText')" +>
- Add
aria-live
region for the preview section:-<div v-if="previewRow.row?.[previewFieldTitle]?.value"> +<div + v-if="previewRow.row?.[previewFieldTitle]?.value" + aria-live="polite" + aria-atomic="true" +>packages/nc-gui/components/cell/AI.vue (1)
Line range hint
2151-2151
: Correct the function name to 'expandAndLoseFocus'There's a typographical error in the function name
expandAndLooseFocus
. The correct spelling isexpandAndLoseFocus
.Apply this diff to fix the typo:
-const expandAndLooseFocus = (row: Row, col: Record<string, any>) => { +const expandAndLoseFocus = (row: Row, col: Record<string, any>) => {Also, update all references to this function accordingly.
packages/nocodb-sdk/src/lib/helperFunctions.ts (1)
231-245
: Enhance type safety and documentation for helper functions.The new helper functions would benefit from improved type safety and documentation.
Consider applying these improvements:
+/** + * Safely parses a JSON string, returning the original value if parsing fails + * @param v - The value to parse + * @returns The parsed object if successful, otherwise the original value + */ -export function parseHelper(v: any): any { +export function parseHelper<T = unknown>(v: unknown): T | unknown { try { return typeof v === 'string' ? JSON.parse(v) : v } catch { return v } } +/** + * Safely stringifies a value to JSON, returning the original value if stringification fails + * @param v - The value to stringify + * @returns The JSON string if successful, otherwise the original value + */ -export function stringifyHelper(v: any): string { +export function stringifyHelper(v: unknown): string { try { - return JSON.stringify(v) + return typeof v === 'undefined' ? 'undefined' : JSON.stringify(v) } catch { return v as string } }packages/nocodb/src/db/sortV2.ts (1)
202-232
: Extract database-specific JSON extraction logic.The database-specific JSON extraction logic should be moved to a separate function for better maintainability.
Consider extracting the logic:
+function getJsonValueExtractor(knex: Knex, columnName: string) { + if (knex.clientType() === 'pg') { + return knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [columnName]); + } else if (knex.clientType().startsWith('mysql')) { + return knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [columnName]); + } else if (knex.clientType() === 'sqlite3') { + return knex.raw(`json_extract(??, '$.value')`, [columnName]); + } else if (knex.clientType() === 'mssql') { + return knex.raw(`JSON_VALUE(??, '$.value')`, [columnName]); + } + return null; +} case UITypes.LongText: { if (isAIPromptCol(column)) { - let col; - if (knex.clientType() === 'pg') { - col = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [ - column.column_name, - ]); - } else if (knex.clientType().startsWith('mysql')) { - col = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [ - column.column_name, - ]); - } else if (knex.clientType() === 'sqlite3') { - col = knex.raw(`json_extract(??, '$.value')`, [ - column.column_name, - ]); - } else if (knex.clientType() === 'mssql') { - col = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]); - } + const col = getJsonValueExtractor(knex, column.column_name); qb.orderBy(col, sort.direction || 'asc', nulls); } else { qb.orderBy(packages/nc-gui/components/ai/PromptWithFields.vue (1)
Line range hint
141-157
: Improve editor focus handling.The focus handling could be more robust by considering the
readOnly
state and potential race conditions.Consider these improvements:
onMounted(async () => { await until(() => vModel.value !== null && vModel.value !== undefined).toBeTruthy() // replace {id} with <span data-type="mention" data-id="id"></span> const renderContent = vModel.value .replace(/\{(.*?)\}/g, '<span data-type="mention" data-id="$1"></span>') .trim() .replace(/\n/g, '<br>') editor.value?.commands.setContent(renderContent) - if (autoFocus.value) { + if (autoFocus.value && !readOnly.value && editor.value) { setTimeout(() => { - editor.value?.chain().focus().setTextSelection(vModel.value.length).run() + editor.value.chain().focus().setTextSelection(vModel.value.length).run() }, 100) } })packages/nc-gui/components/ai/Settings.vue (1)
70-73
: Consider adding a comment explaining the early returnThe early return when
isEditColumn
is true could benefit from a brief comment explaining why we skip initialization in edit mode.+ // Skip initialization in edit mode as the values are already set if (isEditColumn.value) { return }
packages/nc-gui/utils/columnUtils.ts (1)
269-278
: Consider splitting the validation logicThe
isColumnInvalid
function is becoming complex with multiple responsibilities. Consider extracting AI-specific validation into a separate function.+ const isAiColumnInvalid = ( + colOptions: ButtonType, + aiIntegrations: Partial<IntegrationType>[], + isReadOnly: boolean + ): { isInvalid: boolean; tooltip: string } => { + return { + isInvalid: + !colOptions.fk_integration_id || + (isReadOnly + ? false + : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')), + tooltip: 'title.aiIntegrationMissing', + } + }packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts (1)
832-872
: Consider extracting database-specific SQL queries into constantsThe implementation correctly handles different database types, but the SQL queries could be extracted into constants for better maintainability and reusability.
+ const DB_SPECIFIC_VALUE_EXTRACTORS = { + pg: (colName: string) => `TRIM('"' FROM (${colName}::jsonb->>'value'))`, + mysql: (colName: string) => `JSON_UNQUOTE(JSON_EXTRACT(${colName}, '$.value'))`, + sqlite3: (colName: string) => `json_extract(${colName}, '$.value')`, + mssql: (colName: string) => `JSON_VALUE(${colName}, '$.value')` + }; case UITypes.LongText: { if (col.meta?.[LongTextAiMetaProp] === true) { - if (knex.clientType() === 'pg') { - aliasToColumn[col.id] = async (): Promise<any> => ({ - builder: knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [col.column_name]), - }); - } else if (knex.clientType().startsWith('mysql')) { - aliasToColumn[col.id] = async (): Promise<any> => ({ - builder: knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [col.column_name]), - }); - } else if (knex.clientType() === 'sqlite3') { - aliasToColumn[col.id] = async (): Promise<any> => ({ - builder: knex.raw(`json_extract(??, '$.value')`, [col.column_name]), - }); - } else if (knex.clientType() === 'mssql') { - aliasToColumn[col.id] = async (): Promise<any> => ({ - builder: knex.raw(`JSON_VALUE(??, '$.value')`, [col.column_name]), - }); - } + const dbType = knex.clientType(); + const extractor = DB_SPECIFIC_VALUE_EXTRACTORS[dbType]; + if (extractor) { + aliasToColumn[col.id] = async (): Promise<any> => ({ + builder: knex.raw(extractor('??'), [col.column_name]), + }); + } } else { aliasToColumn[col.id] = () => Promise.resolve({ builder: col.column_name }); } break; }packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (2)
Line range hint
249-267
: Consider using a type map for preload configurationsThe type selection logic could be more maintainable by using a map of type configurations.
+ const TYPE_PRELOAD_CONFIG = { + [AIButton]: { + uidt: UITypes.Button, + preload: { type: ButtonActionsType.Ai } + }, + [AIPrompt]: { + uidt: UITypes.LongText, + preload: { meta: { [LongTextAiMetaProp]: true } } + } + }; const UITypes | typeof AIButton | typeof AIPrompt, fromSearchList = false) => { if (fromSearchList && !isEdit.value && aiAutoSuggestMode.value) { onInit() } - if (uidt === AIButton) { - formState.value.uidt = UITypes.Button - preload = { - type: ButtonActionsType.Ai, - } - } else if (uidt === AIPrompt) { - formState.value.uidt = UITypes.LongText - preload = { - meta: { - [LongTextAiMetaProp]: true, - }, - } + const config = TYPE_PRELOAD_CONFIG[uidt]; + if (config) { + formState.value.uidt = config.uidt; + preload = config.preload; } else { formState.value.uidt = uidt } onUidtOrIdTypeChange(preload) }
1070-1076
: Consider using a computed property for icon selectionThe icon selection logic in the template could be moved to a computed property for better maintainability and reusability.
+ const getTypeIcon = computed(() => (opt: { name: string }) => { + if (isAiButtonSelectOption(opt.name) && !isColumnTypeOpen.value) { + return iconMap.cellAiButton; + } + if (isAiPromptSelectOption(opt.name) && !isColumnTypeOpen.value) { + return iconMap.cellAi; + } + return opt.icon; + }); - :is=" - isAiButtonSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAiButton - : isAiPromptSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAi - : opt.icon - " + :is="getTypeIcon(opt)"packages/nocodb/src/db/BaseModelSqlv2.ts (2)
10062-10104
: Add type safety and validation for AI prompt dataThe AI prompt data handling logic could benefit from stronger type safety and validation of the input data structure.
Consider adding TypeScript interfaces and validation:
interface AiPromptData { value: string; lastModifiedBy?: string | null; lastModifiedTime?: string | null; isStale?: boolean; } // Then validate the data structure: if (data[column.column_name]) { let value = data[column.column_name]; // Validate input if (typeof value === 'object' && !value.value) { throw new Error('AI prompt data must contain a value property'); } if (typeof value === 'object') { value = value.value; } if (typeof value !== 'string') { throw new Error('AI prompt value must be a string'); } const obj: AiPromptData = { value, lastModifiedBy: null, lastModifiedTime: null, isStale: false }; // ... rest of the logic }
10107-10137
: Optimize AI column dependency tracking and add error handlingThe AI column isStale handling could be optimized for performance and include better error handling for edge cases.
- Cache the referenced column IDs to avoid repeated regex matching:
// Add column option type interface AiColumnOptions { prompt: string; referencedColumnIds?: string[]; // Cache after first extraction } // Cache referenced columns on first access if (!aiColumn.colOptions.referencedColumnIds) { aiColumn.colOptions.referencedColumnIds = aiColumn.colOptions.prompt ?.match(/{(.*?)}/g) ?.map(id => id.replace(/{|}/g, '')) ?? []; } const referencedColumnIds = aiColumn.colOptions.referencedColumnIds;
- Add validation for referenced columns:
const referencedColumns = referencedColumnIds .map(id => { const col = this.model.columnsById[id]; if (!col) { logger.warn(`Referenced column ${id} not found in AI prompt ${aiColumn.title}`); } return col; }) .filter(Boolean);
- Consider adding a debounce mechanism to avoid excessive isStale updates when multiple referenced columns change rapidly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (72)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/dashboard/settings/base/index.vue
(0 hunks)packages/nc-gui/components/feed/View.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/calendar/MonthView.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(5 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(5 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (2)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
- packages/nc-gui/components/dashboard/settings/base/index.vue
🚧 Files skipped from review as they are similar to previous changes (46)
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nocodb/src/models/index.ts
- packages/nocodb-sdk/src/lib/index.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/components/feed/View.vue
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/utils/commonUtils.ts
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/general/Loader.vue
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/smartsheet/expanded-form/index.vue
- packages/nocodb/src/models/Integration.ts
- packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/smartsheet/calendar/MonthView.vue
- packages/nc-gui/components/smartsheet/Kanban.vue
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/cell/TextArea.vue
👮 Files not reviewed due to content moderation or server errors (2)
- packages/nc-gui/components/smartsheet/details/Fields.vue
- packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (38)
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (2)
369-369
: LGTM: AI Settings integration
The is-edit-column
prop is correctly implemented and follows Vue.js naming conventions.
471-474
: LGTM: Tooltip implementation enhances UX
The tooltip implementation with show-on-truncate-only
is a good UX practice, showing additional information only when the text is truncated.
packages/nc-gui/components/smartsheet/header/Menu.vue (1)
52-61
: Verify error handling in async operations.
The component contains multiple async operations that should be verified for proper error handling.
Also applies to: 431-434
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (4)
4-8
: LGTM: Props and emits follow Vue 3 conventions
The component correctly implements the Vue 3 v-model pattern using modelValue
prop and update:modelValue
emit.
314-340
: LGTM: Well-structured styles
The styles are properly scoped and effectively use Tailwind utilities along with deep selectors for child component styling.
48-58
:
Initialize vModel.value.meta
before assignment
The setter of isEnabledGenerateText
could throw an error if vModel.value.meta
is undefined.
Apply this fix:
set: (value: boolean) => {
+ if (!vModel.value.meta) vModel.value.meta = {}
vModel.value.meta[LongTextAiMetaProp] = value
vModel.value.prompt_raw = ''
previewRow.value.row = {}
isAlreadyGenerated.value = false
},
66-104
:
Add error handling for the generateRows
asynchronous call
The generate
function should handle potential errors from the generateRows
call.
Apply this fix:
const generate = async () => {
generatingPreview.value = true
await loadViewData()
const pk = formattedData.value.length ? extractPkFromRow(unref(formattedData.value[0].row), meta.value?.columns || []) : ''
if (!formattedData.value.length || !pk) {
message.error('Include at least 1 sample record in table to generate')
generatingPreview.value = false
return
}
previewFieldTitle.value = vModel.value?.title || 'temp_title'
+ try {
const res = await generateRows(
meta.value.id!,
{
title: previewFieldTitle.value,
prompt_raw: vModel.value.prompt_raw,
fk_integration_id: vModel.value.fk_integration_id,
uidt: UITypes.LongText,
},
[pk],
)
if (res?.length && res[0]?.[previewFieldTitle.value]) {
previewRow.value.row = {
...res[0],
[previewFieldTitle.value]: {
value: res[0]?.[previewFieldTitle.value],
},
}
isAlreadyGenerated.value = true
}
+ } catch (error) {
+ message.error(`Failed to generate preview: ${error.message}`)
+ }
generatingPreview.value = false
}
packages/nc-gui/components/smartsheet/grid/Table.vue (3)
8-8
: Good use of 'isAIPromptCol' function
Importing isAIPromptCol
enhances the clarity and maintainability of AI prompt column checks.
1061-1061
: Addition of default parameter enhances flexibility
Adding a default value for _scrollBehaviour
in scrollToCell
function improves its usability.
2140-2146
:
Fix incorrect self-closing <component>
tag
The <component>
tag is incorrectly self-closed. In Vue.js, <component>
should use a closing tag to encapsulate its content.
Apply this diff to fix the issue:
- <component
- :is="iconMap.maximize"
- v-if="expandForm"
- class="select-none nc-row-expand opacity-90 w-4 h-4"
- @click="expandAndLooseFocus(row, state)"
- />
+ <component
+ :is="iconMap.maximize"
+ v-if="expandForm"
+ class="select-none nc-row-expand opacity-90 w-4 h-4"
+ @click="expandAndLooseFocus(row, state)"
+ ></component>
Likely invalid or redundant comment.
packages/nc-gui/components/cell/AI.vue (1)
131-135
: Fix the tooltip disabled logic
The tooltip's :disabled
condition is incorrect. The tooltip should be disabled when the button is enabled (i.e., not disabled).
Apply this diff to fix the logic:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
packages/nc-gui/components/ai/PromptWithFields.vue (1)
159-174
: Add tooltip cleanup on component unmount.
The tooltips are properly initialized but need cleanup to prevent memory leaks.
packages/nc-gui/components/ai/Settings.vue (3)
10-15
: LGTM: Props definition is clear and well-structured
The new isEditColumn
prop with its default value is properly defined.
215-221
: LGTM: Improved select styling
The scoped style addition improves visual consistency with rounded corners.
84-86
: Verify model initialization logic
The condition for model initialization has been modified. Ensure this doesn't break existing functionality.
✅ Verification successful
Model initialization logic is safe
After reviewing the code, the modified condition is actually an improvement in the initialization logic. The onIntegrationChange
is called only when either:
- No model is selected (
!vModel.value
), or - No available models are loaded (
!availableModels.value.length
)
This ensures models are properly loaded when needed while avoiding unnecessary API calls when a model is already selected and models are available. The logic maintains proper initialization while being more efficient.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for any existing usages of this component to verify the initialization pattern
rg -A 5 "Settings\.vue|<Settings" --type vue
Length of output: 74
Script:
#!/bin/bash
# Search for the file and check its content
cat "packages/nc-gui/components/ai/Settings.vue"
Length of output: 7716
Script:
#!/bin/bash
# Search for any imports or usage of this component
rg -l "Settings" --type-add 'vue:*.vue' --type vue
Length of output: 2223
Script:
#!/bin/bash
# Look for any references to onIntegrationChange
rg "onIntegrationChange" --type-add 'vue:*.vue' --type vue
Length of output: 403
packages/nc-gui/composables/useNocoAi.ts (2)
20-23
: LGTM: Type refinement and computed property
The change from IntegrationType[]
to Partial<IntegrationType>[]
allows for more flexible integration definitions, and the computed property provides a clean way to check availability.
24-28
:
Fix computed property access in isAiIntegrationAvailableInList
The function has a bug where it's using the computed property name instead of its value.
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
packages/nc-gui/utils/columnUtils.ts (1)
289-296
: Verify AI integration validation logic
The AI integration validation for buttons needs thorough testing, especially the edge cases with isReadOnly
.
✅ Verification successful
Based on the gathered information, I can now provide a final response:
AI integration validation logic is correctly implemented
The validation logic for AI button type is properly implemented with appropriate checks:
- The
ncIsArrayIncludes
helper correctly validates if the integration ID exists in available integrations - The
isReadOnly
flag is properly handled to bypass validation in read-only mode - The validation is used consistently across the codebase, particularly in the Button component
- Error handling includes appropriate tooltip messaging for missing integrations
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usages of this validation in button components
rg -A 5 "isColumnInvalid.*Button.*Ai" --type vue --type ts
Length of output: 88
Script:
#!/bin/bash
# Search for usages of isColumnInvalid with Ai type
rg -A 5 "isColumnInvalid.*Ai"
# Search for AI button validation related code
rg -A 5 "ButtonActionsType.Ai"
# Look for AI integration validation patterns
rg -A 5 "fk_integration_id.*aiIntegrations"
Length of output: 12100
Script:
#!/bin/bash
# Search for the implementation of ncIsArrayIncludes function
rg -A 5 "ncIsArrayIncludes.*="
# Look for the validation usage in Button component
ast-grep --pattern 'const isFieldAiIntegrationAvailable = computed(() => { $$$ })'
Length of output: 545
packages/nc-gui/composables/useIntegrationsStore.ts (5)
93-94
: LGTM!
Clean addition of the aiIntegrations import for AI integration management.
191-201
: LGTM!
Good implementation of event tracking and AI integration cleanup on deletion.
300-309
: LGTM!
Clean implementation of AI integration creation with all required properties.
229-238
: 🛠️ Refactor suggestion
Avoid mutating objects within map—return new objects instead
Mutating objects within the map
function can lead to unexpected behavior in reactive systems. Instead, return new objects to ensure reactivity is preserved.
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.title = integration.title
- return i
+ return { ...i, title: integration.title }
}
return i
})
}
Likely invalid or redundant comment.
258-269
: 🛠️ Refactor suggestion
Avoid mutating objects within map—return new objects instead
Similar to the previous comment, mutate objects immutably within the map
function to maintain reactivity.
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.is_default = true
+ return { ...i, is_default: true }
} else {
- i.is_default = false
+ return { ...i, is_default: false }
}
- return i
})
}
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (3)
186-196
: LGTM!
Clean implementation of the UI filter logic for AI prompt and button types.
600-602
: LGTM!
Clean implementation of the AI prompt type check.
1137-1141
: LGTM!
Good implementation of the LongText options component with navigation handling.
packages/nocodb/src/db/conditionV2.ts (1)
596-612
: LGTM: Database-specific JSON value extraction for AI prompt columns
The implementation correctly handles JSON value extraction for AI prompt columns across different database types:
- PostgreSQL: Uses
jsonb->>'value'
- MySQL: Uses
JSON_UNQUOTE(JSON_EXTRACT())
- SQLite: Uses
json_extract()
- MSSQL: Uses
JSON_VALUE()
packages/nocodb/src/models/Column.ts (6)
452-469
: LGTM: AI column option insertion
The implementation correctly handles AI column option insertion when the column is marked as an AI prompt column via metadata.
573-577
: LGTM: AI column option retrieval
The implementation correctly retrieves AI column options when the column is marked as an AI prompt column.
Line range hint 1244-1280
: LGTM: Button column workspace handling
The implementation correctly handles workspace and integration IDs for button columns when importing across different workspaces.
Line range hint 1281-1296
: LGTM: AI prompt column workspace handling
The implementation correctly handles workspace and integration IDs for AI prompt columns when importing across different workspaces.
1353-1369
: LGTM: AI column option deletion
The implementation correctly handles cleanup of AI column options during column updates.
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect. It should check for the actual column ID to detect whether the prompt includes the deleted column ID.
Apply this diff to fix the placeholder matching:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (2)
182-182
: LGTM: AI prompt column filtering
The implementation correctly filters out AI prompt columns during import to handle them separately.
1113-1114
: LGTM: AI prompt column handling in referenced columns
The implementation correctly includes AI prompt columns in the referenced column set for proper handling during import.
packages/nocodb/src/db/BaseModelSqlv2.ts (1)
5721-5723
:
Add await to Promise.all for proper async handling
The current code does not properly await the async prepareNocoData
calls inside map()
, which could lead to unhandled promise rejections.
Apply this diff to fix the async handling:
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
packages/nocodb/src/services/columns.service.ts (2)
1562-1629
: LGTM: Comprehensive AI prompt column handling in columnUpdate
The implementation properly:
- Detects changes in AI prompt status
- Converts data between AI record types
- Handles prompt substitution with proper error handling
2266-2288
: LGTM: Proper AI prompt handling in columnAdd
The implementation correctly:
- Validates and processes AI prompts
- Substitutes column aliases with IDs
- Provides clear error messages for missing fields
insertObj, | ||
); | ||
|
||
return this.read(context, longTextColumn.fk_column_id, ncMeta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return this.read(context, longTextColumn.fk_column_id, ncMeta); | |
return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta); |
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
await NocoCache.set(`${CacheScope.COL_LONG_TEXT}:${columnId}`, column); | ||
} | ||
|
||
return column ? this.castType(column) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return column ? this.castType(column) : null; | |
return column ? LongTextColumn.castType(column) : null; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nc-gui/utils/columnUtils.ts
Outdated
case UITypes.LongText: { | ||
if (parseProp(col.meta)[LongTextAiMetaProp]) { | ||
const colOptions = col.colOptions as ButtonType | ||
|
||
result.isInvalid = | ||
!colOptions.fk_integration_id || | ||
(isReadOnly | ||
? false | ||
: !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')) | ||
|
||
result.tooltip = 'title.aiIntegrationMissing' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider DRYing up duplicate AI validation logic
The AI integration validation logic is duplicated between Button and LongText types. Consider using the suggested isAiColumnInvalid
helper function.
5616a30
to
68ce927
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (12)
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue (1)
42-42
: LGTM: Good separation of concernsMoving the integration loading logic to the store improves code organization and maintainability.
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
: Consider reducing the copy feedback durationWhile adding a delay improves the UX by showing the copy confirmation, 5000ms (5 seconds) might be too long. Consider reducing it to 2000-3000ms for a more responsive feel, which is the standard duration for such feedback.
- await ncDelay(5000) + await ncDelay(2000)packages/nc-gui/components/cell/TextArea.vue (1)
Line range hint
421-470
: Consider extracting AI status componentThe AI status section contains complex conditional rendering and repeated user display logic. Consider extracting this into a separate component for better maintainability.
// AIStatusDisplay.vue <template> <div class="flex items-center gap-2 px-3 py-0.5 !text-small leading-[18px]"> <span class="text-nc-content-purple-light truncate">Generated by AI</span> <slot name="edit-status" /> <slot name="regenerate-button" /> </div> </template>packages/nc-gui/composables/useIntegrationsStore.ts (3)
301-309
: Consider extracting AI integration object creation to a helper functionThe AI integration object creation could be made more maintainable by extracting it to a helper function.
Consider refactoring as follows:
+ const createAiIntegrationObject = (integration: IntegrationType) => ({ + id: integration.id, + title: integration.title, + is_default: integration.is_default, + type: integration.type, + sub_type: integration.sub_type, + }) if (response.type === IntegrationsType.Ai) { - aiIntegrations.value.push({ - id: response.id, - title: response.title, - is_default: response.is_default, - type: response.type, - sub_type: response.sub_type, - }) + aiIntegrations.value.push(createAiIntegrationObject(response)) }
191-192
: Consider enriching event tracking with additional contextThe event tracking is well-placed but could provide more valuable insights by including additional properties.
Consider adding relevant properties to the events, such as:
- Integration type
- Integration sub-type
- Operation status (success/failure)
Example:
- $e('a:integration:delete') + $e('a:integration:delete', { + type: integration.type, + subType: integration.sub_type, + })Also applies to: 224-225, 253-254, 287-291
Line range hint
447-496
: Add error handling and improve type safetyThe
loadDynamicIntegrations
function could benefit from several improvements:
- Missing error handling
- Type assertion could be replaced with proper typing
- Complex icon handling logic could be extracted
Consider refactoring as follows:
+ interface DynamicIntegration { + type: IntegrationsType + subType: string + meta: { + title?: string + icon?: string + description?: string + order?: number + } + } + const resolveIntegrationIcon = (icon?: string) => { + if (!icon) return iconMap.puzzle + if (icon in iconMap) return iconMap[icon as keyof typeof iconMap] + if (isValidURL(icon)) { + return h('img', { + src: icon, + alt: di.meta.title || di.subType, + }) + } + return iconMap.puzzle + } const loadDynamicIntegrations = async () => { if (integrationsInitialized.value) return integrationsInitialized.value = true + try { - const dynamicIntegrations = (await $api.integrations.list()) as { - type: IntegrationsType - subType: string - meta: { - title?: string - icon?: string - description?: string - order?: number - } - }[] + const dynamicIntegrations = await $api.integrations.list() as DynamicIntegration[] dynamicIntegrations.sort((a, b) => (a.meta.order ?? Infinity) - (b.meta.order ?? Infinity)) for (const di of dynamicIntegrations) { - let icon: FunctionalComponent<SVGAttributes, {}, any, {}> | VNode - if (di.meta.icon) { - if (di.meta.icon in iconMap) { - icon = iconMap[di.meta.icon as keyof typeof iconMap] - } else { - if (isValidURL(di.meta.icon)) { - icon = h('img', { - src: di.meta.icon, - alt: di.meta.title || di.subType, - }) - } - } - } else { - icon = iconMap.puzzle - } + const icon = resolveIntegrationIcon(di.meta.icon) const integration: IntegrationItemType = { title: di.meta.title || di.subType, subType: di.subType, icon, type: di.type, isAvailable: true, dynamic: true, } allIntegrations.push(integration) integrationsRefreshKey.value++ } + } catch (e) { + console.error('Failed to load dynamic integrations:', e) + throw e + } }packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
130-144
: Simplify the submit button state logicThe current implementation can be made more readable by reducing nesting and simplifying the conditions.
Consider this refactor:
const handleDisableSubmitBtn = () => { - if (!isEnabledGenerateText.value) { - if (disableSubmitBtn.value) { - disableSubmitBtn.value = false - } - return - } - - if (isPreviewEnabled.value) { - disableSubmitBtn.value = false - } else { - disableSubmitBtn.value = true - } + disableSubmitBtn.value = isEnabledGenerateText.value && !isPreviewEnabled.value }
156-308
: Enhance accessibility for interactive elementsSeveral interactive elements could benefit from ARIA attributes and keyboard navigation improvements.
Consider these improvements:
- Add aria-label to the settings button:
- <NcButton size="xs" theme="ai" class="!px-1" type="text"> + <NcButton size="xs" theme="ai" class="!px-1" type="text" aria-label="AI Settings">
- Add aria-live region for the preview section:
- <div v-if="aiIntegrationAvailable && isEnabledGenerateText" class="nc-ai-options-preview overflow-hidden"> + <div v-if="aiIntegrationAvailable && isEnabledGenerateText" class="nc-ai-options-preview overflow-hidden" aria-live="polite">
- Add role and aria-label to the preview container:
- <div v-if="previewRow.row?.[previewFieldTitle]?.value"> + <div v-if="previewRow.row?.[previewFieldTitle]?.value" role="region" aria-label="AI Generated Preview">packages/nc-gui/components/cell/AI.vue (1)
111-112
: Consider using object spread operator directly.The current implementation creates an unnecessary temporary object.
-vModel.value = { ...vModel.value } +vModel.value = { ...(vModel.value || {}) }packages/nc-gui/components/ai/PromptWithFields.vue (1)
63-68
: Consider optimizing the filter condition.The current implementation might be inefficient for large lists. Consider using early returns or memoization.
return ( props.options?.filter( - (o) => - o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), + (o) => { + if (!o.title) return false + const lowerTitle = o.title.toLowerCase() + const lowerQuery = query.toLowerCase() + return lowerTitle.includes(lowerQuery) || `${lowerTitle}}` === lowerQuery + } ) ?? [] )packages/nc-gui/components/ai/Settings.vue (1)
Line range hint
70-86
: Consider adding error handling for initialization.The initialization logic lacks error handling for edge cases.
onMounted(async () => { + try { if (isEditColumn.value) { return } if (!vFkIntegrationId.value) { if (aiIntegrations.value.length > 0 && aiIntegrations.value[0].id) { vFkIntegrationId.value = aiIntegrations.value[0].id nextTick(() => { onIntegrationChange() }) } } else { lastIntegrationId.value = vFkIntegrationId.value if (!vModel.value || !availableModels.value.length) { onIntegrationChange() } } + } catch (error) { + console.error('Error during Settings initialization:', error) + // Consider showing a user-friendly error message + } })packages/nocodb/src/services/columns.service.ts (1)
1578-1645
: Robust AI prompt column update handlingThe implementation:
- Properly handles conversion between AI and non-AI column types
- Correctly processes column references in prompts
- Maintains data integrity during type changes
Consider extracting the prompt processing logic into a separate utility function to improve reusability and maintainability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (69)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (44)
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/models/index.ts
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nc-gui/utils/commonUtils.ts
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/composables/useData.ts
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nocodb/src/models/Integration.ts
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nc-gui/components/general/Loader.vue
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nc-gui/components/smartsheet/Kanban.vue
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nocodb/src/models/Column.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
- packages/nc-gui/components/smartsheet/details/Fields.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (44)
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue (1)
216-217
:
Add error handling for dynamic integration loading
The loadDynamicIntegrations
call should include error handling to gracefully handle loading failures and provide user feedback.
onMounted(() => {
- loadDynamicIntegrations()
+ try {
+ await loadDynamicIntegrations()
+ } catch (error) {
+ // Handle error appropriately (e.g., show error notification)
+ console.error('Failed to load integrations:', error)
+ }
if (!integrationsCategoryFilter.value.length) {
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
Line range hint 1-458
: Implementation looks solid!
The code demonstrates good practices with:
- Comprehensive error handling and user feedback
- Proper type safety and permission checks
- Well-documented solutions for known issues
- Clear separation of concerns
packages/nc-gui/components/cell/TextArea.vue (5)
104-111
: LGTM: Well-structured computed property
The isAiGenerating
computed property is well-structured with proper null checks and logical conditions.
Line range hint 408-421
: LGTM: Clear and informative AI warning message
The warning message for stale AI-generated content is well-structured and provides clear information to users.
524-527
: Remove invalid Tailwind class
The class animate-infinite
is not a valid Tailwind CSS utility class. The animate-spin
class already provides an infinite animation.
-:class="{ 'animate-infinite animate-spin': isAiGenerating }"
+:class="{ 'animate-spin': isAiGenerating }"
Line range hint 712-723
: LGTM: Well-structured CSS for expanded form
The CSS for handling the expanded form state is well-organized and uses appropriate selectors.
100-103
:
Replace non-null assertions with safe property access
Using non-null assertions (!
) can lead to runtime errors if meta.value
or columns
are undefined.
-return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
+return currentRow.value?.row && meta.value?.columns
+ ? extractPkFromRow(currentRow.value.row, meta.value.columns)
+ : undefined
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (4)
1-25
: LGTM! Component setup follows Vue 3 best practices
The component properly defines its props, emits, and dependencies. The availableFields
computed property correctly filters columns based on valid criteria.
314-340
: LGTM! Well-organized styles
The styles follow Vue scoped CSS best practices and properly use the deep selector for targeting child components.
48-58
:
Initialize vModel.value.meta
before assignment
The setter of isEnabledGenerateText
could cause runtime errors if vModel.value.meta
is undefined.
Apply this fix:
set: (value: boolean) => {
+ if (!vModel.value.meta) vModel.value.meta = {}
vModel.value.meta[LongTextAiMetaProp] = value
vModel.value.prompt_raw = ''
previewRow.value.row = {}
isAlreadyGenerated.value = false
},
66-104
:
Add error handling for the generateRows
asynchronous call
The generate
function needs error handling to gracefully handle failures.
Apply this fix:
const generate = async () => {
generatingPreview.value = true
await loadViewData()
const pk = formattedData.value.length ? extractPkFromRow(unref(formattedData.value[0].row), meta.value?.columns || []) : ''
if (!formattedData.value.length || !pk) {
message.error('Include at least 1 sample record in table to generate')
generatingPreview.value = false
return
}
previewFieldTitle.value = vModel.value?.title || 'temp_title'
+ try {
const res = await generateRows(
meta.value.id!,
{
title: previewFieldTitle.value,
prompt_raw: vModel.value.prompt_raw,
fk_integration_id: vModel.value.fk_integration_id,
uidt: UITypes.LongText,
},
[pk],
)
if (res?.length && res[0]?.[previewFieldTitle.value]) {
previewRow.value.row = {
...res[0],
[previewFieldTitle.value]: {
value: res[0]?.[previewFieldTi
10000
tle.value],
},
}
isAlreadyGenerated.value = true
}
+ } catch (error) {
+ message.error(`Failed to generate preview: ${error.message}`)
+ }
generatingPreview.value = false
}
packages/nc-gui/components/cell/AI.vue (4)
16-19
: LGTM: Role and public status checks added.
The addition of isUIAllowed
and isPublic
injection provides proper access control for AI features.
118-120
: LGTM: Consolidated button disable logic.
The isDisabledAiButton
computed property effectively consolidates multiple conditions into a single, readable check.
131-139
: Fix tooltip disabled logic.
The tooltip's disabled condition seems incorrect. It should be disabled when the button is enabled, not the other way around.
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
160-171
: LGTM: Enhanced button and focus styles.
The updated styles improve visual feedback and accessibility through better focus states and consistent styling.
Also applies to: 184-187
packages/nc-gui/components/ai/PromptWithFields.vue (2)
Line range hint 19-31
: LGTM: Read-only mode implementation.
The readOnly
prop is properly implemented with a default value and correctly referenced in the component setup.
Also applies to: 44-46
159-174
: Add tooltip cleanup on component unmount.
The tooltips are properly initialized, but they should be cleaned up when the component is unmounted to prevent memory leaks.
+ 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
- tippy(el, {
+ const instance = tippy(el, {
content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
placement: 'top',
allowHTML: true,
arrow: true,
animation: 'fade',
duration: 0,
maxWidth: '200px',
})
+ tooltipInstances.push(instance)
})
}
+ onBeforeUnmount(() => {
+ tooltipInstances.forEach(instance => instance.destroy())
+ tooltipInstances.length = 0
+ })
packages/nc-gui/components/ai/Settings.vue (1)
10-15
: LGTM: Edit column mode properly implemented.
The isEditColumn
prop is correctly added with appropriate default value and type safety.
Also applies to: 24-25
packages/nc-gui/composables/useNocoAi.ts (3)
20-20
: LGTM: Type change improves flexibility.
The change from IntegrationType[]
to Partial<IntegrationType>[]
is a good improvement as it allows for partial integration objects.
22-22
: LGTM: Clear and concise computed property.
The computed property provides a clean way to check for AI integration availability.
24-28
:
Fix the condition in isAiIntegrationAvailableInList
The function has a bug where it's using the computed property name instead of its value.
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (3)
369-369
: Verify prop name in AiSettings component
The prop :is-edit-column="isEdit"
might not match the expected prop name in the AiSettings
component.
#!/bin/bash
# Description: Check prop name in AiSettings component
# Search for prop definition in AiSettings component
ast-grep --pattern 'defineProps<{
$$$
isEditColumn?: $_
$$$
}>()'
471-475
: LGTM: Enhanced accessibility with tooltips.
The addition of tooltips for truncated text improves the user experience.
687-688
: LGTM: Well-structured button state management.
The implementation properly handles button states with:
- Clear conditional classes
- Consistent color scheme
- Comprehensive state conditions
Also applies to: 698-714, 724-730
packages/nocodb/src/models/AIColumn.ts (2)
26-26
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing because this
refers to the class itself in a static context. It's clearer and more explicit to reference the class name directly.
Apply this diff to fix the issue:
- return this._insert(
+ return AIColumn._insert(
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
40-40
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing because this
refers to the class itself. It's better to use the class name directly for clarity.
Apply this diff to fix the issue:
- return this._update(
+ return AIColumn._update(
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: Avoid using 'this' in static methods; use the class name instead
In static methods, using this
can be confusing. It's clearer to reference the class name directly to indicate that the method belongs to the class, not an instance.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
85-85
: Avoid using 'this' in static methods; use the class name instead
Using this
in a static context can lead to confusion. It's best practice to use the class name directly for clarity.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
135-149
: Avoid string concatenation in SQL queries to prevent SQL injection
In the MSSQL query, string concatenation is used to build the JSON string. This approach can lead to SQL injection vulnerabilities. It's important to use parameterized queries to ensure security.
Consider refactoring the query to use parameter bindings or MSSQL's JSON functions without concatenation. Here's a suggested fix:
- `UPDATE ??
- SET ?? = JSON_QUERY('{"value":' + ?? + ',"lastModifiedBy":' + ? + ',"lastModifiedTime":' + ? + ',"isStale":' + ? + '}')
- WHERE ?? is not null;`,
+ `UPDATE ??
+ SET ?? = (SELECT JSON_QUERY(T.*) FROM (SELECT ?? AS value, ? AS lastModifiedBy, ? AS lastModifiedTime, ? AS isStale) AS T)
+ WHERE ?? IS NOT NULL;`,
Ensure that all variables are properly parameterized to prevent injection.
packages/nocodb/src/db/sortV2.ts (1)
203-221
: Remove duplicate condition check
The condition isAIPromptCol(column)
is checked twice unnecessarily, leading to redundant code.
Apply this diff to remove the duplicate condition:
case UITypes.LongText: {
if (isAIPromptCol(column)) {
let col;
- if (isAIPromptCol(column)) {
if (knex.clientType() === 'pg') {
col = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [
column.column_name,
]);
} else if (knex.clientType().startsWith('mysql')) {
col = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [
column.column_name,
]);
} else if (knex.clientType() === 'sqlite3') {
col = knex.raw(`json_extract(??, '$.value')`, [
column.column_name,
]);
} else if (knex.clientType() === 'mssql') {
col = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]);
}
- }
qb.orderBy(col, sort.direction || 'asc', nulls);
} else {
qb.orderBy(
sanitize(column.column_name),
sort.direction || 'asc',
nulls,
);
}
break;
}
packages/nc-gui/utils/columnUtils.ts (1)
269-309
: Refactor to eliminate duplicate AI validation logic
The AI integration validation logic is duplicated in the Button
and LongText
cases within the isColumnInvalid
function. Refactoring this code will improve maintainability and reduce potential errors.
Consider extracting the common logic into a helper function, such as isAiColumnInvalid
, and reuse it in both cases:
function isAiColumnInvalid(
colOptions: ButtonType,
aiIntegrations: Partial<IntegrationType>[],
isReadOnly: boolean,
): { isInvalid: boolean; tooltip: string } {
const isInvalid =
!colOptions.fk_integration_id ||
(!isReadOnly &&
!!colOptions.fk_integration_id &&
!ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'));
const tooltip = 'title.aiIntegrationMissing';
return { isInvalid, tooltip };
}
Then update the Button
and LongText
cases:
} else if (colOptions.type === ButtonActionsType.Ai) {
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ Object.assign(
+ result,
+ isAiColumnInvalid(colOptions, aiIntegrations, isReadOnly),
+ );
}
And similarly for the LongText
case.
packages/nc-gui/components/smartsheet/expanded-form/index.vue (1)
330-333
: LGTM! Good user feedback implementation.
The addition of a 5-second delay after copying the URL provides clear visual feedback to users, improving the user experience.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (4)
186-196
: LGTM! Good validation for AI prompt columns.
The addition of hideAiPromptOnEdit
condition correctly prevents AI prompt columns from being shown in edit mode, maintaining data integrity.
Line range hint 249-267
: LGTM! Clean implementation of AI prompt type handling.
The onSelectType
function has been enhanced to handle AI prompt columns by setting appropriate defaults and metadata.
600-602
: LGTM! Clear and focused implementation.
The isAiPromptSelectOption
function provides a clean way to identify AI prompt columns using the SDK utility.
1137-1141
: LGTM! Good integration of AI prompt options.
The SmartsheetColumnLongTextOptions component is properly configured with AI prompt options and navigation functionality.
packages/nocodb/src/db/conditionV2.ts (1)
596-612
: LGTM! Robust database-agnostic implementation for AI prompt columns.
The implementation correctly handles JSON value extraction for AI prompt columns across different database types:
- PostgreSQL using
jsonb->>'value'
- MySQL using
JSON_EXTRACT
andJSON_UNQUOTE
- SQLite using
json_extract
- MSSQL using
JSON_VALUE
This ensures consistent behavior across different database backends.
packages/nc-gui/components/smartsheet/grid/Table.vue (2)
855-856
: Improved AI column type detection logic
The code now properly handles both AI prompt columns and AI button types, making the implementation more robust and maintainable.
2140-2146
: Enhanced expand button UI with proper styling
The expand button now has:
- Proper border and hover states
- Consistent icon sizing
- Better accessibility with semantic markup
packages/nocodb/src/services/columns.service.ts (2)
71-74
: Added data conversion utilities for AI columns
Clean import of utility functions for converting between AI record types and values.
2282-2304
: Comprehensive AI prompt handling during column creation
The code properly:
- Validates prompt format
- Processes column references
- Ensures data consistency
Let's verify the column reference handling:
✅ Verification successful
Based on the search results, I can now verify the AI prompt handling. The code shows:
- The prompt handling is implemented in multiple places with proper validation
- The column reference replacement is consistently implemented using regex pattern
/{(.*?)}/g
- There's proper error handling with
NcError.badRequest
when referenced fields are not found - The prompt_raw and processed prompt are stored separately in the database schema
- The implementation is used consistently across the codebase
Let me verify one final aspect - the error handling implementation:
Based on the search results, I can now provide a complete verification of the AI prompt handling implementation. The code shows:
Comprehensive and secure AI prompt handling verified
The implementation is robust and secure because:
- It properly validates column references using regex pattern
/{(.*?)}/g
- It includes proper error handling with
NcError.badRequest
when referenced fields are not found - The prompt handling is consistently implemented across the codebase
- It maintains separation between raw prompt (
prompt_raw
) and processed prompt for security - The column reference substitution is done after validating column existence
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for potential column reference issues in prompts
# Look for column references in prompts that might be invalid
ast-grep --pattern 'prompt_raw.replace(/{(.*?)}/g, $_)'
Length of output: 57
Script:
#!/bin/bash
# Let's try a different approach to verify AI prompt handling
# Search for AI prompt column handling
rg -A 10 "isAIPromptCol"
# Search for prompt validation and processing
rg -A 5 "prompt_raw"
# Look for any error handling related to prompts
rg "NcError.badRequest.*prompt"
Length of output: 42415
Script:
#!/bin/bash
# Check the error handling implementation for AI prompts
ast-grep --pattern 'NcError.badRequest(`Field ${$_} not found`)'
# Look for any other prompt-related error handling
rg "throw.*prompt" -A 2
Length of output: 65629
packages/nocodb/src/db/BaseModelSqlv2.ts (3)
5721-5723
: LGTM: Proper async handling with Promise.all
The change correctly handles the async nature of prepareNocoData by using Promise.all with map.
10071-10075
:
Correct the type of isStale
to boolean
The isStale
property is used as a boolean in the code. Currently, it's typed as string?
, which can lead to type mismatches. It should be typed as boolean?
to reflect its usage.
Apply this diff to fix the type:
const obj: {
value?: string;
lastModifiedBy?: string;
lastModifiedTime?: string;
- isStale?: string;
+ isStale?: boolean;
} = {};
Likely invalid or redundant comment.
8635-8653
: 🛠️ Refactor suggestion
Improve error handling for JSON parsing
The JSON parsing logic has minimal error handling with empty catch blocks that could hide important errors. Additionally, there's no validation of the parsed data structure.
Apply this diff to improve error handling:
if (d) {
for (const col of jsonColumns) {
if (d[col.id] && typeof d[col.id] === 'string') {
try {
d[col.id] = JSON.parse(d[col.id]);
+ // Validate expected data structure
+ if (typeof d[col.id] !== 'object') {
+ throw new Error('Invalid JSON structure');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON for column ${col.id}: ${e.message}`);
+ d[col.id] = null; // Set to null on parse error
+ }
}
if (d[col.id]?.length) {
for (let i = 0; i < d[col.id].length; i++) {
if (typeof d[col.id][i] === 'string') {
try {
d[col.id][i] = JSON.parse(d[col.id][i]);
+ // Validate array element structure
+ if (typeof d[col.id][i] !== 'object') {
+ throw new Error('Invalid JSON array element');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON array element: ${e.message}`);
+ d[col.id][i] = null;
+ }
}
}
}
}
}
Likely invalid or redundant comment.
const meta = inject(MetaInj, ref()) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using a default value for meta injection
The injected meta
dependency uses ref()
as a fallback, which could lead to runtime errors when accessing properties. Consider providing a more specific default value.
-const meta = inject(MetaInj, ref())
+const meta = inject(MetaInj, ref({ columns: [] }))
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const meta = inject(MetaInj, ref()) | |
const meta = inject(MetaInj, ref({ columns: [] })) | |
68ce927
to
f412ec4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Outside diff range and nitpick comments (14)
packages/nc-gui/components/smartsheet/expanded-form/index.vue (1)
330-333
: Consider adding error handling to the copy operation.The implementation of the URL copy feedback is good, providing clear user feedback for 5 seconds. However, the copy operation could fail.
Consider wrapping the copy operation in a try-catch block:
const copyRecordUrl = async () => { - await copy( - encodeURI( - `${dashboardUrl?.value}#/${route.params.typeOrId}/${route.params.baseId}/${meta.value?.id}${ - props.view ? `/${props.view.title}` : '' - }?rowId=${primaryKey.value}`, - ), - ) - - isRecordLinkCopied.value = true - - await ncDelay(5000) - - isRecordLinkCopied.value = false + try { + await copy( + encodeURI( + `${dashboardUrl?.value}#/${route.params.typeOrId}/${route.params.baseId}/${meta.value?.id}${ + props.view ? `/${props.view.title}` : '' + }?rowId=${primaryKey.value}`, + ), + ) + isRecordLinkCopied.value = true + await ncDelay(5000) + isRecordLinkCopied.value = false + } catch (error) { + message.error(t('msg.error.recordURLCopyFailed')) + } }packages/nc-gui/utils/columnUtils.ts (1)
269-318
: LGTM with suggestions: Function changes are well-structuredThe changes to
isColumnInvalid
improve error handling by including tooltips and properly handling AI integrations. However, consider these improvements:
- Extract the AI validation logic into a helper function to reduce duplication
- Remove the redundant Formula type check
Consider extracting the AI validation into a helper function:
const isAiColumnInvalid = ( colOptions: ButtonType, isReadOnly: boolean, aiIntegrations: Partial<IntegrationType>[] ): { isInvalid: boolean; tooltip: string } => ({ isInvalid: !colOptions.fk_integration_id || (isReadOnly ? false : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')), tooltip: 'title.aiIntegrationMissing' })Then use it in both Button and LongText cases:
case UITypes.Button: { const colOptions = col.colOptions as ButtonType if (colOptions.type === ButtonActionsType.Webhook) { result.isInvalid = !colOptions.fk_webhook_id } else if (colOptions.type === ButtonActionsType.Url) { result.isInvalid = !!colOptions.error } else if (colOptions.type === ButtonActionsType.Ai) { return isAiColumnInvalid(colOptions, isReadOnly, aiIntegrations) } break } case UITypes.LongText: { if (parseProp(col.meta)[LongTextAiMetaProp]) { return isAiColumnInvalid(col.colOptions as ButtonType, isReadOnly, aiIntegrations) } }packages/nc-gui/components/cell/TextArea.vue (1)
Line range hint
491-543
: Consider extracting complex class logic into computed propertiesThe class binding logic is quite complex and could be more maintainable if extracted into computed properties.
Example refactor:
+ const expandButtonClasses = computed(() => ({ + 'right-1': isForm, + 'right-0': !isForm, + 'top-0': isGrid && !isExpandedFormOpen && !isForm && !(!rowHeight || rowHeight === 1), + 'top-1': !(isGrid && !isExpandedFormOpen && !isForm) + })) + + const expandButtonStyle = computed(() => + isGrid && !isExpandedFormOpen && !isForm && (!rowHeight || rowHeight === 1) + ? { top: '50%', transform: 'translateY(-50%)' } + : undefined + ) <div class="!absolute !hidden nc-text-area-expand-btn group-hover:block z-3 flex items-center gap-1" - :class="{ - 'right-1': isForm, - 'right-0': !isForm, - 'top-0': isGrid && !isExpandedFormOpen && !isForm && !(!rowHeight || rowHeight === 1), - 'top-1': !(isGrid && !isExpandedFormOpen && !isForm) - }" - :style=" - isGrid && !isExpandedFormOpen && !isForm && (!rowHeight || rowHeight === 1) - ? { top: '50%', transform: 'translateY(-50%)' } - : undefined - " + :class="expandButtonClasses" + :style="expandButtonStyle" >packages/nc-gui/components/smartsheet/Kanban.vue (3)
Line range hint
1-1200
: Consider splitting the component for better maintainability.The Kanban component currently handles multiple responsibilities including drag-and-drop, CRUD operations, and UI state management. Consider breaking it down into smaller, more focused components:
- KanbanStack: Handle stack-level operations
- KanbanCard: Handle card-level operations
- KanbanDragDrop: Handle drag-and-drop logic
This separation would improve:
- Code maintainability
- Testing capabilities
- Reusability
- Performance through better component lifecycle management
Line range hint
89-103
: Optimize infinite scroll performance.The current infinite scroll implementation could be improved:
- Consider using virtual scrolling for large lists to reduce DOM elements.
- The debounced scroll handler could be optimized by:
- Using
IntersectionObserver
instead of scroll events- Adding early return conditions
- Implementing proper cleanup in component unmount
Example implementation using
IntersectionObserver
:-const kanbanListScrollHandler = useDebounceFn(async (e: any) => { - if (!e.target) return - - if (e.target.scrollTop + e.target.clientHeight + INFINITY_SCROLL_THRESHOLD >= e.target.scrollHeight) { - const stackTitle = e.target.getAttribute('data-stack-title') +const setupInfiniteScroll = (element: HTMLElement) => { + const observer = new IntersectionObserver( + async (entries) => { + const entry = entries[0] + if (!entry.isIntersecting) return + + const stackTitle = element.getAttribute('data-stack-title') + if (!stackTitle) return + + const stack = formattedData.value.get(stackTitle) + if (!stack) return + + const pageSize = appInfo.value.defaultLimit || 25 + if (stack.length >= (countByStack.value.get(stackTitle) ?? 0)) return + + const page = Math.ceil(stack.length / pageSize) + await loadMoreKanbanData(stackTitle, { + offset: page * pageSize, + }) + }, + { threshold: 0.5 } + ) + + const sentinel = document.createElement('div') + element.appendChild(sentinel) + observer.observe(sentinel) + + return () => { + observer.disconnect() + sentinel.remove() + } +}
Line range hint
1-1200
: Enhance accessibility support.The component could benefit from improved accessibility:
Add ARIA attributes for drag-and-drop operations:
aria-grabbed
aria-dropeffect
aria-describedby
Improve keyboard navigation:
- Add keyboard shortcuts for common operations
- Implement focus management for drag-and-drop
- Add skip links for large stacks
Enhance screen reader support:
- Add live regions for dynamic updates
- Improve announcement of stack changes
Example implementation:
+const KEYBOARD_SHORTCUTS = { + MOVE_CARD: 'm', + EXPAND_CARD: 'e', + DELETE_CARD: 'd', +} + +const setupKeyboardNavigation = () => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === KEYBOARD_SHORTCUTS.MOVE_CARD && e.ctrlKey) { + // Implement card movement logic + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) +}packages/nc-gui/composables/useIntegrationsStore.ts (1)
Line range hint
447-496
: Add error handling and loading state managementThe
loadDynamicIntegrations
function needs improvements in several areas:
- Error handling for API call
- Loading state management
- Memory management for icon resources
const loadDynamicIntegrations = async () => { + isLoadingIntegrations.value = true if (integrationsInitialized.value) return integrationsInitialized.value = true try { const dynamicIntegrations = (await $api.integrations.list()) as { type: IntegrationsType subType: string meta: { title?: string icon?: string description?: string order?: number } }[] dynamicIntegrations.sort((a, b) => (a.meta.order ?? Infinity) - (b.meta.order ?? Infinity)) for (const di of dynamicIntegrations) { let icon: FunctionalComponent<SVGAttributes, {}, any, {}> | VNode | undefined if (di.meta.icon) { if (di.meta.icon in iconMap) { icon = iconMap[di.meta.icon as keyof typeof iconMap] } else if (isValidURL(di.meta.icon)) { icon = h('img', { src: di.meta.icon, alt: di.meta.title || di.subType, + loading: 'lazy', + onerror: 'this.src="fallback-icon.png"' }) } } else { icon = iconMap.puzzle } const integration: IntegrationItemType = { title: di.meta.title || di.subType, subType: di.subType, icon, type: di.type, isAvailable: true, dynamic: true, } allIntegrations.push(integration) } integrationsRefreshKey.value++ + } catch (e) { + await message.error(await extractSdkResponseErrorMsg(e)) + } finally { + isLoadingIntegrations.value = false } }packages/nc-gui/components/smartsheet/details/Fields.vue (2)
1040-1043
: Consider refactoring field management logic for better maintainabilityThe field management logic, particularly in
onFieldUpdate
andsaveChanges
, is complex and handles multiple responsibilities. Consider breaking these down into smaller, more focused methods.Some suggestions:
- Extract field validation logic into a separate method
- Split save operations by type (add/update/delete)
- Consider using a state management pattern for tracking field changes
Example refactor for the copy timeout:
+ const COPY_TIMEOUT_MS = 5000 + + async function resetCopyState() { + await delay(COPY_TIMEOUT_MS) + isFieldIdCopied.value = false + } + - await delay(5000) - isFieldIdCopied.value = false + await resetCopyState()Also applies to: 1041-1042, 1043-1043
1040-1043
: Enhance AI integration error handling and state managementThe AI integration could benefit from more robust error handling and centralized state management.
Consider:
- Creating an enum for AI error types
- Moving AI state management to a dedicated composable
- Adding retry logic for failed AI operations
- Implementing proper cleanup on component unmount
Example error handling enhancement:
enum AiErrorType { NO_INTEGRATION = 'no_integration', PREDICTION_FAILED = 'prediction_failed', RATE_LIMITED = 'rate_limited' } interface AiError { type: AiErrorType; message: string; retryable: boolean; }packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
Line range hint
94-94
: Use enum values instead of string literals for consistencyIn the condition
if (type === 'personal' && view.value.is_default)
, you are comparingtype
(which is of typeLockType
) to a string literal'personal'
. This might cause inconsistencies or bugs if theLockType
enum changes.Replace the string literal with the corresponding enum value:
- if (type === 'personal' && view.value.is_default) { + if (type === LockType.Personal && view.value.is_default) {This ensures type safety and consistency throughout the codebase.
packages/nocodb/src/services/columns.service.ts (1)
1601-1621
: Add validation for prompt template formatThe prompt template processing lacks validation for the template format. Consider adding checks for:
- Valid template syntax
- Maximum template length
- Restricted characters/patterns
let prompt = ''; /* Substitute column alias with id in prompt */ if (colBody.prompt_raw) { + // Validate prompt template format + const MAX_PROMPT_LENGTH = 1000; + if (colBody.prompt_raw.length > MAX_PROMPT_LENGTH) { + NcError.badRequest(`Prompt template exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`); + } + + // Check for valid template syntax + const templatePattern = /^[^{}]*(\{[^{}]+\}[^{}]*)*$/; + if (!templatePattern.test(colBody.prompt_raw)) { + NcError.badRequest('Invalid prompt template format'); + } await table.getColumns(context); prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => { const column = table.columns.find((c) => c.title === p1); if (!column) { NcError.badRequest(`Field '${p1}' not found`); } return `{${column.id}}`; }); } colBody.prompt = prompt;packages/nocodb/src/db/conditionV2.ts (1)
596-612
: Consider refactoring database-specific JSON handlingThe JSON value extraction logic for AI prompt columns is duplicated across different database types. Consider refactoring this into a reusable helper function to improve maintainability.
+ // Add at the top of the file + function getAIPromptValueExtractor(knex: Knex, columnName: string) { + if (knex.clientType() === 'pg') { + return knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [columnName]); + } else if (knex.clientType().startsWith('mysql')) { + return knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [columnName]); + } else if (knex.clientType() === 'sqlite3') { + return knex.raw(`json_extract(??, '$.value')`, [columnName]); + } else if (knex.clientType() === 'mssql') { + return knex.raw(`JSON_VALUE(??, '$.value')`, [columnName]); + } + return null; + } - if (isAIPromptCol(column)) { - if (knex.clientType() === 'pg') { - field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name]); - } else if (knex.clientType().startsWith('mysql')) { - field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name]); - } else if (knex.clientType() === 'sqlite3') { - field = knex.raw(`json_extract(??, '$.value')`, [column.column_name]); - } else if (knex.clientType() === 'mssql') { - field = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]); - } + if (isAIPromptCol(column)) { + field = getAIPromptValueExtractor(knex, column.column_name); }packages/nocodb/src/models/Column.ts (1)
Line range hint
1282-1286
: Extract workspace validation into a shared helperThe workspace ID validation logic is duplicated between button and AI prompt columns. Consider extracting this into a shared helper function.
+ function resetWorkspaceSpecificProps(base: Base, options: any) { + if (base.fk_workspace_id !== options.fk_workspace_id) { + options.fk_workspace_id = null; + options.fk_integration_id = null; + options.model = null; + } + } - if (base.fk_workspace_id !== colOptions.fk_workspace_id) { - colOptions.fk_workspace_id = null; - colOptions.fk_integration_id = null; - colOptions.model = null; - } + resetWorkspaceSpecificProps(base, colOptions);packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (1)
182-182
: Consider preserving AI prompt column metadata during importWhile filtering out AI prompt columns from the initial import is correct, consider preserving their metadata for reference or validation.
+ // Track filtered AI prompt columns for validation + const filteredAIPromptColumns = []; const reducedColumnSet = modelData.columns.filter( (a) => { const keep = !isVirtualCol(a) && a.uidt !== UITypes.ForeignKey && - !isAIPromptCol(a) && + !(isAIPromptCol(a) && (filteredAIPromptColumns.push(a), true)) && (param.importColumnIds ? param.importColumnIds.includes(getEntityIdentifier(a.id)) : true); return keep; } );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (69)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(1 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (46)
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nocodb/src/models/index.ts
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/components/nc/Button.vue
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/general/Loader.vue
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/utils/commonUtils.ts
- packages/nocodb/src/db/sortV2.ts
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nocodb/src/models/Integration.ts
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/ai/Settings.vue
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/components/smartsheet/grid/Table.vue
- packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (31)
packages/nc-gui/utils/columnUtils.ts (4)
2-3
: LGTM: New imports and constants are well-organized
The new imports and constants are properly structured and follow the existing naming conventions.
Also applies to: 15-18
27-32
: LGTM: New AIPrompt UI type is properly defined
The new AIPrompt UI type follows the existing pattern and includes appropriate flags.
315-317
: Remove redundant Formula type validation
The Formula type validation is duplicated. It exists both in the switch statement and after it.
289-309
: Extract duplicated AI validation logic
The AI integration validation logic is duplicated between Button and LongText types. Consider extracting it into a helper function:
packages/nc-gui/components/cell/TextArea.vue (6)
17-18
: Add type safety to meta injection
The injected meta
dependency uses ref()
as a fallback, which could lead to runtime errors when accessing properties.
100-103
: Add null checks to prevent runtime errors
The use of non-null assertions !
in meta.value!.columns!
may lead to runtime errors if meta.value
or columns
are undefined
.
Line range hint 408-466
: LGTM! Well-structured AI warning and regeneration UI
The implementation properly handles:
- Conditional rendering based on AI states
- Clear warning messages for stale data
- User-friendly regeneration controls
629-630
: Correct the usage of Tailwind CSS classes
The class animate-infinite
is not a valid Tailwind CSS utility.
Line range hint 637-648
: LGTM! Clear and well-structured warning message
The warning message implementation:
- Uses appropriate icons and styling
- Provides clear explanation of the stale state
- Maintains consistent design language
Line range hint 708-724
: LGTM! Well-structured styles
The CSS implementation:
- Properly scopes styles
- Uses appropriate selectors
- Maintains consistent naming conventions
packages/nc-gui/components/smartsheet/Kanban.vue (1)
1146-1146
: Icon change aligns with design system consistency.
The change from iconMap.expand
to iconMap.maximize
for the expand record action maintains consistency with similar icon usage in other components like Gallery.vue, attachment/index.vue, and ManyToMany.vue.
packages/nc-gui/composables/useIntegrationsStore.ts (2)
229-238
: Avoid mutating objects within map
—return new objects instead
The current implementation mutates the object within the map function. This can lead to unexpected behavior in reactive systems.
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.title = integration.title
- return i
+ return { ...i, title: integration.title }
}
return i
})
}
258-269
: Avoid mutating objects within map
—return new objects instead
Similar to the previous comment, mutate objects immutably within the map function.
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.is_default = true
+ return { ...i, is_default: true }
} else {
- i.is_default = false
+ return { ...i, is_default: false }
}
- return i
})
}
packages/nocodb/src/db/BaseModelSqlv2.ts (4)
10062-10137
: LGTM! AI prompt handling implementation
The implementation for handling AI prompt columns is well structured and includes:
- Proper metadata tracking (lastModifiedBy, lastModifiedTime)
- Stale state management
- System vs user modification handling
- Referenced column change detection
10071-10075
: 🛠️ Refactor suggestion
Correct the type of isStale
to boolean
The isStale
property is used as a boolean in the code. Currently, it's typed as string?
, which can lead to type mismatches. It should be typed as boolean?
to reflect its usage.
Apply this diff to fix the type:
const obj: {
value?: string;
lastModifiedBy?: string;
lastModifiedTime?: string;
- isStale?: string;
+ isStale?: boolean;
} = {};
Likely invalid or redundant comment.
5721-5723
:
Ensure prepareNocoData
is properly awaited inside map
The prepareNocoData
function is asynchronous, but its calls inside insertDatas.map
are not being properly awaited. This can lead to unhandled Promise rejections and unexpected behavior.
Apply this diff to fix the issue:
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
8635-8653
: 🛠️ Refactor suggestion
Improve error handling for JSON parsing
The JSON parsing logic has minimal error handling with empty catch blocks that could hide important errors. Additionally, there's no validation of the parsed data structure.
Apply this diff to improve error handling:
if (d) {
for (const col of jsonColumns) {
if (d[col.id] && typeof d[col.id] === 'string') {
try {
d[col.id] = JSON.parse(d[col.id]);
+ // Validate expected data structure
+ if (typeof d[col.id] !== 'object') {
+ throw new Error('Invalid JSON structure');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON for column ${col.id}: ${e.message}`);
+ d[col.id] = null; // Set to null on parse error
+ }
}
if (d[col.id]?.length) {
for (let i = 0; i < d[col.id].length; i++) {
if (typeof d[col.id][i] === 'string') {
try {
d[col.id][i] = JSON.parse(d[col.id][i]);
+ // Validate array element structure
+ if (typeof d[col.id][i] !== 'object') {
+ throw new Error('Invalid JSON array element');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON array element: ${e.message}`);
+ d[col.id][i] = null;
+ }
}
}
}
}
}
Likely invalid or redundant comment.
packages/nc-gui/components/cell/AI.vue (3)
118-120
: LGTM! Well-structured computed property
The isDisabledAiButton
computed property effectively consolidates multiple conditions into a single, readable expression.
131-139
:
Fix tooltip disabled logic
The tooltip's disabled condition appears to be inverted, potentially showing tooltips when they should be hidden.
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
Likely invalid or redundant comment.
81-92
: 🛠️ Refactor suggestion
Improve error handling for AI response processing
The object processing logic lacks proper error handling for malformed responses.
if (obj && typeof obj === 'object') {
+ try {
vModel.value = obj
setTimeout(() => {
isAiEdited.value = false
}, 100)
+ } catch (error) {
+ console.error('Error processing AI response:', error)
+ message.error('Failed to process AI response')
+ }
} else {
+ try {
vModel.value = {
...(ncIsObject(vModel.value) ? vModel.value : {}),
isStale: false,
value: resRow[column.value.title!],
}
+ } catch (error) {
+ console.error('Error setting AI response value:', error)
+ message.error('Failed to set AI response value')
+ }
}
Likely invalid or redundant comment.
packages/nc-gui/components/ai/PromptWithFields.vue (2)
63-68
: LGTM! Improved suggestion filtering
The addition of exact match condition ${o.title?.toLowerCase()}}
=== query.toLowerCase()` enhances the accuracy of suggestions.
159-174
:
Add tooltip cleanup on component unmount
The tooltips are initialized but not cleaned up, which could lead to memory leaks.
+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
- tippy(el, {
+ const instance = tippy(el, {
content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
placement: 'top',
allowHTML: true,
arrow: true,
animation: 'fade',
duration: 0,
maxWidth: '200px',
})
+ tooltipInstances.push(instance)
})
}
+onBeforeUnmount(() => {
+ tooltipInstances.forEach(instance => instance.destroy())
+ tooltipInstances.length = 0
+})
Likely invalid or redundant comment.
packages/nc-gui/composables/useNocoAi.ts (2)
20-21
: LGTM! Type enhancement for better flexibility
The change from IntegrationType[]
to Partial<IntegrationType>[]
allows for more flexible integration definitions.
24-28
:
Fix the condition in isAiIntegrationAvailableInList
The function is using the computed property name instead of its value.
const isAiIntegrationAvailableInList = (integrationId?: string) => {
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
return ncIsArrayIncludes(aiIntegrations.value, integrationId, 'id')
}
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/details/Fields.vue (1)
1040-1043
:
Replace undefined ncDelay
with delay
from @vueuse/core
The ncDelay
function is undefined and will cause runtime errors.
Apply this diff to fix the issue:
- await ncDelay(5000)
+ await delay(5000)
packages/nocodb/src/models/AIColumn.ts (2)
26-26
: Avoid using 'this' in static methods
Using this
inside static methods can be confusing and may lead to unexpected behavior. It's clearer and more explicit to reference the class name directly.
Apply this diff to fix the issue:
- return this._insert(
+ return AIColumn._insert(
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
40-40
: Avoid using 'this' in static methods
Using this
inside static methods can be confusing and may lead to unexpected behavior. It's clearer and more explicit to reference the class name directly.
Apply this diff to fix the issue:
- return this._update(
+ return AIColumn._update(
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: Avoid using 'this' in static methods
Using this
inside static methods can be confusing and may lead to unexpected behavior. It's clearer and more explicit to reference the class name directly.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
85-85
: Avoid using 'this' in static methods
Using this
inside static methods can be confusing and may lead to unexpected behavior. It's clearer and more explicit to reference the class name directly.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
136-149
: Prevent SQL injection by avoiding string concatenation in SQL queries
In the MSSQL query, string concatenation is used within the JSON_QUERY
function to build a JSON string. This can lead to SQL injection vulnerabilities if any of the concatenated values are user-controlled or not properly sanitized. It's important to use parameterized queries to prevent such risks.
Consider refactoring the query to eliminate string concatenation. You can construct the JSON object using parameter bindings or MSSQL's JSON functions more safely. For example:
- `UPDATE ??
- SET ?? = JSON_QUERY('{"value":' + ?? + ',"lastModifiedBy":' + ? + ',"lastModifiedTime":' + ? + ',"isStale":' + ? + '}')
- WHERE ?? is not null;`,
+ `UPDATE ??
+ SET ?? = (SELECT TOP 1 *
+ FROM (VALUES (??, ?, ?, ?)) AS t(value, lastModifiedBy, lastModifiedTime, isStale)
+ FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
+ WHERE ?? IS NOT NULL;`,
Alternatively, you can use JSON_MODIFY
or other JSON functions to safely construct the JSON object without concatenation.
packages/nocodb/src/models/Column.ts (1)
921-924
:
Fix incorrect placeholder matching in AI prompts
The code uses a hardcoded {column_id}
placeholder pattern which is incorrect. It should check for the actual column ID.
Apply this diff to fix the placeholder matching:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
Outdated
Show resolved
Hide resolved
// If column wasn't AI before, convert the data to AIRecordType format | ||
if ( | ||
column.uidt !== UITypes.LongText || | ||
column.meta?.[LongTextAiMetaProp] !== true | ||
) { | ||
const baseModel = await reuseOrSave('baseModel', reuse, async () => | ||
Model.getBaseModelSQL(context, { | ||
id: table.id, | ||
dbDriver: await reuseOrSave('dbDriver', reuse, async () => | ||
NcConnectionMgrv2.get(source), | ||
), | ||
}), | ||
); | ||
|
||
await convertValueToAIRecordType({ | ||
source, | ||
table, | ||
column, | ||
baseModel, | ||
sqlClient, | ||
user: param.user, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation and error handling for data conversion
The data conversion from non-AI to AI format lacks proper validation and error handling. Consider adding checks for data integrity and handling conversion failures gracefully.
// If column wasn't AI before, convert the data to AIRecordType format
if (
column.uidt !== UITypes.LongText ||
column.meta?.[LongTextAiMetaProp] !== true
) {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
Model.getBaseModelSQL(context, {
id: table.id,
dbDriver: await reuseOrSave('dbDriver', reuse, async () =>
NcConnectionMgrv2.get(source),
),
}),
);
+ // Start transaction
+ const trx = await sqlClient.knex.transaction();
+ try {
+ // Validate data before conversion
+ const validationResult = await baseModel.validateData({
+ column,
+ user: param.user
+ });
+
+ if (!validationResult.valid) {
+ throw new Error(`Data validation failed: ${validationResult.message}`);
+ }
+
await convertValueToAIRecordType({
source,
table,
column,
baseModel,
sqlClient,
user: param.user,
});
+
+ await trx.commit();
+ } catch (error) {
+ await trx.rollback();
+ NcError.badRequest(`Failed to convert to AI format: ${error.message}`);
+ }
}
Committable suggestion skipped: line range outside the PR's diff.
if (isAIPromptCol(colBody)) { | ||
let prompt = ''; | ||
|
||
/* | ||
Substitute column alias with id in prompt | ||
*/ | ||
if (colBody.prompt_raw) { | ||
await table.getColumns(context); | ||
|
||
prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => { | ||
const column = table.columns.find((c) => c.title === p1); | ||
|
||
if (!column) { | ||
NcError.badRequest(`Field '${p1}' not found`); | ||
} | ||
|
||
return `{${column.id}}`; | ||
}); | ||
} | ||
|
||
colBody.prompt = prompt; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor duplicate prompt processing logic and add validations
The prompt processing logic is duplicated between column update and add operations. Additionally, there are missing validations for AI integration requirements.
- Extract common prompt processing logic to a shared method:
+ private async processPromptTemplate(
+ context: NcContext,
+ table: Model,
+ promptRaw: string
+ ): Promise<string> {
+ await table.getColumns(context);
+
+ // Validate prompt template format
+ const MAX_PROMPT_LENGTH = 1000;
+ if (promptRaw.length > MAX_PROMPT_LENGTH) {
+ NcError.badRequest(`Prompt template exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`);
+ }
+
+ // Check for valid template syntax
+ const templatePattern = /^[^{}]*(\{[^{}]+\}[^{}]*)*$/;
+ if (!templatePattern.test(promptRaw)) {
+ NcError.badRequest('Invalid prompt template format');
+ }
+
+ return promptRaw.replace(/{(.*?)}/g, (match, p1) => {
+ const column = table.columns.find((c) => c.title === p1);
+ if (!column) {
+ NcError.badRequest(`Field '${p1}' not found`);
+ }
+ return `{${column.id}}`;
+ });
+ }
if (isAIPromptCol(colBody)) {
let prompt = '';
if (colBody.prompt_raw) {
- await table.getColumns(context);
- prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
- const column = table.columns.find((c) => c.title === p1);
- if (!column) {
- NcError.badRequest(`Field '${p1}' not found`);
- }
- return `{${column.id}}`;
- });
+ prompt = await this.processPromptTemplate(context, table, colBody.prompt_raw);
}
colBody.prompt = prompt;
}
- Add validation for AI integration requirements:
if (isAIPromptCol(colBody)) {
+ // Validate AI integration requirements
+ if (!colBody.meta?.aiEnabled) {
+ NcError.badRequest('AI integration must be enabled for AI prompt columns');
+ }
+
+ // Validate AI model configuration
+ if (!colBody.meta?.aiModel) {
+ NcError.badRequest('AI model must be specified for AI prompt columns');
+ }
let prompt = '';
// ... rest of the code
}
- Add JSDoc documentation for the AI prompt handling:
+ /**
+ * Processes an AI prompt column during creation
+ * @param colBody - The column configuration
+ * @param table - The table model
+ * @throws {NcError} When AI integration requirements are not met
+ * @throws {NcError} When prompt template is invalid
+ */
if (isAIPromptCol(colBody)) {
// ... rest of the code
}
Committable suggestion skipped: line range outside the PR's diff.
if ( | ||
isAIPromptCol(column) && | ||
(colBody.uidt !== UITypes.LongText || | ||
(colBody.uidt === UITypes.LongText && | ||
colBody.meta?.[LongTextAiMetaProp] !== true)) | ||
) { | ||
const baseModel = await reuseOrSave('baseModel', reuse, async () => | ||
Model.getBaseModelSQL(context, { | ||
id: table.id, | ||
dbDriver: await reuseOrSave('dbDriver', reuse, async () => | ||
NcConnectionMgrv2.get(source), | ||
), | ||
}), | ||
); | ||
|
||
await convertAIRecordTypeToValue({ | ||
source, | ||
table, | ||
column, | ||
baseModel, | ||
sqlClient, | ||
}); | ||
} else if (isAIPromptCol(colBody)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and transaction management for type conversion
The conversion between AI and non-AI column types involves data transformation that should be wrapped in a transaction to maintain data consistency. Additionally, error handling should be added for data conversion failures.
if (
isAIPromptCol(column) &&
(colBody.uidt !== UITypes.LongText ||
(colBody.uidt === UITypes.LongText &&
colBody.meta?.[LongTextAiMetaProp] !== true))
) {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
Model.getBaseModelSQL(context, {
id: table.id,
dbDriver: await reuseOrSave('dbDriver', reuse, async () =>
NcConnectionMgrv2.get(source),
),
}),
);
+ // Start transaction
+ const trx = await sqlClient.knex.transaction();
+ try {
await convertAIRecordTypeToValue({
source,
table,
column,
baseModel,
sqlClient,
});
+ await trx.commit();
+ } catch (error) {
+ await trx.rollback();
+ NcError.badRequest(`Failed to convert AI column: ${error.message}`);
+ }
}
Committable suggestion skipped: line range outside the PR's diff.
packages/nocodb/src/models/Column.ts
Outdated
case UITypes.LongText: { | ||
if (column.meta?.[LongTextAiMetaProp] === true) { | ||
await AIColumn.insert( | ||
context, | ||
{ | ||
fk_model_id: column.fk_model_id, | ||
fk_column_id: colId, | ||
fk_integration_id: column.fk_integration_id, | ||
model: column.model, | ||
prompt: column.prompt, | ||
prompt_raw: column.prompt_raw, | ||
error: column.error, | ||
}, | ||
ncMeta, | ||
); | ||
} | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for AI column insertion
The AI column insertion lacks error handling. Consider adding try-catch and logging.
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ try {
await AIColumn.insert(
context,
{
fk_model_id: column.fk_model_id,
fk_column_id: colId,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
},
ncMeta,
);
+ } catch (error) {
+ logger.error(`Failed to insert AI column: ${error.message}`);
+ throw error;
+ }
}
break;
}
Committable suggestion skipped: line range outside the PR's diff.
} else if (isAIPromptCol(col)) { | ||
if (base.fk_workspace_id !== colOptions.fk_workspace_id) { | ||
colOptions.fk_workspace_id = null; | ||
colOptions.fk_integration_id = null; | ||
colOptions.model = null; | ||
} | ||
|
||
const freshModelData = await this.columnsService.columnAdd(context, { | ||
tableId: getIdOrExternalId(getParentIdentifier(col.id)), | ||
column: withoutId({ | ||
...flatCol, | ||
...{ | ||
fk_integration_id: colOptions.fk_integration_id, | ||
model: colOptions.model, | ||
prompt_raw: colOptions.prompt_raw, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation and error handling for AI prompt column import
The AI prompt column import lacks validation and error handling. Consider adding checks for required fields and error handling.
} else if (isAIPromptCol(col)) {
if (base.fk_workspace_id !== colOptions.fk_workspace_id) {
colOptions.fk_workspace_id = null;
colOptions.fk_integration_id = null;
colOptions.model = null;
}
+ // Validate required fields
+ if (!colOptions.prompt_raw) {
+ this.logger.warn(`Skipping AI prompt column import: Missing prompt_raw for column ${col.title}`);
+ continue;
+ }
+ try {
const freshModelData = await this.columnsService.columnAdd(context, {
tableId: getIdOrExternalId(getParentIdentifier(col.id)),
column: withoutId({
...flatCol,
...{
fk_integration_id: colOptions.fk_integration_id,
model: colOptions.model,
prompt_raw: colOptions.prompt_raw,
},
}) as any,
req: param.req,
user: param.user,
});
+ } catch (error) {
+ this.logger.error(`Failed to import AI prompt column ${col.title}: ${error.message}`);
+ throw error;
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
} else if (isAIPromptCol(col)) { | |
if (base.fk_workspace_id !== colOptions.fk_workspace_id) { | |
colOptions.fk_workspace_id = null; | |
colOptions.fk_integration_id = null; | |
colOptions.model = null; | |
} | |
const freshModelData = await this.columnsService.columnAdd(context, { | |
tableId: getIdOrExternalId(getParentIdentifier(col.id)), | |
column: withoutId({ | |
...flatCol, | |
...{ | |
fk_integration_id: colOptions.fk_integration_id, | |
model: colOptions.model, | |
prompt_raw: colOptions.prompt_raw, | |
}, | |
} else if (isAIPromptCol(col)) { | |
if (base.fk_workspace_id !== colOptions.fk_workspace_id) { | |
colOptions.fk_workspace_id = null; | |
colOptions.fk_integration_id = null; | |
colOptions.model = null; | |
} | |
// Validate required fields | |
if (!colOptions.prompt_raw) { | |
this.logger.warn(`Skipping AI prompt column import: Missing prompt_raw for column ${col.title}`); | |
continue; | |
} | |
try { | |
const freshModelData = await this.columnsService.columnAdd(context, { | |
tableId: getIdOrExternalId(getParentIdentifier(col.id)), | |
column: withoutId({ | |
...flatCol, | |
...{ | |
fk_integration_id: colOptions.fk_integration_id, | |
model: colOptions.model, | |
prompt_raw: colOptions.prompt_raw, | |
}, | |
}) as any, | |
req: param.req, | |
user: param.user, | |
}); | |
} catch (error) { | |
this.logger.error(`Failed to import AI prompt column ${col.title}: ${error.message}`); | |
throw error; | |
} |
f412ec4
to
43ddfc6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (14)
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
156-156
: Consider reducing the delay duration5000ms (5 seconds) is an unusually long duration for copy feedback. Consider reducing it to 2000ms (2 seconds) which is more standard for temporary UI feedback.
- await ncDelay(5000) + await ncDelay(2000)packages/nc-gui/components/smartsheet/Kanban.vue (2)
Line range hint
249-282
: Consider extracting infinite scroll logic into a reusable composableThe infinite scroll implementation could be moved to a separate composable to promote reusability across different views (Kanban, Gallery, etc.).
Example implementation:
// composables/useInfiniteScroll.ts export function useInfiniteScroll(options: { threshold?: number onLoadMore: (e: Event) => Promise<void> }) { const { threshold = 100, onLoadMore } = options const scrollHandler = useDebounceFn(async (e: Event) => { const target = e.target as HTMLElement if (!target) return if (target.scrollTop + target.clientHeight + threshold >= target.scrollHeight) { await onLoadMore(e) } }) const scrollRef: VNodeRef = (element) => { if (element) { element.removeEventListener('scroll', scrollHandler) element.addEventListener('scroll', scrollHandler) } } return { scrollRef } }
Line range hint
283-379
: Consider extracting stack operations into a separate componentThe stack operations (collapse/expand, rename, delete) could be moved to a dedicated component to improve maintainability and reduce the complexity of the main Kanban component.
Example structure:
<!-- components/smartsheet/KanbanStack.vue --> <script setup lang="ts"> interface Props { stack: Stack index: number isCollapsed: boolean // ... other props } // ... emit stack operations </script> <template> <div class="nc-kanban-stack"> <!-- Stack header with operations --> <!-- Stack content --> </div> </template>packages/nc-gui/components/cell/TextArea.vue (2)
Line range hint
421-466
: Consider extracting the user display logic into a computed propertyThe repeated user display name logic could be simplified by extracting it into a computed property.
+const userDisplayName = computed(() => { + if (!props.aiMeta?.lastModifiedBy) return '' + return user?.id === props.aiMeta?.lastModifiedBy + ? 'you' + : idUserMap[props.aiMeta?.lastModifiedBy]?.display_name || idUserMap[props.aiMeta?.lastModifiedBy]?.email +}) -user?.id === props.aiMeta?.lastModifiedBy - ? 'you' - : idUserMap[props.aiMeta?.lastModifiedBy]?.display_name || idUserMap[props.aiMeta?.lastModifiedBy]?.email +userDisplayName.value
Line range hint
637-648
: Consider using design tokens for consistent stylingThe hardcoded color values and spacing in the AI warning message could be replaced with design tokens for better maintainability.
-<div class="flex items-center p-4 bg-nc-bg-purple-light gap-4"> +<div class="flex items-center p-4 bg-nc-bg-purple gap-4"> - <GeneralIcon icon="alertTriangleSolid" class="text-nc-content-purple-medium h-6 w-6 flex-none" /> + <GeneralIcon icon="alertTriangleSolid" class="text-nc-purple h-6 w-6 flex-none" /> <div class="flex flex-col"> - <div class="font-bold text-base text-nc-content-gray">Record Data Updated</div> + <div class="font-bold text-base text-nc-gray">Record Data Updated</div> - <div class="text-nc-content-gray-muted text-sm"> + <div class="text-nc-gray-muted text-sm">packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (3)
Line range hint
4-16
: Consider enhancing type safety and prop definitions.The component's props could be more explicitly typed for better maintainability and type safety.
Consider defining an interface for the props:
+interface AiButtonOptionsProps { + value: any // Consider being more specific about the type + submitBtnLabel: { + label: string + loadingLabel: string + } + saving: boolean +} -const props = defineProps<{ - value: any - submitBtnLabel: { - label: string - loadingLabel: string - } - saving: boolean -}>() +const props = defineProps<AiButtonOptionsProps>()
471-475
: Enhance tooltip accessibility.The tooltip implementation could benefit from improved accessibility features.
Consider adding ARIA attributes and keyboard navigation:
<NcTooltip show-on-truncate-only class="truncate max-w-[150px]"> <template #title>{{ op.title }}</template> - {{ op.title }} + <span role="tooltip" tabindex="0" aria-label="{{ op.title }}"> + {{ op.title }} + </span> </NcTooltip>
Line range hint
32-38
: Consider adding input validation and sanitization.The component processes user input and AI-generated content without explicit sanitization, which could potentially lead to XSS vulnerabilities.
Consider implementing input validation and sanitization:
- Add input validation for user-provided content
- Sanitize AI-generated content before rendering
- Implement Content Security Policy (CSP) headers
Example implementation:
const sanitizeContent = (content: string): string => { // Implement content sanitization logic return DOMPurify.sanitize(content) } // Use in template <div v-html="sanitizeContent(aiGeneratedContent)"></div>packages/nc-gui/composables/useIntegrationsStore.ts (1)
Consider adding error handling and extracting icon processing logic
The codebase shows consistent error handling patterns using try-catch blocks with
extractSdkResponseErrorMsg
andextractSdkResponseErrorMsgv2
utilities, but theloadDynamicIntegrations
function lacks error handling. Consider:
- Adding error handling for the API call:
try { const dynamicIntegrations = await $api.integrations.list() // ... rest of the code } catch (e) { await message.error(await extractSdkResponseErrorMsg(e)) integrationsInitialized.value = false }
- Extracting the icon processing logic into a separate function as suggested in the diff.
The refactoring will improve:
- Error handling consistency with the rest of the codebase
- Code maintainability by separating concerns
- Error recovery by resetting the initialization flag on failure
🔗 Analysis chain
Line range hint
447-496
: Consider splitting the dynamic integration loading logicThe
loadDynamicIntegrations
function handles multiple responsibilities. Consider splitting it into smaller, focused functions for better maintainability.+ const processIntegrationIcon = (meta: { icon?: string, title?: string }, subType: string) => { + if (!meta.icon) return iconMap.puzzle + + if (meta.icon in iconMap) { + return iconMap[meta.icon as keyof typeof iconMap] + } + + if (isValidURL(meta.icon)) { + return h('img', { + src: meta.icon, + alt: meta.title || subType, + }) + } + + return iconMap.puzzle + } const loadDynamicIntegrations = async () => { if (integrationsInitialized.value) return integrationsInitialized.value = true const dynamicIntegrations = (await $api.integrations.list()) as { type: IntegrationsType subType: string meta: { title?: string icon?: string description?: string order?: number } }[] dynamicIntegrations.sort((a, b) => (a.meta.order ?? Infinity) - (b.meta.order ?? Infinity)) for (const di of dynamicIntegrations) { - let icon: FunctionalComponent<SVGAttributes, {}, any, {}> | VNode - if (di.meta.icon) { - if (di.meta.icon in iconMap) { - icon = iconMap[di.meta.icon as keyof typeof iconMap] - } else { - if (isValidURL(di.meta.icon)) { - icon = h('img', { - src: di.meta.icon, - alt: di.meta.title || di.subType, - }) - } - } - } else { - icon = iconMap.puzzle - } + const icon = processIntegrationIcon(di.meta, di.subType) const integration: IntegrationItemType = { title: di.meta.title || di.subType, subType: di.subType, icon, type: di.type, isAvailable: true, dynamic: true, } allIntegrations.push(integration) integrationsRefreshKey.value++ } }Also, consider adding explicit error handling for the API call and icon loading:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check if there are any existing error handlers for integration loading rg -A 2 "catch.*{" .Length of output: 102
Script:
#!/bin/bash # Check if there are any existing error handlers for integration 10000 loading with fixed regex rg -A 2 'catch\s*\{' packages/nc-gui/composables/useIntegrationsStore.ts # Look for try-catch blocks in the file rg -A 5 'try\s*\{' packages/nc-gui/composables/useIntegrationsStore.ts # Check if there are any error handling utilities or patterns used rg 'error|catch|throw' packages/nc-gui/composables/useIntegrationsStore.tsLength of output: 3175
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (1)
Line range hint
1244-1280
: Document workspace ID security checkThe workspace ID validation and nullification is a good security measure. Consider adding a comment explaining this security check and its implications.
+ // Clear workspace and integration IDs if they don't match the base workspace + // This prevents unauthorized access across workspaces if (base.fk_workspace_id !== colOptions.fk_workspace_id) { colOptions.fk_workspace_id = null; colOptions.fk_integration_id = null; colOptions.model = null; }packages/nc-gui/components/ai/Settings.vue (1)
84-86
: Consider adding error handling for model initializationThe condition
if (!vModel.value || !availableModels.value.length)
could be enhanced with error handling for cases where the API call fails.- if (!vModel.value || !availableModels.value.length) { - onIntegrationChange() - } + if (!vModel.value || !availableModels.value.length) { + try { + await onIntegrationChange() + } catch (error) { + console.error('Failed to initialize models:', error) + // Consider showing user feedback + } + }packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (2)
186-196
: Improve readability of AI prompt filter conditionThe AI prompt filter condition is mixed with other conditions making it harder to read. Consider extracting it into a separate variable for better readability.
+ const isAiPromptEditHidden = !((t.name === AIPrompt || t.name === AIButton) && isEdit.value) return ( systemFiledNotEdited && geoDataToggle && !specificDBType && showDeprecatedField && isAllowToAddInFormView && - hideAiPromptOnEdit + isAiPromptEditHidden )
1070-1076
: Simplify nested ternary operators for icon selectionThe nested ternary operators for icon selection reduce readability. Consider using a computed property or helper function.
+ const getColumnIcon = (optName: string, isOpen: boolean) => { + if (isAiButtonSelectOption(optName) && !isOpen) return iconMap.cellAiButton + if (isAiPromptSelectOption(optName) && !isOpen) return iconMap.cellAi + return opt.icon + } - :is="isAiButtonSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAiButton - : isAiPromptSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAi - : opt.icon" + :is="getColumnIcon(opt.name, isColumnTypeOpen)"packages/nocodb/src/models/Column.ts (1)
1957-1970
: Optimize bulk insert for AI columnsThe bulk insert implementation could be optimized by batching the inserts.
case UITypes.LongText: { if (column.meta?.[LongTextAiMetaProp] === true) { + // Batch inserts in groups of 100 for better performance + const batchSize = 100; + for (let i = 0; i < insertArr.length; i += batchSize) { + const batch = insertArr.slice(i, i + batchSize); insertArr.push({ fk_model_id: column.fk_model_id, fk_column_id: column.id, fk_integration_id: column.fk_integration_id, model: column.model, prompt: column.prompt, prompt_raw: column.prompt_raw, error: column.error, }); + } } break; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (69)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(10 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (43)
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/utils/commonUtils.ts
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nocodb/src/models/index.ts
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/components/general/Loader.vue
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nocodb/src/utils/dataConversion.ts
- packages/nocodb/src/models/Integration.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/cell/attachment/index.vue
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nc-gui/components/smartsheet/expanded-form/index.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (52)
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
:
Missing import for ncDelay
function
The ncDelay
function is used but not imported. This will cause a runtime error.
Import the function from the appropriate utility module:
<script lang="ts" setup>
+import { ncDelay } from '~/utils/ncDelay';
packages/nc-gui/components/smartsheet/Kanban.vue (1)
1146-1146
: LGTM: Icon standardization
The change from expand
to maximize
icon aligns with similar updates in other components, maintaining consistency across the UI.
packages/nc-gui/components/cell/TextArea.vue (6)
17-18
: Consider using a more specific default value for meta injection
The injected meta
dependency uses an empty ref()
as a fallback, which could lead to runtime errors when accessing properties.
100-103
: Add null checks to prevent potential runtime errors
The use of non-null assertions (!
) in meta.value!.columns!
may lead to runtime errors if meta.value
or columns
are undefined
.
104-111
: LGTM: Well-structured computed property for AI generation status
The isAiGenerating
computed property effectively combines multiple conditions to determine the AI generation state.
Line range hint 408-421
: LGTM: Clear and informative AI warning message
The warning message implementation effectively communicates the stale state of AI-generated content and provides clear guidance for user action.
629-630
: Correct the usage of Tailwind CSS classes
The class animate-infinite
is not a valid Tailwind CSS utility. The animate-spin
class already provides an infinite spin animation.
Line range hint 708-724
: LGTM: Well-structured CSS for AI text area integration
The CSS changes effectively handle the AI-specific styling requirements while maintaining consistency with the existing design.
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (1)
369-369
: Verify prop name consistency.
The is-edit-column
prop passed to AiSettings
component needs verification for consistency with the component's prop definition.
packages/nc-gui/composables/useIntegrationsStore.ts (4)
93-94
: LGTM: Clean integration of AI state management
The addition of aiIntegrations
from useNocoAi
composable follows Vue's composition pattern well.
191-192
: LGTM: Consistent event tracking implementation
Event tracking is well-implemented with:
- Consistent placement before operations
- Clear event naming
- Proper differentiation between create and duplicate operations
Also applies to: 224-225, 253-254, 287-291
541-541
: LGTM: Clean export of new functionality
The loadDynamicIntegrations
function is properly exported as part of the store's public API.
229-238
:
Avoid mutating objects within map functions
The code still contains object mutations within map functions which can lead to reactivity issues in Vue.
Apply these fixes:
// First instance
aiIntegrations.value = aiIntegrations.value.map((i) =>
i.id === integration.id
? { ...i, title: integration.title }
: i
)
// Second instance
aiIntegrations.value = aiIntegrations.value.map((i) =>
i.id === integration.id
? { ...i, is_default: true }
: { ...i, is_default: false }
)
Also applies to: 258-269
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (3)
Line range hint 1-7
: LGTM: Import changes are clean and follow conventions
The addition of isAIPromptCol
to the imports is well-organized and follows the existing pattern.
1281-1296
: 🛠️ Refactor suggestion
Add validation and error handling for AI prompt column import
The AI prompt column import lacks validation and error handling, as noted in a previous review.
Apply this diff to add validation and error handling:
} else if (isAIPromptCol(col)) {
if (base.fk_workspace_id !== colOptions.fk_workspace_id) {
colOptions.fk_workspace_id = null;
colOptions.fk_integration_id = null;
colOptions.model = null;
}
+ // Validate required fields
+ if (!colOptions.prompt_raw) {
+ this.logger.warn(`Skipping AI prompt column import: Missing prompt_raw for column ${col.title}`);
+ continue;
+ }
+ try {
const freshModelData = await this.columnsService.columnAdd(context, {
tableId: getIdOrExternalId(getParentIdentifier(col.id)),
column: withoutId({
...flatCol,
...{
fk_integration_id: colOptions.fk_integration_id,
model: colOptions.model,
prompt_raw: colOptions.prompt_raw,
},
}) as any,
req: param.req,
user: param.user,
});
+ } catch (error) {
+ this.logger.error(`Failed to import AI prompt column ${col.title}: ${error.message}`);
+ throw error;
+ }
180-183
: Verify the isAIPromptCol function implementation
The addition of !isAIPromptCol(a)
to filter out AI prompt columns is logically sound. However, we should verify the implementation of this function.
✅ Verification successful
isAIPromptCol function is properly implemented and safe to use
The function is well-defined in packages/nocodb-sdk/src/lib/UITypes.ts
with proper dependencies:
- Uses
UITypes.LongText
enum value which is correctly defined parseHelper
is a robust utility that safely handles JSON parsingLongTextAiMetaProp
is properly defined as a constant with value 'ai'
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the implementation of isAIPromptCol function
# Expected: Find the implementation to ensure it's properly defined
# Search for isAIPromptCol implementation
ast-grep --pattern 'function isAIPromptCol($_) {
$$$
}'
# Search for isAIPromptCol type definition
rg -A 5 'export (type|interface).*isAIPromptCol'
Length of output: 589
Script:
#!/bin/bash
# Let's verify the dependencies of isAIPromptCol function
# Check for parseHelper and LongTextAiMetaProp
rg "parseHelper|LongTextAiMetaProp" packages/nocodb-sdk/src/lib/UITypes.ts -A 5
# Check UITypes.LongText definition
rg "UITypes.LongText|LongText =" packages/nocodb-sdk/src/lib/UITypes.ts -A 2
Length of output: 938
Script:
#!/bin/bash
# Let's verify the parseHelper implementation and LongTextAiMetaProp definition
# Check parseHelper implementation
rg -p "export.*parseHelper" packages/nocodb-sdk/src/lib/helperFunctions.ts -A 5
# Check LongTextAiMetaProp definition
rg "export.*LongTextAiMetaProp" -A 2
Length of output: 700
packages/nocodb/src/models/AIColumn.ts (3)
14-16
: Ensure correct instantiation in the castType
method
The castType
method should handle cases where data
might be null
or undefined
. Currently, if data
is falsy, the method returns data
, which could be undefined
instead of an AIColumn
instance. This might lead to unexpected behavior downstream.
Consider explicitly handling falsy values:
- return data && new AIColumn(data);
+ return data ? new AIColumn(data) : null;
26-26
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return this._insert(
+ return AIColumn._insert(
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
40-40
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return this._update(
+ return AIColumn._update(
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
85-85
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nc-gui/components/cell/AI.vue (2)
81-92
: Consider adding error handling for AI response processing
The code processes AI responses without explicit error handling, which could lead to runtime errors if the response format is unexpected.
Consider adding try-catch blocks to handle potential errors and provide user feedback.
131-135
: Fix the tooltip disabled logic
The tooltip's disabled condition seems incorrect. It should be disabled when the button is enabled, not the other way around.
Consider changing:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
This ensures the tooltip is only shown when the button is disabled.
packages/nocodb/src/db/sortV2.ts (1)
203-221
: Remove duplicate condition check
The isAIPromptCol(column)
condition is checked twice unnecessarily. Removing the duplicate check simplifies the code.
Apply this fix:
case UITypes.LongText: {
if (isAIPromptCol(column)) {
let col;
- if (isAIPromptCol(column)) {
if (knex.clientType() === 'pg') {
col = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [
column.column_name,
]);
} else if (knex.clientType().startsWith('mysql')) {
col = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [
column.column_name,
]);
} else if (knex.clientType() === 'sqlite3') {
col = knex.raw(`json_extract(??, '$.value')`, [
column.column_name,
]);
} else if (knex.clientType() === 'mssql') {
col = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]);
}
- }
qb.orderBy(col, sort.direction || 'asc', nulls);
} else {
qb.orderBy(
sanitize(column.column_name),
sort.direction || 'asc',
nulls,
);
}
break;
}
packages/nc-gui/components/ai/PromptWithFields.vue (1)
159-174
: Add tooltip cleanup on component unmount
The tooltips are properly initialized but should be cleaned up when the component is unmounted to prevent memory leaks.
Consider adding cleanup code in onBeforeUnmount
:
+ 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
- tippy(el, {
+ const instance = tippy(el, {
content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
placement: 'top',
allowHTML: true,
arrow: true,
animation: 'fade',
duration: 0,
maxWidth: '200px',
})
+ tooltipInstances.push(instance)
})
}
+ onBeforeUnmount(() => {
+ tooltipInstances.forEach(instance => instance.destroy())
+ tooltipInstances.length = 0
+ })
This change ensures that tooltip instances are properly destroyed when the component is unmounted, preventing potential memory leaks.
packages/nc-gui/components/ai/Settings.vue (3)
10-15
: LGTM: Props definition is clear and well-structured
The addition of the isEditColumn
prop with a default value of false
is clean and follows Vue best practices.
215-221
: LGTM: Scoped styling follows best practices
The added styles use proper scoping and SCSS nesting with appropriate deep selector usage.
70-73
: Verify the impact of early return
The early return when isEditColumn
is true prevents initialization of AI models. Ensure this doesn't affect existing functionality where model data might be needed even in edit mode.
✅ Verification successful
Early return in edit mode is correct and safe
The early return when isEditColumn
is true is safe because:
- The component is a settings panel that allows selecting AI integration and model
- The early return only skips the initial model/integration setup in
onMounted
hook - The component still renders all UI elements and maintains reactive bindings
- The skip only prevents auto-selecting default values, which is appropriate for edit mode
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usages of this component with isEditColumn prop
rg -A 5 '<Settings.*:is-edit-column'
Length of output: 38
Script:
#!/bin/bash
# Let's first find where Settings component is used
rg -l "Settings" --type vue
# Then let's examine the component itself to understand the context
rg -A 10 "isEditColumn" packages/nc-gui/components/ai/Settings.vue
Length of output: 965
Script:
#!/bin/bash
# Let's search for the file with .vue extension explicitly
rg -l "Settings\.vue"
# And let's get more context about the component's usage of models
rg -B 5 -A 10 "const vModel = useVModel" packages/nc-gui/components/ai/Settings.vue
Length of output: 544
Script:
#!/bin/bash
# Let's look at the full component implementation to understand the flow
cat packages/nc-gui/components/ai/Settings.vue
# Also search for any references to this component in the codebase
rg -l "ai/Settings"
Length of output: 7736
packages/nc-gui/composables/useNocoAi.ts (2)
20-21
: LGTM: Type enhancement improves flexibility
The change to Partial<IntegrationType>[]
makes the type more flexible while maintaining type safety.
24-28
:
Fix computed property access in isAiIntegrationAvailableInList
The function has a bug where it's using the computed property name instead of its value.
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
Likely invalid or redundant comment.
packages/nc-gui/utils/columnUtils.ts (4)
15-17
: LGTM: Constants are well-defined
The addition of AIPrompt
constant and its export maintains consistency with existing patterns.
27-32
: LGTM: UI type definition follows existing pattern
The new AIPrompt UI type is well-structured and consistent with other type definitions.
314-316
: 🛠️ Refactor suggestion
Remove redundant Formula type check
The check for UITypes.Formula
is performed twice: once within the switch statement and once afterward.
- if (col.uidt === UITypes.Formula) {
- result.isInvalid = !!(col.colOptions as FormulaType).error
- }
Likely invalid or redundant comment.
289-309
: 🛠️ Refactor suggestion
DRY up duplicate AI validation logic
The AI integration validation logic is duplicated between Button and LongText types.
Consider extracting the common logic into a helper function:
+const isAiColumnInvalid = (colOptions: ButtonType, aiIntegrations: Partial<IntegrationType>[], isReadOnly: boolean) => ({
+ isInvalid:
+ !colOptions.fk_integration_id ||
+ (isReadOnly
+ ? false
+ : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')),
+ tooltip: 'title.aiIntegrationMissing'
+})
case UITypes.Button: {
const colOptions = col.colOptions as ButtonType
if (colOptions.type === ButtonActionsType.Webhook) {
result.isInvalid = !colOptions.fk_webhook_id
} else if (colOptions.type === ButtonActionsType.Url) {
result.isInvalid = !!colOptions.error
} else if (colOptions.type === ButtonActionsType.Ai) {
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const validation = isAiColumnInvalid(colOptions, aiIntegrations, isReadOnly)
+ result.isInvalid = validation.isInvalid
+ result.tooltip = validation.tooltip
}
break
}
case UITypes.LongText: {
if (parseProp(col.meta)[LongTextAiMetaProp]) {
const colOptions = col.colOptions as ButtonType
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const validation = isAiColumnInvalid(colOptions, aiIntegrations, isReadOnly)
+ result.isInvalid = validation.isInvalid
+ result.tooltip = validation.tooltip
}
}
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/details/Fields.vue (2)
Line range hint 2255-2261
: LGTM! Improved expand icon styling
The changes enhance the visual feedback and hover states while maintaining consistency with the design system.
1040-1043
:
Replace undefined ncDelay
with delay
from @vueuse/core
The ncDelay
function is not defined or imported, which will lead to a runtime error.
Apply this diff to fix the issue:
+import { delay } from '@vueuse/core'
await copy(field.id!)
isFieldIdCopied.value = true
-await ncDelay(5000)
+await delay(5000)
isFieldIdCopied.value = false
packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue (2)
1025-1026
: LGTM! Improved AI column type checking
The changes correctly identify AI-related columns by checking both AI prompt columns and AI buttons.
1049-1049
: LGTM! Robust output column handling for AI buttons
The code properly handles output column IDs from button options, including graceful handling of empty or malformed values.
packages/nc-gui/components/smartsheet/grid/Table.vue (2)
855-856
: LGTM! Consistent AI column type checking
The changes maintain consistency with InfiniteTable.vue in handling AI prompt columns and AI buttons.
2140-2146
: LGTM! Consistent expand icon styling
The changes maintain visual consistency with Fields.vue in styling and behavior of expand icons.
packages/nocodb/src/services/columns.service.ts (4)
1578-1600
:
Add transaction management and error handling for data conversion
The conversion from AI to non-AI format should be wrapped in a transaction to maintain data consistency. Additionally, error handling should be added to handle conversion failures gracefully.
Apply this diff to add transaction management:
if (
isAIPromptCol(column) &&
(colBody.uidt !== UITypes.LongText ||
(colBody.uidt === UITypes.LongText &&
colBody.meta?.[LongTextAiMetaProp] !== true))
) {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
Model.getBaseModelSQL(context, {
id: table.id,
dbDriver: await reuseOrSave('dbDriver', reuse, async () =>
NcConnectionMgrv2.get(source),
),
}),
);
+ // Start transaction
+ const trx = await sqlClient.knex.transaction();
+ try {
await convertAIRecordTypeToValue({
source,
table,
column,
baseModel,
sqlClient,
});
+ await trx.commit();
+ } catch (error) {
+ await trx.rollback();
+ NcError.badRequest(`Failed to convert AI column: ${error.message}`);
+ }
}
1601-1621
:
Add security checks for prompt template processing
The prompt template processing needs additional security checks:
- Verify user permissions for referenced columns
- Sanitize input to prevent SQL injection
- Validate template complexity to prevent DoS attacks
Apply this diff to add security checks:
if (colBody.prompt_raw) {
await table.getColumns(context);
+ // Validate template complexity
+ if (this.getTemplateComplexity(colBody.prompt_raw) > MAX_TEMPLATE_COMPLEXITY) {
+ NcError.badRequest('Prompt template too complex');
+ }
prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
const column = table.columns.find((c) => c.title === p1);
if (!column) {
NcError.badRequest(`Field '${p1}' not found`);
}
+ // Verify user has permission to access the column
+ if (!this.hasColumnPermission(context, column, param.user)) {
+ NcError.forbidden(`No access to field '${p1}'`);
+ }
+
+ // Sanitize column reference
+ if (!this.isValidColumnReference(p1)) {
+ NcError.badRequest(`Invalid field reference '${p1}'`);
+ }
return `{${column.id}}`;
});
}
1622-1645
:
Add validation and error handling for data conversion
The data conversion from non-AI to AI format lacks proper validation and error handling. Consider adding checks for data integrity and handling conversion failures gracefully.
Apply this diff to add validation and error handling:
if (
column.uidt !== UITypes.LongText ||
column.meta?.[LongTextAiMetaProp] !== true
) {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
Model.getBaseModelSQL(context, {
id: table.id,
dbDriver: await reuseOrSave('dbDriver', reuse, async () =>
NcConnectionMgrv2.get(source),
),
}),
);
+ // Start transaction
+ const trx = await sqlClient.knex.transaction();
+ try {
+ // Validate data before conversion
+ const validationResult = await baseModel.validateData({
+ column,
+ user: param.user
+ });
+
+ if (!validationResult.valid) {
+ throw new Error(`Data validation failed: ${validationResult.message}`);
+ }
+
await convertValueToAIRecordType({
source,
table,
column,
baseModel,
sqlClient,
user: param.user,
});
+
+ await trx.commit();
+ } catch (error) {
+ await trx.rollback();
+ NcError.badRequest(`Failed to convert to AI format: ${error.message}`);
+ }
}
2282-2304
: 🛠️ Refactor suggestion
Refactor duplicate prompt processing logic and add validations
The prompt processing logic is duplicated between column update and add operations. Additionally, there are missing validations for AI integration requirements.
Apply this diff to refactor and add validations:
+ private async processPromptTemplate(
+ context: NcContext,
+ table: Model,
+ promptRaw: string
+ ): Promise<string> {
+ await table.getColumns(context);
+
+ // Validate prompt template format
+ const MAX_PROMPT_LENGTH = 1000;
+ if (promptRaw.length > MAX_PROMPT_LENGTH) {
+ NcError.badRequest(`Prompt template exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`);
+ }
+
+ // Check for valid template syntax
+ const templatePattern = /^[^{}]*(\{[^{}]+\}[^{}]*)*$/;
+ if (!templatePattern.test(promptRaw)) {
+ NcError.badRequest('Invalid prompt template format');
+ }
+
+ return promptRaw.replace(/{(.*?)}/g, (match, p1) => {
+ const column = table.columns.find((c) => c.title === p1);
+ if (!column) {
+ NcError.badRequest(`Field '${p1}' not found`);
+ }
+ return `{${column.id}}`;
+ });
+ }
if (isAIPromptCol(colBody)) {
+ // Validate AI integration requirements
+ if (!colBody.meta?.aiEnabled) {
+ NcError.badRequest('AI integration must be enabled for AI prompt columns');
+ }
+
+ // Validate AI model configuration
+ if (!colBody.meta?.aiModel) {
+ NcError.badRequest('AI model must be specified for AI prompt columns');
+ }
let prompt = '';
if (colBody.prompt_raw) {
- await table.getColumns(context);
- prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
- const column = table.columns.find((c) => c.title === p1);
- if (!column) {
- NcError.badRequest(`Field '${p1}' not found`);
- }
- return `{${column.id}}`;
- });
+ prompt = await this.processPromptTemplate(context, table, colBody.prompt_raw);
}
colBody.prompt = prompt;
}
packages/nocodb/src/db/BaseModelSqlv2.ts (4)
10062-10137
: Implement AI prompt column handling
New AI prompt column handling has been added with proper state tracking and metadata management.
The implementation:
- Handles stale state tracking for AI columns
- Manages last modified metadata (user and timestamp)
- Properly differentiates between system and user modifications
- Tracks referenced column changes to mark AI columns as stale
- Uses proper JSON serialization for storage
The code follows best practices and includes proper error handling.
9778-9778
: Add raw flag parameter to prepareNocoData()
Added extra parameter with raw flag to control AI prompt handling behavior.
The raw flag parameter allows bypassing AI prompt handling when needed, improving flexibility of the method.
5721-5723
:
Fix Promise handling in bulkInsert()
The Promise handling has been improved by properly awaiting all promises using Promise.all().
Apply this diff to fix the unhandled Promise rejections:
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
8635-8653
: 🛠️ Refactor suggestion
Improve JSON parsing error handling
The JSON parsing error handling has been improved with proper validation and error handling.
Apply this diff to improve error handling:
if (d) {
for (const col of jsonColumns) {
if (d[col.id] && typeof d[col.id] === 'string') {
try {
d[col.id] = JSON.parse(d[col.id]);
+ // Validate expected data structure
+ if (typeof d[col.id] !== 'object') {
+ throw new Error('Invalid JSON structure');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON for column ${col.id}: ${e.message}`);
+ d[col.id] = null; // Set to null on parse error
+ }
}
if (d[col.id]?.length) {
for (let i = 0; i < d[col.id].length; i++) {
if (typeof d[col.id][i] === 'string') {
try {
d[col.id][i] = JSON.parse(d[col.id][i]);
+ // Validate array element structure
+ if (typeof d[col.id][i] !== 'object') {
+ throw new Error('Invalid JSON array element');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON array element: ${e.message}`);
+ d[col.id][i] = null;
+ }
}
}
}
}
}
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (1)
Line range hint 249-267
: LGTM! AI prompt type handling looks good
The type selection logic correctly sets up the AI prompt column with proper metadata and UI type.
packages/nocodb/src/models/Column.ts (3)
573-577
: LGTM! AI column reading implementation looks good
The implementation correctly handles reading AI column options.
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect. It should check for the actual column ID.
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
452-469
: 🛠️ Refactor suggestion
Add error handling for AI column insertion
The AI column insertion lacks error handling which could lead to silent failures.
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ try {
await AIColumn.insert(
context,
{
fk_model_id: column.fk_model_id,
fk_column_id: colId,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
},
ncMeta,
);
+ } catch (error) {
+ logger.error(`Failed to insert AI column: ${error.message}`);
+ throw error;
+ }
}
break;
}
Likely invalid or redundant comment.
if (isAIPromptCol(column)) { | ||
if (knex.clientType() === 'pg') { | ||
field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [ | ||
column.column_name, | ||
]); | ||
} else if (knex.clientType().startsWith('mysql')) { | ||
field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [ | ||
column.column_name, | ||
]); | ||
} else if (knex.clientType() === 'sqlite3') { | ||
field = knex.raw(`json_extract(??, '$.value')`, [ | ||
column.column_name, | ||
]); | ||
} else if (knex.clientType() === 'mssql') { | ||
field = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for JSON extraction
The JSON extraction logic should handle invalid JSON values to prevent runtime errors.
if (isAIPromptCol(column)) {
+ const handleInvalidJson = (field) => {
+ return knex.raw('CASE WHEN JSON_VALID(??) THEN ? ELSE NULL END', [
+ column.column_name,
+ field
+ ]);
+ };
if (knex.clientType() === 'pg') {
- field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name])
+ );
} else if (knex.clientType().startsWith('mysql')) {
- field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name])
+ );
} // Apply similar changes for other DB types
}
Committable suggestion skipped: line range outside the PR's diff.
43ddfc6
to
c2d7348
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (14)
packages/nc-gui/components/smartsheet/column/ButtonOptions.vue (2)
Line range hint
174-212
: Use i18n for validation messagesThe validation message for
formula_raw
is hardcoded in English. Consider using the i18n system for consistency with other messages.formula_raw: [ { required: true, - message: 'Prompt required for AI Button', + message: t('msg.promptRequiredForAiButton'), }, ],
Line range hint
212-224
: Consider using constants for default valuesMove the hardcoded default values for AI button properties to constants for better maintainability and reusability.
+const AI_BUTTON_DEFAULTS = { + theme: 'light', + label: 'Generate data', + color: 'purple', + icon: 'ncAutoAwesome', +} as const; if (vModel.value.type === ButtonActionsType.Ai) { - vModel.value.theme = 'light' - vModel.value.label = 'Generate data' - vModel.value.color = 'purple' - vModel.value.icon = 'ncAutoAwesome' + Object.assign(vModel.value, AI_BUTTON_DEFAULTS) vModel.value.output_column_ids = '' }packages/nc-gui/composables/useIntegrationsStore.ts (2)
Line range hint
447-496
: Consider using immutable state updatesThe function mutates the global
allIntegrations
array directly withpush
. Consider using immutable updates for better state management.- allIntegrations.push(integration) + allIntegrations = [...allIntegrations, integration]Additionally, consider adding type safety improvements:
interface IntegrationMeta { title?: string icon?: string description?: string order?: number } interface DynamicIntegration { type: IntegrationsType subType: string meta: IntegrationMeta }
296-296
: Use optional chaining for safer null checksConsider using optional chaining for better null safety.
- if (response && response?.id) + if (response?.id)🧰 Tools
🪛 Biome (1.9.4)
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nc-gui/utils/columnUtils.ts (1)
269-273
: Consider adding type guard for aiIntegrations parameterThe
aiIntegrations
parameter is typed asPartial<IntegrationType>[]
but the code assumes certain properties exist. Consider adding a type guard to ensure type safety.-const isColumnInvalid = ( +const isColumnInvalid = ( col: ColumnType, - aiIntegrations: Partial<IntegrationType>[] = [], + aiIntegrations: Pick<IntegrationType, 'id'>[] = [], isReadOnly: boolean = false, ): { isInvalid: boolean; tooltip: string } => {packages/nc-gui/components/ai/Settings.vue (2)
10-11
: Add prop validation forisEditColumn
.Consider adding runtime validation for the
isEditColumn
prop to ensure type safety.const props = withDefaults( defineProps<{ fkIntegrationId?: string model?: string randomness?: string workspaceId: string scope?: string showTooltip?: boolean - isEditColumn?: boolean + isEditColumn?: boolean & { type: Boolean } }>(), { showTooltip: true, isEditColumn: false, }, )
Line range hint
82-104
: Add error handling for thegenerateRows
asynchronous call.The
generate
function lacks error handling for thegenerateRows
call.- const res = await generateRows( - meta.value.id!, - { - title: previewFieldTitle.value, - prompt_raw: vModel.value.prompt_raw, - fk_integration_id: vModel.value.fk_integration_id, - uidt: UITypes.LongText, - }, - [pk], - ) + try { + const res = await generateRows( + meta.value.id!, + { + title: previewFieldTitle.value, + prompt_raw: vModel.value.prompt_raw, + fk_integration_id: vModel.value.fk_integration_id, + uidt: UITypes.LongText, + }, + [pk], + ) + if (res?.length && res[0]?.[previewFieldTitle.value]) { + previewRow.value.row = { + ...res[0], + [previewFieldTitle.value]: { + value: res[0]?.[previewFieldTitle.value], + }, + } + isAlreadyGenerated.value = true + } + } catch (error) { + message.error(`Failed to generate preview: ${error.message}`) + } finally { + generatingPreview.value = false + }packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
126-140
: Simplify conditional logic inhandleDisableSubmitBtn
.The function can be simplified to improve readability.
- const handleDisableSubmitBtn = () => { - if (!isEnabledGenerateText.value) { - if (disableSubmitBtn.value) { - disableSubmitBtn.value = false - } - return - } - if (isPreviewEnabled.value) { - disableSubmitBtn.value = false - } else { - disableSubmitBtn.value = true - } - } + const handleDisableSubmitBtn = () => { + disableSubmitBtn.value = isEnabledGenerateText.value && !isPreviewEnabled.value + }
150-166
: Consider extracting validation logic.The watch effect contains validation logic that could be moved to a separate function for better maintainability.
+ const updateValidations = (hasAiEnabled: boolean) => { + setAdditionalValidations({ + fk_integration_id: [{ + required: hasAiEnabled, + message: hasAiEnabled ? t('title.aiIntegrationMissing') : undefined + }], + }) + } + watch( isEnabledGenerateText, - (newValue) => { - if (newValue) { - setAdditionalValidations({ - fk_integration_id: [{ required: true, message: t('title.aiIntegrationMissing') }], - }) - } else { - setAdditionalValidations({ - fk_integration_id: [{ required: false }], - }) - } - }, + updateValidations, { immediate: true } )packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (1)
1098-1104
: Consider using a more maintainable approach for icon selectionThe nested ternary operations make the code harder to read and maintain.
Consider refactoring to a more readable approach:
-:is=" - isAiButtonSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAiButton - : isAiPromptSelectOption(opt.name) && !isColumnTypeOpen - ? iconMap.cellAi - : opt.icon -" +:is="getColumnTypeIcon(opt.name, isColumnTypeOpen)"Add a helper method:
function getColumnTypeIcon(name: string, isOpen: boolean) { if (!isOpen) { if (isAiButtonSelectOption(name)) return iconMap.cellAiButton; if (isAiPromptSelectOption(name)) return iconMap.cellAi; } return opt.icon; }packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue (1)
1051-1051
: Improve error handling in AI bulk generationThe AI bulk generation logic could benefit from more robust error handling and user feedback.
if (isAiButton(field)) { + try { outputColumnIds = ncIsString(field.colOptions?.output_column_ids) && field.colOptions.output_column_ids.split(',').length > 0 ? field.colOptions.output_column_ids.split(',') : [] + } catch (error) { + console.error('Failed to parse output column IDs:', error) + message.error('Failed to process AI button configuration') + return + } }packages/nc-gui/components/smartsheet/grid/Table.vue (2)
Line range hint
879-916
: Add validation and error handling for AI bulk generationThe function now properly handles:
- Output column IDs for AI button columns
- Validation of column existence
- Proper cleanup of generating states
Consider adding error handling for failed row updates:
if (res) { + try { for (const row of res) { const oldRow = dataRef.value.find( (r) => extractPkFromRow(r.row, meta.value!.columns!) === extractPkFromRow(row, meta.value!.columns!), ); if (oldRow) { oldRow.row = { ...oldRow.row, ...row } } } + } catch (error) { + console.error('Failed to update rows:', error) + } }
2140-2146
: Improve expand button styling and accessibilityThe expand button styling has been enhanced with proper border and hover states.
Consider adding ARIA labels for better accessibility:
<component :is="iconMap.maximize" v-if="expandForm" v-e="['c:row-expand:open']" class="select-none nc-row-expand opacity-90 w-4 h-4" + aria-label="Expand row details" @click="expandAndLooseFocus(row, state)" />
packages/nocodb/src/services/columns.service.ts (1)
1622-1645
: Add data conversion for non-AI to AI formatThe code handles converting data to AI format when changing column type to AI.
Add transaction handling to ensure data consistency:
+ const trx = await sqlClient.knex.transaction(); + try { await convertValueToAIRecordType({ source, table, column, baseModel, sqlClient, user: param.user, }); + await trx.commit(); + } catch (error) { + await trx.rollback(); + throw new Error(`Failed to convert to AI format: ${error.message}`); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (69)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(11 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (43)
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/models/index.ts
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/utils/commonUtils.ts
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/nc/Button.vue
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/components/general/Loader.vue
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nocodb/src/db/sortV2.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nocodb/src/utils/globals.ts
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/smartsheet/Kanban.vue
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (46)
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
: 🛠️ Refactor suggestion
The ncDelay
function is undefined
The function ncDelay
is called but not imported or defined within this component.
Additionally, consider these improvements:
- The 5-second delay might be too long for user feedback. A 2-3 second delay is typically sufficient.
- Add error handling for the copy operation.
const () => {
+ try {
await copy(view.value!.id!)
isViewIdCopied.value = true
- await ncDelay(5000)
+ await new Promise(resolve => setTimeout(resolve, 2000))
isViewIdCopied.value = false
+ } catch (e: any) {
+ message.error(t('msg.error.copyToClipboardError'))
+ }
}
packages/nc-gui/components/smartsheet/column/ButtonOptions.vue (1)
Line range hint 1-724
: Overall implementation looks good!
The changes effectively implement AI button configuration with proper validation and initialization. The code is well-structured and follows Vue.js best practices.
packages/nc-gui/composables/useIntegrationsStore.ts (4)
93-94
: LGTM: AI Integration State Management
The integration with the useNocoAi
composable is properly implemented.
191-192
: LGTM: Event Tracking Implementation
Event tracking is consistently implemented across all integration operations with proper naming conventions.
Also applies to: 224-225, 253-254, 287-292
541-541
: LGTM: Store Export
The loadDynamicIntegrations
function is properly exported in the store's return object.
229-238
:
Fix object mutations in map functions
The code mutates objects within map functions, which can lead to reactivity issues in Vue.
Previous review comments have already addressed this issue. Please implement the suggested fixes for both instances:
- For title update (lines 229-238):
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) =>
i.id === integration.id
? { ...i, title: integration.title }
: i
)
}
- For default status update (lines 258-269):
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) =>
i.id === integration.id
? { ...i, is_default: true }
: { ...i, is_default: false }
)
}
Also applies to: 258-269
packages/nc-gui/utils/columnUtils.ts (4)
15-18
: LGTM: Constants are well-defined and properly exported
The new constants AIPrompt
and LongTextAiMetaProp
are clearly defined and follow the existing pattern.
27-32
: LGTM: UI type definition follows established pattern
The new AIPrompt
UI type is well-structured with appropriate flags for feature management.
289-296
: 🛠️ Refactor suggestion
Extract duplicate AI validation logic
The AI integration validation logic is duplicated between Button and LongText types.
Extract the common logic into a helper function:
+const isAiColumnInvalid = (
+ colOptions: ButtonType,
+ aiIntegrations: Pick<IntegrationType, 'id'>[],
+ isReadOnly: boolean
+): { isInvalid: boolean; tooltip: string } => ({
+ isInvalid:
+ !colOptions.fk_integration_id ||
+ (isReadOnly
+ ? false
+ : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')),
+ tooltip: 'title.aiIntegrationMissing'
+})
const isColumnInvalid = (
col: ColumnType,
- aiIntegrations: Partial<IntegrationType>[] = [],
+ aiIntegrations: Pick<IntegrationType, 'id'>[] = [],
isReadOnly: boolean = false,
): { isInvalid: boolean; tooltip: string } => {
// ...
case ButtonActionsType.Ai: {
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const aiValidation = isAiColumnInvalid(colOptions, aiIntegrations, isReadOnly)
+ result.isInvalid = aiValidation.isInvalid
+ result.tooltip = aiValidation.tooltip
}
// ...
case UITypes.LongText: {
if (parseProp(col.meta)[LongTextAiMetaProp]) {
const colOptions = col.colOptions as ButtonType
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id && !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const aiValidation = isAiColumnInvalid(colOptions, aiIntegrations, isReadOnly)
+ result.isInvalid = aiValidation.isInvalid
+ result.tooltip = aiValidation.tooltip
}
}
Also applies to: 299-309
314-317
: 🛠️ Refactor suggestion
Remove redundant Formula type check
The check for UITypes.Formula
is performed twice: once within the switch statement and once afterward.
Remove the redundant check:
- if (col.uidt === UITypes.Formula) {
- result.isInvalid = !!(col.colOptions as FormulaType).error
- }
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (2)
369-369
: LGTM: Prop addition follows Vue.js conventions
The addition of :is-edit-column
prop with the isEdit
value from the store follows Vue.js naming conventions and best practices.
471-475
: LGTM: Tooltip implementation improves user experience
The tooltip is properly implemented with the show-on-truncate-only
prop, ensuring it appears only when the text is truncated. This follows best practices for progressive enhancement of the UI.
packages/nocodb/src/models/AIColumn.ts (2)
14-16
:
Ensure consistent return types in castType
method
The castType
method may return undefined
if data
is falsy, leading to inconsistent return types. It's better to explicitly return null
when data
is falsy to maintain consistency.
Apply this diff to fix the issue:
- return data && new AIColumn(data);
+ return data ? new AIColumn(data) : null;
26-26
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be confusing because this
refers to the class, not an instance. It's clearer to reference the class name directly.
Apply this diff to fix the issue:
At line 26:
- return this._insert(
+ return AIColumn._insert(
At line 40:
- return this._update(
+ return AIColumn._update(
Also applies to: 40-40
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (1)
61-61
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can lead to confusion since this
refers to the class itself in a static context. It's clearer to use the class name directly.
Apply this diff to fix the issue:
At line 61:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
At line 85:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
Also applies to: 85-85
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
135-149
:
Avoid string concatenation in SQL queries to prevent SQL injection
In the MSSQL query, string concatenation is used to build the JSON string, which can lead to SQL injection vulnerabilities. It's important to use parameterized queries or proper JSON construction methods to prevent security risks.
Consider refactoring the query to use parameter bindings or MSSQL's JSON functions without concatenation. For example, you can use OPENJSON
or FOR JSON
to construct JSON objects safely.
Apply this diff to fix the issue:
- await sqlClient.raw(
- `UPDATE ??
- SET ?? = JSON_QUERY('{"value":' + ?? + ',"lastModifiedBy":' + ? + ',"lastModifiedTime":' + ? + ',"isStale":' + ? + '}')
- WHERE ?? is not null;`,
- [
- baseModel.getTnPath(table.table_name),
- column.column_name,
- column.column_name,
- commonRecord.lastModifiedBy.toString(),
- commonRecord.lastModifiedTime.toString(),
- commonRecord.isStale,
- column.column_name,
- ],
- );
+ await sqlClient.raw(
+ `UPDATE ??
+ SET ?? = JSON_QUERY(?)
+ WHERE ?? IS NOT NULL;`,
+ [
+ baseModel.getTnPath(table.table_name),
+ column.column_name,
+ JSON.stringify({
+ value: sqlClient.ref(column.column_name),
+ lastModifiedBy: commonRecord.lastModifiedBy.toString(),
+ lastModifiedTime: commonRecord.lastModifiedTime.toString(),
+ isStale: commonRecord.isStale,
+ }),
+ column.column_name,
+ ],
+ );
Likely invalid or redundant comment.
packages/nc-gui/components/cell/AI.vue (2)
81-92
: Consider adding error handling for AI response processing
The code processes AI responses without explicit error handling, which could lead to runtime errors if the response format is unexpected.
Consider wrapping the response processing in try...catch
blocks to handle any potenti
F438
al errors gracefully.
Apply this diff to enhance error handling:
if (obj && typeof obj === 'object') {
+ try {
vModel.value = obj
setTimeout(() => {
isAiEdited.value = false
}, 100)
+ } catch (error) {
+ console.error('Error processing AI response:', error)
+ // Optionally, display a user-friendly error message
+ }
} else {
+ try {
vModel.value = {
...(ncIsObject(vModel.value) ? vModel.value : {}),
isStale: false,
value: resRow[column.value.title!],
}
+ } catch (error) {
+ console.error('Error setting AI response value:', error)
+ // Optionally, display a user-friendly error message
+ }
}
131-135
:
Fix the tooltip disabled logic
The current logic for disabling the tooltip appears to be inverted. The tooltip should be disabled when the button is enabled, not when it is disabled.
Consider updating the :disabled
prop of NcTooltip
to reflect the correct logic.
Apply this diff to fix the issue:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
packages/nc-gui/components/ai/PromptWithFields.vue (1)
159-174
: 🛠️ Refactor suggestion
Add tooltip cleanup on component unmount
The tooltips initialized with tippy
should be cleaned up when the component is unmounted to prevent memory leaks.
Consider storing the tooltip instances and destroying them in the onBeforeUnmount
hook.
Apply this change to manage the tooltip instances:
+ 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
- tippy(el, {
+ const instance = tippy(el, {
content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
placement: 'top',
allowHTML: true,
arrow: true,
animation: 'fade',
duration: 0,
maxWidth: '200px',
})
+ tooltipInstances.push(instance)
})
}
+ onBeforeUnmount(() => {
+ tooltipInstances.forEach((instance) => instance.destroy())
+ tooltipInstances.length = 0
+ })
packages/nc-gui/components/ai/Settings.vue (2)
24-28
:
Fix the condition in isAiIntegrationAvailableInList
.
The function has a bug where it's using the computed property name instead of its value.
const isAiIntegrationAvailableInList = (integrationId?: string) => {
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
return ncIsArrayIncludes(aiIntegrations.value, integrationId, 'id')
}
Likely invalid or redundant comment.
70-72
: 🛠️ Refactor suggestion
Add cleanup in early return.
When returning early due to missing data, ensure generatingPreview
is reset.
if (!formattedData.value.length || !pk) {
message.error('Include at least 1 sample record in table to generate')
+ try {
generatingPreview.value = false
+ } finally {
+ // Ensure cleanup happens even if error occurs
+ generatingPreview.value = false
+ }
return
}
Likely invalid or redundant comment.
packages/nc-gui/composables/useNocoAi.ts (1)
20-21
: Consider adding migration strategy for type change.
The type change from IntegrationType[]
to Partial<IntegrationType>[]
might affect existing code that expects all properties to be present.
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
48-58
:
Initialize vModel.value.meta
before assignment.
The setter of isEnabledGenerateText
might throw if vModel.value.meta
is undefined.
set: (value: boolean) => {
+ if (!vModel.value.meta) vModel.value.meta = {}
vModel.value.meta[LongTextAiMetaProp] = value
vModel.value.prompt_raw = ''
previewRow.value.row = {}
isAlreadyGenerated.value = false
},
Likely invalid or redundant comment.
66-104
:
Add error handling for async operations.
The generate
function should handle errors from both loadViewData
and generateRows
.
const generate = async () => {
+ try {
generatingPreview.value = true
await loadViewData()
const pk = formattedData.value.length
? extractPkFromRow(unref(formattedData.value[0].row), meta.value?.columns || [])
: ''
if (!formattedData.value.length || !pk) {
message.error('Include at least 1 sample record in table to generate')
return
}
previewFieldTitle.value = vModel.value?.title || 'temp_title'
const res = await generateRows(/* ... */)
if (res?.length && res[0]?.[previewFieldTitle.value]) {
previewRow.value.row = {/* ... */}
isAlreadyGenerated.value = true
}
+ } catch (error) {
+ message.error(`Failed to generate: ${error.message}`)
+ } finally {
+ generatingPreview.value = false
+ }
- generatingPreview.value = false
}
Likely invalid or redundant comment.
packages/nocodb/src/models/Integration.ts (2)
454-457
: LGTM: Defensive initialization of config.models array
The initialization of config.models
as an empty array when it's not already an array is a good defensive programming practice that prevents potential null reference errors.
474-500
: 🛠️ Refactor suggestion
Refactor duplicate unbinding logic
The unbinding logic is duplicated between delete
and softDelete
methods. Consider extracting it into a separate private method to improve maintainability.
Apply this diff to reduce code duplication:
+ private async unbindAssociations(ncMeta = Noco.ncMeta) {
+ // unbind all buttons and long texts associated with this integration
+ await ncMeta.metaUpdate(
+ this.fk_workspace_id ? this.fk_workspace_id : RootScopes.WORKSPACE,
+ RootScopes.WORKSPACE,
+ MetaTable.COL_BUTTON,
+ {
+ fk_integration_id: null,
+ model: null,
+ },
+ {
+ fk_integration_id: this.id,
+ },
+ );
+
+ await ncMeta.metaUpdate(
+ this.fk_workspace_id ? this.fk_workspace_id : RootScopes.WORKSPACE,
+ RootScopes.WORKSPACE,
+ MetaTable.COL_LONG_TEXT,
+ {
+ fk_integration_id: null,
+ model: null,
+ },
+ {
+ fk_integration_id: this.id,
+ },
+ );
+ }
async delete(ncMeta = Noco.ncMeta) {
const sources = await this.getSources(ncMeta, true);
for (const source of sources) {
await source.delete(
{
workspace_id: this.fk_workspace_id,
base_id: source.base_id,
},
ncMeta,
);
}
- // unbind all buttons and long texts associated with this integration
- await ncMeta.metaUpdate(...)
- await ncMeta.metaUpdate(...)
+ await this.unbindAssociations(ncMeta);
return await ncMeta.metaDelete(...);
}
async softDelete(ncMeta = Noco.ncMeta) {
const sources = await this.getSources(ncMeta, true);
for (const source of sources) {
await source.softDelete(...);
}
- // unbind all buttons and long texts associated with this integration
- await ncMeta.metaUpdate(...)
- await ncMeta.metaUpdate(...)
+ await this.unbindAssociations(ncMeta);
await ncMeta.metaUpdate(...);
}
Also applies to: 522-547
packages/nc-gui/components/cell/TextArea.vue (3)
17-18
: 🛠️ Refactor suggestion
Consider using a default value for meta injection
The injected meta
dependency uses ref()
as a fallback, which could lead to runtime errors when accessing properties. Consider providing a more specific default value.
-const meta = inject(MetaInj, ref())
+const meta = inject(MetaInj, ref({ columns: [] }))
100-103
:
Avoid using non-null assertions to prevent potential runtime errors
The use of non-null assertions !
in meta.value!.columns!
may lead to runtime errors if meta.value
or columns
are undefined
. Consider adding null checks or default values to ensure the code is robust against null or undefined values.
Apply this diff to handle possible null values:
const rowId = computed(() => {
- return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
+ if (currentRow.value?.row && meta.value?.columns) {
+ return extractPkFromRow(currentRow.value.row, meta.value.columns)
+ }
+ return null
})
542-545
:
Correct the usage of Tailwind CSS classes
The class animate-infinite
is not a valid Tailwind CSS utility. The animate-spin
class already provides an infinite spin animation. Remove animate-infinite
to prevent any unexpected behavior.
Apply this diff to correct the class:
<GeneralIcon
icon="refresh"
class="transform group-hover:(!text-grey-800) text-gray-700 w-3 h-3"
- :class="{ 'animate-infinite animate-spin': isAiGenerating }"
+ :class="{ 'animate-spin': isAiGenerating }"
/>
packages/nc-gui/components/smartsheet/expanded-form/index.vue (2)
330-333
: LGTM: Improved user feedback for URL copying
The addition of a delay after copying the URL provides better visual feedback to users. The 5-second timeout is a reasonable duration for the copy confirmation message.
Line range hint 337-353
: LGTM: Enhanced modal state handling
The saveChanges method now properly handles both prevent change and close modal states, ensuring consistent behavior when saving changes.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (2)
186-196
: LGTM: Proper handling of AI prompt visibility in edit mode
The condition hideAiPromptOnEdit
correctly prevents AI prompt and button types from being shown in edit mode, which is the expected behavior since these types cannot be changed after creation.
Line range hint 263-285
: LGTM: Comprehensive handling of AI prompt type selection
The onSelectType
function properly handles AI prompts by:
- Setting the correct underlying type (LongText)
- Initializing required metadata
- Preserving AI-specific properties
packages/nocodb/src/db/conditionV2.ts (1)
596-613
:
Add error handling for JSON extraction
The JSON extraction logic should handle invalid JSON values to prevent runtime errors.
Apply this diff to add error handling:
if (isAIPromptCol(column)) {
+ const handleInvalidJson = (field) => {
+ return knex.raw('CASE WHEN JSON_VALID(??) THEN ? ELSE NULL END', [
+ column.column_name,
+ field
+ ]);
+ };
if (knex.clientType() === 'pg') {
- field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name])
+ );
} else if (knex.clientType().startsWith('mysql')) {
- field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name])
+ );
} // Apply similar changes for other DB types
}
packages/nocodb/src/models/Column.ts (2)
452-469
:
Add error handling for AI column insertion
The AI column insertion lacks error handling.
Apply this diff to add error handling:
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ try {
await AIColumn.insert(
context,
{
fk_model_id: column.fk_model_id,
fk_column_id: colId,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
},
ncMeta,
);
+ } catch (error) {
+ logger.error(`Failed to insert AI column: ${error.message}`);
+ throw error;
+ }
}
break;
}
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect.
Apply this diff to fix the placeholder matching:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (1)
182-182
: LGTM: AI prompt column filtering
The filtering logic correctly excludes AI prompt columns from the initial import, which is necessary since they require special handling.
packages/nc-gui/components/smartsheet/details/Fields.vue (1)
1040-1043
:
Fix undefined ncDelay
function
The ncDelay
function is not defined or imported, which will lead to a runtime error. Use the delay
function from @vueuse/core
instead.
+import { delay } from '@vueuse/core'
await copy(field.id!)
isFieldIdCopied.value = true
-await ncDelay(5000)
+await delay(5000)
isFieldIdCopied.value = false
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue (1)
1027-1028
: LGTM: AI prompt column check
The isSelectedOnlyAI
computed property correctly checks for both AI prompt columns and AI buttons.
packages/nc-gui/components/smartsheet/grid/Table.vue (1)
855-856
: Enhance AI column type checking
The condition now properly checks for both AI prompt columns and AI button columns, improving type safety.
packages/nocodb/src/services/columns.service.ts (3)
2282-2304
:
Add AI prompt handling during column creation
The code processes AI prompt templates during column creation.
The prompt template processing needs additional security checks as mentioned in the previous comment.
Add validation for AI integration requirements:
if (isAIPromptCol(colBody)) {
+ // Validate AI integration requirements
+ if (!colBody.meta?.aiEnabled) {
+ NcError.badRequest('AI integration must be enabled for AI prompt columns');
+ }
+ // Validate AI model configuration
+ if (!colBody.meta?.aiModel) {
+ NcError.badRequest('AI model must be specified for AI prompt columns');
+ }
let prompt = '';
// Rest of the code...
}
1601-1621
:
Add prompt template processing
The code processes the prompt template by replacing column aliases with their IDs.
The prompt template processing needs additional security checks:
- Validate template complexity to prevent DoS attacks
- Sanitize input to prevent injection attacks
if (colBody.prompt_raw) {
await table.getColumns(context);
+ // Validate template complexity
+ const MAX_TEMPLATE_LENGTH = 10000;
+ const MAX_PLACEHOLDERS = 50;
+ if (colBody.prompt_raw.length > MAX_TEMPLATE_LENGTH) {
+ NcError.badRequest('Prompt template too long');
+ }
+ if ((colBody.prompt_raw.match(/{/g) || []).length > MAX_PLACEHOLDERS) {
+ NcError.badRequest('Too many placeholders in template');
+ }
+ // Validate template format
+ const validTemplatePattern = /^[^{}]*(\{[^{}]+\}[^{}]*)*$/;
+ if (!validTemplatePattern.test(colBody.prompt_raw)) {
+ NcError.badRequest('Invalid prompt template format');
+ }
prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
const column = table.columns.find((c) => c.title === p1);
if (!column) {
NcError.badRequest(`Field '${p1}' not found`);
}
+ // Sanitize column reference
+ if (!/^[a-zA-Z0-9_]+$/.test(p1)) {
+ NcError.badRequest('Invalid field name format');
+ }
return `{${column.id}}`;
});
}
Likely invalid or redundant comment.
1578-1600
: Add data conversion for AI column type changes
The code handles converting data from AI format to regular format when changing column type.
Run the following script to verify the data conversion:
✅ Verification successful
Let me gather more information about the data conversion implementation to verify it properly.
Let me try a different approach to get the implementation details.
Let me get more context about the data conversion implementation.
Data conversion for AI columns properly implemented
The code correctly handles bidirectional data conversion between AI and non-AI formats across all supported databases (PostgreSQL, MySQL, SQLite, and MS SQL):
convertAIRecordTypeToValue
: Extracts the value from JSON object when converting from AI to regular formatconvertValueToAIRecordType
: Wraps the value in a JSON object with metadata when converting to AI format
The implementation includes proper null checks and database-specific JSON handling functions for each database type.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify data conversion from AI to non-AI format
# Test: Search for any AI prompt columns that were converted
rg -A 5 "convertAIRecordTypeToValue|convertValueToAIRecordType"
Length of output: 2279
Script:
#!/bin/bash
# Check the implementation of data conversion functions
ast-grep --pattern 'export const convertAIRecordTypeToValue = async $_'
ast-grep --pattern 'export const convertValueToAIRecordType = async $_'
Length of output: 65856
Script:
#!/bin/bash
# Search for the data conversion implementation files
rg -g "*.ts" -A 10 "export const convertAIRecordTypeToValue" packages/nocodb/src/utils/
Length of output: 864
Script:
#!/bin/bash
# Get the full implementation of both conversion functions
cat packages/nocodb/src/utils/dataConversion.ts
Length of output: 4846
packages/nocodb/src/db/BaseModelSqlv2.ts (3)
10062-10137
: LGTM! Well-structured AI prompt column handling
The implementation properly handles:
- AI field value storage and updates
- Last modified tracking with user attribution
- Staleness state management based on referenced column changes
- Different behavior for system vs user updates
5721-5723
:
Fix async operation handling in bulkInsert
The current implementation doesn't properly await all prepareNocoData operations. This could lead to race conditions or data inconsistency.
Apply this diff to properly handle async operations:
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
8635-8653
: 🛠️ Refactor suggestion
Improve error handling for JSON parsing
The JSON parsing logic has minimal error handling with empty catch blocks that could hide important errors. Additionally, there's no validation of the parsed data structure.
Apply this diff to improve error handling:
if (d) {
for (const col of jsonColumns) {
if (d[col.id] && typeof d[col.id] === 'string') {
try {
d[col.id] = JSON.parse(d[col.id]);
+ // Validate expected data structure
+ if (typeof d[col.id] !== 'object') {
+ throw new Error('Invalid JSON structure');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON for column ${col.id}: ${e.message}`);
+ d[col.id] = null; // Set to null on parse error
+ }
}
if (d[col.id]?.length) {
for (let i = 0; i < d[col.id].length; i++) {
if (typeof d[col.id][i] === 'string') {
try {
d[col.id][i] = JSON.parse(d[col.id][i]);
+ // Validate array element structure
+ if (typeof d[col.id][i] !== 'object') {
+ throw new Error('Invalid JSON array element');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON array element: ${e.message}`);
+ d[col.id][i] = null;
+ }
}
}
}
}
}
Likely invalid or redundant comment.
o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), | ||
) ?? [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
10000The reason will be displayed to describe this comment to others. Learn more.
Correct the string comparison logic in the suggestion filter
There seems to be an extra closing brace '}' in the string comparison, which may lead to incorrect filtering of suggestions.
Update the comparison to ensure accurate matching of the query with the option title.
Apply this diff to fix the issue:
return (
props.options?.filter(
(o) =>
- o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(),
+ o.title?.toLowerCase()?.includes(query.toLowerCase()) || `{${o.title?.toLowerCase()}}` === query.toLowerCase(),
) ?? []
)
This change ensures that when the user types an exact field placeholder (e.g., {fieldname}
), it matches the options correctly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(), | |
) ?? [] | |
o.title?.toLowerCase()?.includes(query.toLowerCase()) || `{${o.title?.toLowerCase()}}` === query.toLowerCase(), | |
) ?? [] |
packages/nocodb/src/models/Column.ts
Outdated
case UITypes.LongText: { | ||
if (column.meta?.[LongTextAiMetaProp] === true) { | ||
insertArr.push({ | ||
fk_model_id: column.fk_model_id, | ||
fk_column_id: column.id, | ||
fk_integration_id: column.fk_integration_id, | ||
model: column.model, | ||
prompt: column.prompt, | ||
prompt_raw: column.prompt_raw, | ||
error: column.error, | ||
}); | ||
} | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for required AI column fields
The bulk insert for AI columns should validate required fields before insertion.
Add validation:
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ const requiredFields = ['fk_model_id', 'fk_column_id', 'model'];
+ const missingFields = requiredFields.filter(field => !column[field]);
+ if (missingFields.length > 0) {
+ throw new Error(`Missing required fields for AI column: ${missingFields.join(', ')}`);
+ }
insertArr.push({
fk_model_id: column.fk_model_id,
fk_column_id: column.id,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
});
}
break;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
case UITypes.LongText: { | |
if (column.meta?.[LongTextAiMetaProp] === true) { | |
insertArr.push({ | |
fk_model_id: column.fk_model_id, | |
fk_column_id: column.id, | |
fk_integration_id: column.fk_integration_id, | |
model: column.model, | |
prompt: column.prompt, | |
prompt_raw: column.prompt_raw, | |
error: column.error, | |
}); | |
} | |
break; | |
} | |
case UITypes.LongText: { | |
if (column.meta?.[LongTextAiMetaProp] === true) { | |
const requiredFields = ['fk_model_id', 'fk_column_id', 'model']; | |
const missingFields = requiredFields.filter(field => !column[field]); | |
if (missingFields.length > 0) { | |
throw new Error(`Missing required fields for AI column: ${missingFields.join(', ')}`); | |
} | |
insertArr.push({ | |
fk_model_id: column.fk_model_id, | |
fk_column_id: column.id, | |
fk_integration_id: column.fk_integration_id, | |
model: column.model, | |
prompt: column.prompt, | |
prompt_raw: column.prompt_raw, | |
error: column.error, | |
}); | |
} | |
break; | |
} |
a.uidt === UITypes.Barcode || | ||
isAIPromptCol(a)) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation and error handling for AI prompt column import
The AI prompt column import lacks validation and error handling. Consider adding checks for required fields and error handling.
} else if (isAIPromptCol(col)) {
if (base.fk_workspace_id !== colOptions.fk_workspace_id) {
colOptions.fk_workspace_id = null;
colOptions.fk_integration_id = null;
colOptions.model = null;
}
+ // Validate required fields
+ if (!colOptions.prompt_raw) {
+ this.logger.warn(`Skipping AI prompt column import: Missing prompt_raw for column ${col.title}`);
+ continue;
+ }
+ try {
const freshModelData = await this.columnsService.columnAdd(context, {
tableId: getIdOrExternalId(getParentIdentifier(col.id)),
column: withoutId({
...flatCol,
...{
fk_integration_id: colOptions.fk_integration_id,
model: colOptions.model,
prompt_raw: colOptions.prompt_raw,
},
}) as any,
req: param.req,
user: param.user,
});
+ } catch (error) {
+ this.logger.error(`Failed to import AI prompt column ${col.title}: ${error.message}`);
+ throw error;
+ }
Also applies to: 1281-1296
e7a3f81
to
126223b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (6)
packages/nc-gui/components/smartsheet/column/ButtonOptions.vue (2)
Line range hint
174-189
: Consider enhancing the error message for formula_raw validationThe validation logic is well-structured, but the error message for
formula_raw
could be more descriptive to better guide users.formula_raw: [ { required: true, - message: 'Prompt required for AI Button', + message: 'Please provide a prompt template for the AI Button to generate data', }, ],
Line range hint
212-216
: Consider extracting default values into constantsThe default values for AI button type should be extracted into named constants at the top of the file for better maintainability and reusability.
+const AI_BUTTON_DEFAULTS = { + THEME: 'light', + LABEL: 'Generate data', + COLOR: 'purple', + ICON: 'ncAutoAwesome', +} as const if (vModel.value.type === ButtonActionsType.Ai) { - vModel.value.theme = 'light' - vModel.value.label = 'Generate data' - vModel.value.color = 'purple' - vModel.value.icon = 'ncAutoAwesome' + vModel.value.theme = AI_BUTTON_DEFAULTS.THEME + vModel.value.label = AI_BUTTON_DEFAULTS.LABEL + vModel.value.color = AI_BUTTON_DEFAULTS.COLOR + vModel.value.icon = AI_BUTTON_DEFAULTS.ICON vModel.value.output_column_ids = '' }packages/nc-gui/composables/useNocoAi.ts (1)
Line range hint
1-367
: Consider improving error handling consistencyThe file contains multiple functions with similar error handling patterns. Consider:
- Centralizing error messages into constants
- Creating a common error handling utility function
- Standardizing the error response format across all AI operations
This would improve maintainability and ensure consistent error handling throughout the codebase.
packages/nc-gui/composables/useIntegrationsStore.ts (2)
Line range hint
447-496
: Consider breaking down the loadDynamicIntegrations functionThe function handles multiple responsibilities. Consider extracting the icon processing logic into a separate function for better maintainability.
+ const processIntegrationIcon = (meta: { icon?: string, title?: string }, subType: string) => { + if (!meta.icon) return iconMap.puzzle + + if (meta.icon in iconMap) { + return iconMap[meta.icon as keyof typeof iconMap] + } + + if (isValidURL(meta.icon)) { + return h('img', { + src: meta.icon, + alt: meta.title || subType, + }) + } + + return iconMap.puzzle + } const loadDynamicIntegrations = async () => { if (integrationsInitialized.value) return integrationsInitialized.value = true const dynamicIntegrations = (await $api.integrations.list()) as { type: IntegrationsType subType: string meta: { title?: string icon?: string description?: string order?: number } }[] dynamicIntegrations.sort((a, b) => (a.meta.order ?? Infinity) - (b.meta.order ?? Infinity)) for (const di of dynamicIntegrations) { - let icon: FunctionalComponent<SVGAttributes, {}, any, {}> | VNode - if (di.meta.icon) { - if (di.meta.icon in iconMap) { - icon = iconMap[di.meta.icon as keyof typeof iconMap] - } else { - if (isValidURL(di.meta.icon)) { - icon = h('img', { - src: di.meta.icon, - alt: di.meta.title || di.subType, - }) - } - } - } else { - icon = iconMap.puzzle - } + const icon = processIntegrationIcon(di.meta, di.subType) const integration: IntegrationItemType = { title: di.meta.title || di.subType, subType: di.subType, icon, type: di.type, isAvailable: true, dynamic: true, } allIntegrations.push(integration) integrationsRefreshKey.value++ } }
296-296
: Use optional chaining for safer property accessAs suggested by static analysis, use optional chaining to safely access potentially undefined properties.
- if (response && response?.id) { + if (response?.id) {🧰 Tools
🪛 Biome (1.9.4)
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nc-gui/components/smartsheet/grid/Table.vue (1)
Line range hint
879-910
: Add error handling for output column parsingWhile the changes add support for multiple output columns, consider adding validation and error handling:
- Validate that output column IDs exist in the table
- Handle potential parsing errors when splitting comma-separated IDs
- Add error handling for empty or invalid output column configurations
if (isAiButton(field)) { + // Validate output column configuration + if (!field.colOptions?.output_column_ids) { + NcError.badRequest('Output columns not configured for AI button'); + } + + try { outputColumnIds = ncIsString(field.colOptions?.output_column_ids) && field.colOptions.output_column_ids.split(',').length > 0 ? field.colOptions.output_column_ids.split(',') : []; + + // Validate that output columns exist + for (const colId of outputColumnIds) { + if (!meta.value?.columns?.find(c => c.id === colId)) { + NcError.badRequest(`Output column ${colId} not found in table`); + } + } + } catch (e) { + NcError.badRequest('Invalid output column configuration'); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (70)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(11 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (43)
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nocodb/src/models/index.ts
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/utils/commonUtils.ts
- packages/nocodb-sdk/src/lib/helperFunctions.ts
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nocodb-sdk/src/lib/UITypes.ts
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nocodb/src/models/Integration.ts
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/components/smartsheet/header/Menu.vue
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/cell/attachment/index.vue
- packages/nc-gui/components/ai/Settings.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nc-gui/components/general/Loader.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nc-gui/components/ai/PromptWithFields.vue
- packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
- packages/nc-gui/utils/columnUtils.ts
🧰 Additional context used
📓 Learnings (1)
packages/nc-gui/components/smartsheet/grid/GroupByTable.vue (1)
Learnt from: DarkPhoenix2704
PR: nocodb/nocodb#8781
File: packages/nc-gui/components/smartsheet/grid/GroupBy.vue:225-232
Timestamp: 2024-11-12T03:23:04.401Z
Learning: DarkPhoenix2704 prefers to keep code implementations that prioritize readability, even if an alternative might be more concise.
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (52)
packages/nc-gui/components/smartsheet/Kanban.vue (2)
1146-1146
: LGTM: Icon update for consistency
The icon change from expand
to maximize
aligns with similar updates in other components, maintaining UI consistency across the application.
Line range hint 1-1146
: LGTM: Clean implementation of empty stack visibility
The hideEmptyStack
computed property is well-implemented, providing a clean way to toggle empty stack visibility with proper default handling.
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
: 🛠️ Refactor suggestion
Fix undefined ncDelay
function and improve user feedback timing
-
The
ncDelay
function is still undefined and needs to be imported. -
The implementation can be improved:
- 5 seconds might be too long for user feedback
- Error handling should be added
Consider this implementation:
- await ncDelay(5000)
+ try {
+ // Use setTimeout for a shorter 2-second feedback
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+ } catch (e) {
+ console.error('Error in copy feedback timer:', e)
+ } finally {
+ isViewIdCopied.value = false
+ }
Alternatively, if you prefer using the utility function:
+import { ncDelay } from '#imports'
// ... rest of the imports
- await ncDelay(5000)
+ try {
+ // Reduce to 2 seconds for better UX
+ await ncDelay(2000)
+ } catch (e) {
+ console.error('Error in copy feedback timer:', e)
+ } finally {
+ isViewIdCopied.value = false
+ }
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (4)
Line range hint 1-7
: LGTM: Clean import addition
The addition of isAIPromptCol
import is clean and follows the existing pattern of importing type checks from nocodb-sdk.
181-183
: LGTM: Proper column filtering
The addition of !isAIPromptCol(a)
in the column filtering logic is appropriate, ensuring AI prompt columns are handled separately like other special column types.
1113-1114
: LGTM: Consistent column type handling
The addition of isAIPromptCol(a)
to the referenced column set check is consistent with how other special column types are handled.
1281-1296
: Add validation for AI prompt column import
The AI prompt column import implementation follows the same pattern as button columns but lacks validation for required fields.
This was previously noted in a review comment. The suggestion to add validation for required fields and error handling remains valid.
packages/nc-gui/composables/useNocoAi.ts (4)
20-21
: LGTM: Type change improves flexibility
The change from IntegrationType[]
to Partial<IntegrationType>[]
is a good improvement that allows for more flexible integration definitions where not all fields are required.
22-23
: LGTM: Clean computed property implementation
The computed property provides a clean way to determine AI integration availability based on the array length. This is an improvement over manual state management.
347-347
: LGTM: Proper method export
The new method is correctly exported in the composable's return object.
24-28
:
Fix computed property access and missing import
There are two issues in this method:
- The computed property
aiIntegrationAvailable
is used without accessing its.value
- The
ncIsArrayIncludes
helper function appears to be used without being imported
+ import { ncIsArrayIncludes } from '<path-to-helper>'
const isAiIntegrationAvailableInList = (integrationId?: string) => {
- if (!aiIntegrationAvailable) return false
+ if (!aiIntegrationAvailable.value) return false
return ncIsArrayIncludes(aiIntegrations.value, integrationId, 'id')
}
packages/nc-gui/composables/useIntegrationsStore.ts (3)
93-94
: LGTM: Clean integration with AI-specific state management
Good separation of concerns by using a dedicated composable for AI integrations.
191-192
: LGTM: Comprehensive event tracking implementation
Event tracking has been consistently implemented across all major integration operations, providing valuable analytics data.
Also applies to: 224-225, 253-254, 287-291
229-238
:
Avoid mutating objects within map operations
The current implementation mutates objects within map operations, which can lead to reactivity issues in Vue.js. Return new objects instead.
Apply these fixes:
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.title = integration.title
- return i
+ return { ...i, title: integration.title }
}
return i
})
}
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) =>
i.id === integration.id
? { ...i, is_default: true }
: { ...i, is_default: false }
)
}
Also applies to: 258-269
packages/nocodb/src/db/BaseModelSqlv2.ts (3)
10071-10075
:
Correct the type of isStale
to boolean
The isStale
property is used as a boolean in the code. Currently, it's typed as string?
, which can lead to type mismatches. It should be typed as boolean?
to reflect its usage.
Apply this diff to fix the type:
const obj: {
value?: string;
lastModifiedBy?: string;
lastModifiedTime?: string;
- isStale?: string;
+ isStale?: boolean;
} = {};
Likely invalid or redundant comment.
5721-5723
:
Ensure prepareNocoData
is properly awaited inside map
The prepareNocoData
function is asynchronous, but its calls inside insertDatas.map
are not being awaited. This can lead to unhandled Promise rejections and unexpected behavior.
Apply this diff to fix the issue:
- insertDatas.map((d) =>
- this.prepareNocoData(d, true, cookie, null, { raw }),
- ),
+ await Promise.all(
+ insertDatas.map((d) =>
+ this.prepareNocoData(d, true, cookie, null, { raw }),
+ ),
+ );
Likely invalid or redundant comment.
8635-8653
: 🛠️ Refactor suggestion
Improve error handling for JSON parsing
The JSON parsing logic has minimal error handling with empty catch blocks that could hide important errors. Additionally, there's no validation of the parsed data structure.
Apply this diff to improve error handling:
if (d) {
for (const col of jsonColumns) {
if (d[col.id] && typeof d[col.id] === 'string') {
try {
d[col.id] = JSON.parse(d[col.id]);
+ // Validate expected data structure
+ if (typeof d[col.id] !== 'object') {
+ throw new Error('Invalid JSON structure');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON for column ${col.id}: ${e.message}`);
+ d[col.id] = null; // Set to null on parse error
+ }
}
if (d[col.id]?.length) {
for (let i = 0; i < d[col.id].length; i++) {
if (typeof d[col.id][i] === 'string') {
try {
d[col.id][i] = JSON.parse(d[col.id][i]);
+ // Validate array element structure
+ if (typeof d[col.id][i] !== 'object') {
+ throw new Error('Invalid JSON array element');
+ }
- } catch {}
+ } catch (e) {
+ logger.warn(`Error parsing JSON array element: ${e.message}`);
+ d[col.id][i] = null;
+ }
}
}
}
}
}
Likely invalid or redundant comment.
packages/nocodb/src/models/AIColumn.ts (3)
14-16
: Ensure correct instantiation in the castType
method
The castType
method might return undefined
when data
is falsy, which could lead to unexpected behavior. It's better to explicitly handle falsy values to maintain consistent return types.
Consider changing the return statement to:
- return data && new AIColumn(data);
+ return data ? new AIColumn(data) : null;
26-26
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. Referencing the class name directly improves readability.
Apply this diff to fix the issue:
- return this._insert(
+ return AIColumn._insert(
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
40-40
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. Referencing the class name directly improves readability.
Apply this diff to fix the issue:
- return this._update(
+ return AIColumn._update(
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. Referencing the class name directly improves readability.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
85-85
: Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. Referencing the class name directly improves readability.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
135-149
:
Avoid string concatenation in SQL queries to prevent SQL injection
In the MSSQL query, string concatenation is used to build the JSON string, which can lead to SQL injection vulnerabilities. Even when using parameter bindings, concatenating SQL strings can introduce security risks.
Consider constructing the JSON object in code and passing it as a parameter to the SQL query. This approach avoids string concatenation within the SQL statement.
For example, you can construct the JSON string in JavaScript and then use JSON_QUERY(?)
in your SQL statement:
- await sqlClient.raw(
- `UPDATE ??
- SET ?? = JSON_QUERY('{"value":' + ?? + ',"lastModifiedBy":' + ? + ',"lastModifiedTime":' + ? + ',"isStale":' + ? + '}')
- WHERE ?? is not null;`,
- [
-
10000
baseModel.getTnPath(table.table_name),
- column.column_name,
- column.column_name,
- commonRecord.lastModifiedBy.toString(),
- commonRecord.lastModifiedTime.toString(),
- commonRecord.isStale,
- column.column_name,
- ],
- );
+ const jsonData = JSON.stringify({
+ value: sqlClient.raw('??', [column.column_name]),
+ lastModifiedBy: commonRecord.lastModifiedBy.toString(),
+ lastModifiedTime: commonRecord.lastModifiedTime.toString(),
+ isStale: commonRecord.isStale,
+ });
+
+ await sqlClient.raw(
+ `UPDATE ??
+ SET ?? = JSON_QUERY(?)
+ WHERE ?? is not null;`,
+ [
+ baseModel.getTnPath(table.table_name),
+ column.column_name,
+ jsonData,
+ column.column_name,
+ ],
+ );
This ensures that all parameters are properly escaped and reduces the risk of SQL injection.
Likely invalid or redundant comment.
packages/nc-gui/components/cell/AI.vue (2)
81-92
: Consider adding error handling for AI response processing
The code processes AI responses without explicit error handling. If the response format is unexpected, it could lead to runtime errors.
Consider adding try-catch blocks to handle potential errors:
if (obj && typeof obj === 'object') {
+ try {
vModel.value = obj
setTimeout(() => {
isAiEdited.value = false
}, 100)
+ } catch (error) {
+ console.error('Error processing AI response:', error)
+ // Optionally, show a user-friendly error message to the user
+ }
} else {
+ try {
vModel.value = {
...(ncIsObject(vModel.value) ? vModel.value : {}),
isStale: false,
value: resRow[column.value.title!],
}
+ } catch (error) {
+ console.error('Error setting AI response value:', error)
+ // Optionally, show a user-friendly error message to the user
+ }
}
131-135
: Fix the tooltip disabled logic
The :disabled
prop on NcTooltip
seems to have incorrect logic. The tooltip should be disabled when the button is enabled, not when it's disabled.
Consider updating the :disabled
prop to reflect the inverse of isDisabledAiButton
:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
This change ensures the tooltip is enabled when the button is disabled, providing users with feedback on why the button is not clickable.
packages/nocodb/src/db/sortV2.ts (1)
203-221
: Remove duplicate condition check
The isAIPromptCol(column)
condition is checked twice unnecessarily within the same block. This redundancy can be removed to simplify the code.
Apply this fix to eliminate the duplicate condition:
case UITypes.LongText: {
if (isAIPromptCol(column)) {
let col;
- if (isAIPromptCol(column)) {
if (knex.clientType() === 'pg') {
col = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [
column.column_name,
]);
} else if (knex.clientType().startsWith('mysql')) {
col = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [
column.column_name,
]);
} else if (knex.clientType() === 'sqlite3') {
col = knex.raw(`json_extract(??, '$.value')`, [
column.column_name,
]);
} else if (knex.clientType() === 'mssql') {
col = knex.raw(`JSON_VALUE(??, '$.value')`, [column.column_name]);
}
- }
qb.orderBy(col, sort.direction || 'asc', nulls);
} else {
qb.orderBy(
sanitize(column.column_name),
sort.direction || 'asc',
nulls,
);
}
break;
}
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (4)
1-48
: Well-structured component initialization!
The component follows Vue 3 best practices with proper TypeScript integration, clean event handling, and efficient computed properties.
170-324
: Well-structured template with proper accessibility!
The template section follows Vue best practices with clean conditional rendering and proper use of UI components.
48-58
:
Add null check for meta property
Direct access to vModel.value.meta
without a null check could cause runtime errors.
Apply this diff to add the necessary null check:
set: (value: boolean) => {
+ if (!vModel.value.meta) vModel.value.meta = {}
vModel.value.meta[LongTextAiMetaProp] = value
vModel.value.prompt_raw = ''
previewRow.value.row = {}
isAlreadyGenerated.value = false
},
Likely invalid or redundant comment.
66-104
:
Add error handling for generate function
The generate
function makes an API call without error handling, which could lead to unhandled promise rejections.
Apply this diff to add error handling:
const generate = async () => {
generatingPreview.value = true
await loadViewData()
const pk = formattedData.value.length ? extractPkFromRow(unref(formattedData.value[0].row), meta.value?.columns || []) : ''
if (!formattedData.value.length || !pk) {
message.error('Include at least 1 sample record in table to generate')
generatingPreview.value = false
return
}
previewFieldTitle.value = vModel.value?.title || 'temp_title'
+ try {
const res = await generateRows(
meta.value.id!,
{
title: previewFieldTitle.value,
prompt_raw: vModel.value.prompt_raw,
fk_integration_id: vModel.value.fk_integration_id,
uidt: UITypes.LongText,
},
[pk],
)
if (res?.length && res[0]?.[previewFieldTitle.value]) {
previewRow.value.row = {
...res[0],
[previewFieldTitle.value]: {
value: res[0]?.[previewFieldTitle.value],
},
}
isAlreadyGenerated.value = true
}
+ } catch (error: any) {
+ message.error(`Failed to generate text: ${error.message}`)
+ } finally {
generatingPreview.value = false
+ }
}
Likely invalid or redundant comment.
packages/nc-gui/components/cell/TextArea.vue (3)
Line range hint 426-439
: Well-implemented AI warning message section!
The warning message implementation provides clear user feedback with proper styling and iconography.
541-545
:
Fix invalid Tailwind animation class
The class animate-infinite
is not a valid Tailwind CSS utility. The animate-spin
class already provides an infinite animation.
Apply this diff to fix the animation:
<GeneralIcon
icon="refresh"
class="transform group-hover:(!text-grey-800) text-gray-700 w-3 h-3"
- :class="{ 'animate-infinite animate-spin': isAiGenerating }"
+ :class="{ 'animate-spin': isAiGenerating }"
/>
Likely invalid or redundant comment.
100-111
:
Replace non-null assertions with safe null checks
The use of non-null assertions (!
) in computed properties could lead to runtime errors if the values are undefined.
Apply this diff to add proper null checks:
const rowId = computed(() => {
- return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
+ if (!currentRow.value?.row || !meta.value?.columns) return null
+ return extractPkFromRow(currentRow.value.row, meta.value.columns)
})
const isAiGenerating = computed(() => {
return !!(
rowId.value &&
column?.value?.id &&
generatingRows.value?.includes(rowId.value) &&
generatingColumnRows.value?.includes(column.value.id)
)
})
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/expanded-form/index.vue (1)
330-333
: Clean implementation of URL copy feedback!
The implementation properly handles the copy feedback state with a reasonable timeout duration.
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue (2)
369-369
: LGTM: Added isEdit prop to AiSettings component
The prop addition allows proper configuration based on edit mode state.
471-475
: LGTM: Added tooltips for output field titles
The tooltips improve UX by showing full field titles when truncated.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (4)
186-196
: LGTM: Added AIPrompt handling in UI filters
The hideAiPromptOnEdit condition correctly prevents editing AI prompt columns in edit mode.
Line range hint 263-285
: LGTM: Enhanced onSelectType with AIPrompt support
The function now properly handles AIPrompt type by setting the correct UIType and metadata.
625-627
: LGTM: Added isAiPromptSelectOption helper
Clean implementation of the helper method to check if a column is an AI prompt.
1098-1104
: LGTM: Added dynamic icon handling for AI columns
The icon selection logic properly handles both AI button and AI prompt types.
packages/nocodb/src/db/conditionV2.ts (2)
4-4
: LGTM: Added isAIPromptCol import
Clean import of the utility function from nocodb-sdk.
596-613
:
Add error handling for JSON extraction
The JSON extraction logic should handle invalid JSON values to prevent runtime errors.
Add error handling with this diff:
if (isAIPromptCol(column)) {
+ const handleInvalidJson = (field) => {
+ return knex.raw('CASE WHEN JSON_VALID(??) THEN ? ELSE NULL END', [
+ column.column_name,
+ field
+ ]);
+ };
if (knex.clientType() === 'pg') {
- field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name])
+ );
} else if (knex.clientType().startsWith('mysql')) {
- field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name])
+ );
} // Apply similar changes for other DB types
}
Likely invalid or redundant comment.
packages/nocodb/src/models/Column.ts (4)
452-469
: LGTM! Clean implementation of AI column insertion
The AIColumn insertion logic is well-structured and follows the pattern used for other column types.
573-577
: LGTM! Clean implementation of AI column options retrieval
The getColOptions implementation for AI columns is clean and consistent with other column types.
1961-1974
: 🛠️ Refactor suggestion
Add validation for required AI column fields
The bulk insert for AI columns should validate required fields before insertion.
Add validation:
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ const requiredFields = ['fk_model_id', 'fk_column_id', 'model'];
+ const missingFields = requiredFields.filter(field => !column[field]);
+ if (missingFields.length > 0) {
+ throw new Error(`Missing required fields for AI column: ${missingFields.join(', ')}`);
+ }
insertArr.push({
fk_model_id: column.fk_model_id,
fk_column_id: column.id,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
});
}
break;
}
Likely invalid or redundant comment.
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect. It should check for the actual column ID to detect whether the prompt includes the deleted column ID.
Apply this diff to fix the placeholder matching:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
Likely invalid or redundant comment.
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nc-gui/components/smartsheet/details/Fields.vue (1)
1040-1043
:
Undefined function ncDelay
The function ncDelay
used in this code is not defined or imported, which will lead to a runtime error. Please ensure that ncDelay
is properly defined or consider using an alternative.
Apply this diff to fix the issue by using the delay
function from @vueuse/core
:
+import { delay } from '@vueuse/core'
await copy(field.id!)
isFieldIdCopied.value = true
-await ncDelay(5000)
+await delay(5000)
isFieldIdCopied.value = false
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue (1)
1027-1028
: LGTM! Clean implementation of AI column type checking
The isSelectedOnlyAI computed property cleanly handles both AI prompt columns and AI button columns.
packages/nc-gui/components/smartsheet/grid/Table.vue (1)
855-856
: LGTM! Improved AI column type checking
The changes improve code maintainability by using dedicated type checking functions isAIPromptCol
and isAiButton
instead of direct property checks.
packages/nocodb/src/services/columns.service.ts (3)
1601-1645
:
Add security checks for prompt template processing
The prompt template processing needs additional security checks:
- Verify user permissions for referenced columns
- Sanitize input to prevent SQL injection
- Validate template complexity to prevent DoS attacks
if (colBody.prompt_raw) {
await table.getColumns(context);
+ // Validate template complexity
+ const MAX_TEMPLATE_LENGTH = 10000;
+ const MAX_PLACEHOLDERS = 50;
+ if (colBody.prompt_raw.length > MAX_TEMPLATE_LENGTH) {
+ NcError.badRequest(`Template exceeds maximum length of ${MAX_TEMPLATE_LENGTH}`);
+ }
+ if ((colBody.prompt_raw.match(/{/g) || []).length > MAX_PLACEHOLDERS) {
+ NcError.badRequest(`Template exceeds maximum number of placeholders (${MAX_PLACEHOLDERS})`);
+ }
prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
const column = table.columns.find((c) => c.title === p1);
if (!column) {
NcError.badRequest(`Field '${p1}' not found`);
}
+ // Verify user has permission to access the column
+ if (!this.hasColumnPermission(context, column, user)) {
+ NcError.forbidden(`No access to field '${p1}'`);
+ }
+ // Sanitize column reference
+ const sanitizedId = this.sanitizeColumnId(column.id);
+ return `{${sanitizedId}}`;
});
}
2282-2304
: 🛠️ Refactor suggestion
Extract common prompt template processing logic
The prompt template processing logic is duplicated between column update and add operations. Consider extracting it into a shared method.
+ private async processPromptTemplate(
+ context: NcContext,
+ table: Model,
+ promptRaw: string,
+ user: UserType
+ ): Promise<string> {
+ await table.getColumns(context);
+
+ // Validate template format
+ const MAX_TEMPLATE_LENGTH = 10000;
+ const MAX_PLACEHOLDERS = 50;
+ if (promptRaw.length > MAX_TEMPLATE_LENGTH) {
+ NcError.badRequest(`Template exceeds maximum length of ${MAX_TEMPLATE_LENGTH}`);
+ }
+ if ((promptRaw.match(/{/g) || []).length > MAX_PLACEHOLDERS) {
+ NcError.badRequest(`Template exceeds maximum number of placeholders (${MAX_PLACEHOLDERS})`);
+ }
+
+ return promptRaw.replace(/{(.*?)}/g, (match, p1) => {
+ const column = table.columns.find((c) => c.title === p1);
+ if (!column) {
+ NcError.badRequest(`Field '${p1}' not found`);
+ }
+ if (!this.hasColumnPermission(context, column, user)) {
+ NcError.forbidden(`No access to field '${p1}'`);
+ }
+ return `{${this.sanitizeColumnId(column.id)}}`;
+ });
+ }
if (isAIPromptCol(colBody)) {
let prompt = '';
if (colBody.prompt_raw) {
- await table.getColumns(context);
- prompt = colBody.prompt_raw.replace(/{(.*?)}/g, (match, p1) => {
- const column = table.columns.find((c) => c.title === p1);
- if (!column) {
- NcError.badRequest(`Field '${p1}' not found`);
- }
- return `{${column.id}}`;
- });
+ prompt = await this.processPromptTemplate(context, table, colBody.prompt_raw, user);
}
colBody.prompt = prompt;
}
1578-1600
: 🛠️ Refactor suggestion
Add transaction handling for AI to non-AI conversion
The conversion from AI to non-AI format should be wrapped in a transaction to maintain data consistency.
if (
isAIPromptCol(column) &&
(colBody.uidt !== UITypes.LongText ||
(colBody.uidt === UITypes.LongText &&
colBody.meta?.[LongTextAiMetaProp] !== true))
) {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
Model.getBaseModelSQL(context, {
id: table.id,
dbDriver: await reuseOrSave('dbDriver', reuse, async () =>
NcConnectionMgrv2.get(source),
),
}),
);
+ // Start transaction
+ const trx = await sqlClient.knex.transaction();
+ try {
await convertAIRecordTypeToValue({
source,
table,
column,
baseModel,
sqlClient,
+ trx,
});
+ await trx.commit();
+ } catch (error) {
+ await trx.rollback();
+ NcError.badRequest(`Failed to convert AI column: ${error.message}`);
+ }
}
Likely invalid or redundant comment.
packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
Outdated
Show resolved
Hide resolved
126223b
to
62d57b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (13)
packages/nc-gui/utils/commonUtils.ts (1)
103-117
: Consider performance optimization for large datasets.While the implementation is correct and handles edge cases well, consider caching the lowercase query string outside the array iteration for better performance with large arrays.
Here's an optimized version:
export const searchCompare = (source?: string | number | (string | number | undefined)[], query?: string): boolean => { + const normalizedQuery = (query || '').toLowerCase() if (ncIsArray(source)) { return source.some((item) => { return (item || '') .toString() .toLowerCase() - .includes((query || '').toLowerCase()) + .includes(normalizedQuery) }) } return (source || '') .toString() .toLowerCase() - .includes((query || '').toLowerCase()) + .includes(normalizedQuery) }packages/nc-gui/components/cell/attachment/index.vue (3)
Line range hint
89-116
: Refactor the watch handler for better error handling and DRY codeThe current implementation has duplicated logic for resetting arrays. Consider refactoring to reduce duplication and improve error handling.
watch( () => modelValue, async (nextModel) => { + const resetArrays = () => { + if (isPublic.value && isForm.value) { + storedFiles.value = [] + } else { + attachments.value = [] + } + } + if (nextModel) { try { const nextAttachments = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean) - if (isPublic.value && isForm.value) { storedFiles.value = nextAttachments } else { attachments.value = nextAttachments } } catch (e) { - console.error(e) + console.error('Failed to parse attachment data:', e) - if (isPublic.value && isForm.value) { - storedFiles.value = [] - } else { - attachments.value = [] - } + resetArrays() } } else { - if (isPublic.value && isForm.value) { - storedFiles.value = [] - } else { - attachments.value = [] - } + resetArrays() } }, { immediate: true, }, )
Line range hint
186-201
: Enhance type safety and event handling in keyboard interactionsThe keyboard event handlers could benefit from more specific TypeScript types and improved event handling.
-const keydownEnter = (e: KeyboardEvent) => { +const keydownEnter = (e: KeyboardEvent & { currentTarget: HTMLElement }) => { if (!isSurveyForm.value) { open(e) e.stopPropagation() + e.preventDefault() // Prevent form submission } } -const keydownSpace = (e: KeyboardEvent) => { +const keydownSpace = (e: KeyboardEvent & { currentTarget: HTMLElement }) => { if (isSurveyForm.value) { open(e) e.stopPropagation() + e.preventDefault() // Prevent page scroll } }
427-427
: Enhance accessibility for the maximize buttonWhile replacing the expand icon with maximize icon, consider adding ARIA attributes for better accessibility.
- <component :is="iconMap.maximize" v-else class="transform group-hover:(!text-grey-800) text-gray-700 w-3 h-3" /> + <component + :is="iconMap.maximize" + v-else + class="transform group-hover:(!text-grey-800) text-gray-700 w-3 h-3" + role="img" + aria-label="Maximize attachment view" + />packages/nc-gui/utils/columnUtils.ts (2)
27-32
: Consider adding JSDoc documentation for the new AIPrompt typeWhile the implementation is correct, adding documentation would help other developers understand the purpose and usage of this new UI type.
+/** + * AIPrompt type for AI-assisted text generation + * @property {string} name - The type identifier + * @property {FunctionalComponent} icon - The icon component + * @property {number} isNew - Flag indicating this is a new feature + * @property {number} deprecated - Flag indicating deprecation status + */ { name: AIPrompt, icon: iconMap.cellAi, isNew: 1, deprecated: 0, },
269-277
: Consider making the tooltip message more specificThe default tooltip message 'msg.invalidColumnConfiguration' is generic. Consider making it more descriptive or adding context about what aspect of the configuration is invalid.
const result = { isInvalid: false, - tooltip: 'msg.invalidColumnConfiguration', + tooltip: 'msg.pleaseConfigureColumn', }packages/nc-gui/composables/useIntegrationsStore.ts (1)
Line range hint
447-496
: Add null checks and improve type safetyThe function handles dynamic integrations loading well, but there are a few improvements that could be made:
- Use optional chaining for response?.id check
- Add type safety for the iconMap access
Apply these improvements:
- if (response && response?.id) { + if (response?.id) { - if (di.meta.icon in iconMap) { + if (di.meta.icon && di.meta.icon in iconMap) {Also, consider adding error handling for the API call:
+ try { const dynamicIntegrations = (await $api.integrations.list()) as { type: IntegrationsType subType: string meta: { title?: string icon?: string description?: string order?: number } }[] + } catch (e) { + console.error('Failed to load dynamic integrations:', e) + return + }packages/nc-gui/components/smartsheet/grid/GroupByTable.vue (3)
Line range hint
279-297
: Document the ESLint directive and new propThe code functionality looks correct, but could benefit from additional documentation:
- Add a comment explaining why the ESLint rule needs to be disabled
- Document the
call-add-empty-row
prop's purpose and usage
Line range hint
279-324
: Consider caching the expanded row indexThe
getExpandedRowIndex()
is called multiple times in computed properties and navigation methods. Consider caching this value when the route changes to improve performance.+ const expandedRowIndex = ref(-1) + + watch(() => routeQuery.value.rowId, () => { + expandedRowIndex.value = formattedData.value.findIndex( + (row: Row) => routeQuery.value.rowId === extractPkFromRow(row.row, meta.value?.columns as ColumnType[]) + ) + }) + const isLastRow = computed(() => { - const currentIndex = getExpandedRowIndex() + const currentIndex = expandedRowIndex.value return vGroup.value.paginationData?.isLastPage && currentIndex === formattedData.value.length - 1 })
Line range hint
279-324
: Enhance error handling in deleteSelectedRowsWrapperThe function should handle potential errors during deletion and data reload operations.
async function deleteSelectedRowsWrapper() { if (!deleteSelectedRows) return + try { await deleteSelectedRows() // reload table data await reloadTableData({ shouldShowLoading: false }) + } catch (error) { + console.error('Failed to delete selected rows:', error) + message.error(t('msg.error.deleteRecord')) + } }packages/nc-gui/components/smartsheet/column/ButtonOptions.vue (1)
Line range hint
174-212
: LGTM! Consider localizing error messages.The validation rules and default values for AI button type are well-structured. However, the error message "At least one output field is required for AI Button" should use the i18n translation system for consistency.
- message: 'At least one output field is required for AI Button', + message: t('msg.error.aiButtonOutputFieldRequired'),packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (1)
Line range hint
263-285
: LGTM! Consider enhancing type safety.The onSelectType function properly handles AI prompt type selection. However, consider using a type union for better type safety.
- const UITypes | typeof AIButton | typeof AIPrompt, fromSearchList = false) + type SelectableType = UITypes | typeof AIButton | typeof AIPrompt + const SelectableType, fromSearchList = false)packages/nocodb/src/db/BaseModelSqlv2.ts (1)
5721-5724
: Improve error handling in Promise.allThe Promise.all usage for raw data preparation is good for parallel processing, but consider adding error handling for individual promise rejections.
- insertDatas.map( - async (d) => - await this.prepareNocoData(d, true, cookie, null, { raw }), - ), + insertDatas.map(async (d) => { + try { + return await this.prepareNocoData(d, true, cookie, null, { raw }); + } catch (e) { + logger.error(`Error preparing data: ${e.message}`); + throw e; + } + }),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (70)
packages/nc-gui/components/ai/PromptWithFields.vue
(8 hunks)packages/nc-gui/components/ai/Settings.vue
(9 hunks)packages/nc-gui/components/cell/AI.vue
(8 hunks)packages/nc-gui/components/cell/TextArea.vue
(11 hunks)packages/nc-gui/components/cell/attachment/index.vue
(1 hunks)packages/nc-gui/components/general/IntegrationIcon.vue
(1 hunks)packages/nc-gui/components/general/Loader.vue
(1 hunks)packages/nc-gui/components/nc/Button.vue
(6 hunks)packages/nc-gui/components/nc/List/RecordItem.vue
(1 hunks)packages/nc-gui/components/nc/Switch.vue
(2 hunks)packages/nc-gui/components/smartsheet/Cell.vue
(3 hunks)packages/nc-gui/components/smartsheet/Gallery.vue
(1 hunks)packages/nc-gui/components/smartsheet/Kanban.vue
(1 hunks)packages/nc-gui/components/smartsheet/PlainCell.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
(6 hunks)packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
(8 hunks)packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
(2 hunks)packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
(2 hunks)packages/nc-gui/components/smartsheet/details/Fields.vue
(1 hunks)packages/nc-gui/components/smartsheet/expanded-form/index.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
(1 hunks)packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
(4 hunks)packages/nc-gui/components/smartsheet/grid/Table.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/Cell.vue
(1 hunks)packages/nc-gui/components/smartsheet/header/CellIcon.ts
(1 hunks)packages/nc-gui/components/smartsheet/header/Menu.vue
(4 hunks)packages/nc-gui/components/smartsheet/header/VirtualCell.vue
(1 hunks)packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
(1 hunks)packages/nc-gui/components/virtual-cell/Button.vue
(1 hunks)packages/nc-gui/components/virtual-cell/HasMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/ManyToMany.vue
(1 hunks)packages/nc-gui/components/virtual-cell/components/ListItem.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
(0 hunks)packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
(2 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
(1 hunks)packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
(2 hunks)packages/nc-gui/components/workspace/project/AiCreateProject.vue
(2 hunks)packages/nc-gui/composables/useColumnCreateStore.ts
(2 hunks)packages/nc-gui/composables/useData.ts
(2 hunks)packages/nc-gui/composables/useInfiniteData.ts
(2 hunks)packages/nc-gui/composables/useIntegrationsStore.ts
(8 hunks)packages/nc-gui/composables/useMultiSelect/index.ts
(2 hunks)packages/nc-gui/composables/useNocoAi.ts
(3 hunks)packages/nc-gui/helpers/parsers/parserHelpers.ts
(2 hunks)packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
(3 hunks)packages/nc-gui/utils/cell.ts
(1 hunks)packages/nc-gui/utils/columnUtils.ts
(4 hunks)packages/nc-gui/utils/commonUtils.ts
(1 hunks)packages/nocodb-sdk/src/lib/UITypes.ts
(3 hunks)packages/nocodb-sdk/src/lib/globals.ts
(1 hunks)packages/nocodb-sdk/src/lib/helperFunctions.ts
(1 hunks)packages/nocodb-sdk/src/lib/index.ts
(1 hunks)packages/nocodb/src/db/BaseModelSqlv2.ts
(8 hunks)packages/nocodb/src/db/conditionV2.ts
(2 hunks)packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
(2 hunks)packages/nocodb/src/db/sortV2.ts
(2 hunks)packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
(3 hunks)packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
(1 hunks)packages/nocodb/src/models/AIColumn.ts
(1 hunks)packages/nocodb/src/models/Column.ts
(9 hunks)packages/nocodb/src/models/Integration.ts
(3 hunks)packages/nocodb/src/models/LongTextColumn.ts
(1 hunks)packages/nocodb/src/models/index.ts
(1 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
(2 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
(4 hunks)packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
(5 hunks)packages/nocodb/src/services/columns.service.ts
(4 hunks)packages/nocodb/src/utils/dataConversion.ts
(1 hunks)packages/nocodb/src/utils/globals.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
🚧 Files skipped from review as they are similar to previous changes (42)
- packages/nocodb-sdk/src/lib/globals.ts
- packages/nocodb-sdk/src/lib/index.ts
- packages/nocodb/src/models/index.ts
- packages/nc-gui/components/smartsheet/header/VirtualCell.vue
- packages/nocodb/src/meta/migrations/v2/nc_069_ai_prompt.ts
- packages/nc-gui/components/general/IntegrationIcon.vue
- packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
- packages/nc-gui/helpers/tiptapExtensions/mention/FieldList.vue
- packages/nc-gui/components/smartsheet/header/Cell.vue
- packages/nc-gui/components/virtual-cell/HasMany.vue
- packages/nc-gui/components/virtual-cell/Button.vue
- packages/nc-gui/components/workspace/project/AiCreateProject.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddCommon.vue
- packages/nc-gui/components/nc/Switch.vue
- packages/nc-gui/components/smartsheet/Gallery.vue
- packages/nc-gui/components/nc/Button.vue
- packages/nc-gui/helpers/parsers/parserHelpers.ts
- packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.processor.ts
- packages/nc-gui/components/smartsheet/header/CellIcon.ts
- packages/nc-gui/components/virtual-cell/ManyToMany.vue
- packages/nc-gui/components/smartsheet/PlainCell.vue
- packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
- packages/nc-gui/components/virtual-cell/components/ListItem.vue
- packages/nc-gui/composables/useMultiSelect/index.ts
- packages/nocodb/src/db/sortV2.ts
- packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
- packages/nc-gui/composables/useInfiniteData.ts
- packages/nc-gui/utils/cell.ts
- packages/nc-gui/components/ai/Settings.vue
- packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
- packages/nocodb/src/utils/globals.ts
- packages/nc-gui/components/smartsheet/column/AiButtonOptions.vue
- packages/nc-gui/composables/useColumnCreateStore.ts
- packages/nc-gui/components/smartsheet/expanded-form/index.vue
- packages/nc-gui/components/smartsheet/Cell.vue
- packages/nc-gui/components/general/Loader.vue
- packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
- packages/nc-gui/composables/useData.ts
- packages/nc-gui/components/nc/List/RecordItem.vue
- packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
- packages/nc-gui/components/smartsheet/details/Fields.vue
- packages/nc-gui/components/smartsheet/Kanban.vue
👮 Files not reviewed due to content moderation or server errors (3)
- packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
- packages/nc-gui/components/smartsheet/grid/Table.vue
- packages/nocodb/src/services/columns.service.ts
🧰 Additional context used
📓 Learnings (1)
packages/nc-gui/components/smartsheet/grid/GroupByTable.vue (1)
Learnt from: DarkPhoenix2704
PR: nocodb/nocodb#8781
File: packages/nc-gui/components/smartsheet/grid/GroupBy.vue:225-232
Timestamp: 2024-11-12T03:23:04.401Z
Learning: DarkPhoenix2704 prefers to keep code implementations that prioritize readability, even if an alternative might be more concise.
🪛 Biome (1.9.4)
packages/nc-gui/composables/useIntegrationsStore.ts
[error] 296-296: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/Column.ts
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/models/AIColumn.ts
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (49)
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue (1)
155-158
:
Fix missing import for ncDelay
function
The ncDelay
function is called but not imported, which will cause a runtime error. This issue was previously identified but remains unfixed.
Import the ncDelay
utility:
<script lang="ts" setup>
import { ProjectRoles, type TableType, type ViewType, WorkspaceUserRoles } from 'nocodb-sdk'
import { ViewTypes, viewTypeAlias } from 'nocodb-sdk'
import { LockType } from '#imports'
+ import { ncDelay } from '~/utils/ncDelay'
packages/nc-gui/utils/commonUtils.ts (2)
75-102
: Well-documented with comprehensive examples!
The JSDoc documentation is excellent, providing clear parameter descriptions and covering various use cases with examples.
103-117
: Add test coverage for the searchCompare function.
As mentioned in a previous review, this utility function needs test coverage. Based on the documentation and implementation, consider adding tests for:
- Basic string/number searches
- Array searches with mixed types
- Edge cases (undefined, null, empty values)
- Case sensitivity scenarios
Let's verify existing test coverage:
#!/bin/bash
# Description: Check for existing test coverage of searchCompare function
# Look for test files and any usage of searchCompare
# Search for test files
fd -e spec.ts -e test.ts commonUtils packages/nc-gui/
# Search for usage of searchCompare
rg "searchCompare" packages/nc-gui/ -l
packages/nc-gui/components/cell/attachment/index.vue (1)
Line range hint 1-585
: Overall implementation looks good!
The changes align well with the PR objectives. The code implements all the described functionality including:
- Enhanced attachment modal handling
- Improved keyboard interactions
- Updated UI elements
- Proper error handling for attachment data
The implementation is consistent with similar changes in other components mentioned in the AI summary.
packages/nc-gui/utils/columnUtils.ts (2)
2-3
: LGTM: Clean addition of AI-related types and constants
The new imports and constants are well-organized and follow the existing pattern.
Also applies to: 15-17
289-295
: 🛠️ Refactor suggestion
DRY up duplicate AI validation logic
The AI integration validation logic is duplicated between Button and LongText types. This violates the DRY principle and makes maintenance harder.
Extract the common logic into a helper function:
+ const isAiColumnInvalid = (
+ colOptions: ButtonType,
+ isReadOnly: boolean,
+ aiIntegrations: Partial<IntegrationType>[]
+ ): { isInvalid: boolean; tooltip: string } => ({
+ isInvalid:
+ !colOptions.fk_integration_id ||
+ (isReadOnly
+ ? false
+ : !!colOptions.fk_integration_id &&
+ !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id')),
+ tooltip: 'title.aiIntegrationMissing'
+ })
case UITypes.Button: {
const colOptions = col.colOptions as ButtonType
if (colOptions.type === ButtonActionsType.Webhook) {
result.isInvalid = !colOptions.fk_webhook_id
} else if (colOptions.type === ButtonActionsType.Url) {
result.isInvalid = !!colOptions.error
} else if (colOptions.type === ButtonActionsType.Ai) {
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id &&
- !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const aiValidation = isAiColumnInvalid(colOptions, isReadOnly, aiIntegrations)
+ result.isInvalid = aiValidation.isInvalid
+ result.tooltip = aiValidation.tooltip
}
break
}
case UITypes.LongText: {
if (parseProp(col.meta)[LongTextAiMetaProp]) {
const colOptions = col.colOptions as ButtonType
- result.isInvalid =
- !colOptions.fk_integration_id ||
- (isReadOnly
- ? false
- : !!colOptions.fk_integration_id &&
- !ncIsArrayIncludes(aiIntegrations, colOptions.fk_integration_id, 'id'))
- result.tooltip = 'title.aiIntegrationMissing'
+ const aiValidation = isAiColumnInvalid(colOptions, isReadOnly, aiIntegrations)
+ result.isInvalid = aiValidation.isInvalid
+ result.tooltip = aiValidation.tooltip
}
break
}
Also applies to: 303-309
packages/nc-gui/composables/useIntegrationsStore.ts (5)
191-192
: LGTM: Event tracking implementation is consistent and well-placed
The event tracking calls are strategically placed before each operation and follow a consistent naming pattern.
Also applies to: 224-225, 253-254, 287-292
541-541
: LGTM: Export addition is consistent with the existing pattern
93-94
: 🛠️ Refactor suggestion
Consider using a computed property for AI integrations
The manual synchronization of AI integrations state across multiple functions could lead to race conditions.
Consider using a computed property as suggested in the previous review to derive AI integrations from the main integrations array:
- const { aiIntegrations } = useNocoAi()
+ const aiIntegrations = computed(() =>
+ integrations.value.filter(i => i.type === IntegrationsType.Ai)
+ .map(i => ({
+ id: i.id,
+ title: i.title,
+ is_default: i.is_default,
+ type: i.type,
+ sub_type: i.sub_type,
+ }))
+ )
229-238
: 🛠️ Refactor suggestion
Avoid mutating objects within map function
The current implementation mutates the object directly within the map function.
Apply this diff to return new objects instead:
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.title = integration.title
- return i
+ return { ...i, title: integration.title }
}
return i
})
}
258-269
: 🛠️ Refactor suggestion
Avoid mutating objects within map function
Similar mutation pattern in setDefaultIntegration function.
Apply this diff to return new objects instead:
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
- i.is_default = true
+ return { ...i, is_default: true }
} else {
- i.is_default = false
+ return { ...i, is_default: false }
}
})
}
packages/nc-gui/components/cell/AI.vue (4)
16-18
: LGTM! Improved permission handling.
The addition of isUIAllowed
and isPublic
enhances access control by properly managing user permissions and public access scenarios.
118-120
: LGTM! Well-structured button state management.
The isDisabledAiButton
computed property effectively consolidates multiple conditions into a single, readable expression for managing the AI button's disabled state.
131-139
: Fix the tooltip disabled logic.
The tooltip's disabled condition is still incorrect. It should be disabled when the button is enabled, not the other way around.
Apply this diff to fix the issue:
- <NcTooltip :disabled="isFieldAiIntegrationAvailable || isPublic || isUIAllowed('dataEdit')" class="flex">
+ <NcTooltip :disabled="!isDisabledAiButton" class="flex">
Line range hint 194-211
: LGTM! Enhanced visual feedback.
The styling changes improve user experience by providing clear visual feedback for different button states, particularly in read-only mode.
packages/nc-gui/components/ai/PromptWithFields.vue (3)
Line range hint 19-31
: LGTM! Enhanced component flexibility.
The addition of the readOnly
prop improves component reusability by allowing external control of the edit state.
159-187
: LGTM! Proper tooltip lifecycle management.
The implementation correctly manages tooltip instances and includes cleanup on component unmount to prevent memory leaks.
63-68
: Correct the string comparison logic in the suggestion filter.
The string comparison still has an extra closing brace that may lead to incorrect filtering.
Apply this diff to fix the issue:
return (
props.options?.filter(
(o) =>
- o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(),
+ o.title?.toLowerCase()?.includes(query.toLowerCase()) || `{${o.title?.toLowerCase()}}` === query.toLowerCase(),
) ?? []
)
packages/nc-gui/components/cell/TextArea.vue (4)
Line range hint 17-41
: Consider using a default value for meta injection.
The injected meta
dependency uses ref()
as a fallback, which could lead to runtime errors when accessing properties.
Apply this diff to add a proper default value:
-const meta = inject(MetaInj, ref())
+const meta = inject(MetaInj, ref({ columns: [] }))
100-111
: Avoid using non-null assertions to prevent potential runtime errors.
The use of non-null assertions (!
) in meta.value!.columns!
may lead to runtime errors if meta.value
or columns
are undefined
.
Apply this diff to handle possible null values:
const rowId = computed(() => {
- return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
+ if (currentRow.value?.row && meta.value?.columns) {
+ return extractPkFromRow(currentRow.value.row, meta.value.columns)
+ }
+ return null
})
Line range hint 426-561
: LGTM! Well-structured AI feature UI.
The UI changes provide clear feedback for AI-related features with proper handling of:
- Warning states for stale data
- Loading indicators
- User editing states
- Regeneration controls
647-648
: Correct the usage of Tailwind CSS classes.
The class animate-infinite
is not a valid Tailwind CSS utility. The animate-spin
class already provides an infinite spin animation.
Apply this diff to correct the class:
<GeneralIcon icon="refresh" :class="{
- 'animate-infinite animate-spin': isAiGenerating
+ 'animate-spin': isAiGenerating
}" />
packages/nc-gui/components/smartsheet/grid/GroupByTable.vue (1)
Line range hint 279-324
: Verify pagination handling for edge cases
Please ensure proper handling of the following scenarios:
- Navigation when deleting the last row of a page
- Navigation when adding a row that creates a new page
✅ Verification successful
Based on the shell script results, I can now provide a definitive response about the pagination handling in the GroupByTable component:
Pagination edge cases are properly handled in the codebase
The code shows robust pagination handling for the mentioned scenarios:
-
When deleting the last row of a page:
- The component syncs with
paginationData
and automatically adjusts page numbers - If the current page becomes empty, it navigates to the previous page via
loadGroupPage
- The component syncs with
-
When adding a row that creates a new page:
- The pagination state is properly managed through
vGroup.paginationData
- Page navigation is handled through the
loadGroupPage
callback - The component maintains proper page size limits and total row counts
- The pagination state is properly managed through
The implementation includes proper page boundary checks (isFirstPage
, isLastPage
), page size management, and navigation controls, making it resilient to edge cases in row manipulation scenarios.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for pagination-related tests and edge case handling
rg -A 5 "loadGroupPage|paginationData" --type vue
Length of output: 79
Script:
#!/bin/bash
# Search for pagination-related code with ripgrep, without file type restriction
rg -A 5 "loadGroupPage|paginationData"
Length of output: 75492
Script:
#!/bin/bash
# Search for pagination-related tests
fd "test|spec" --exec rg -l "loadGroupPage|paginationData" {} \;
Length of output: 69
Script:
#!/bin/bash
# Look for pagination handling in the component
ast-grep --pattern 'async function loadGroupPage($_, $_) {
$$$
}'
Length of output: 69
Script:
#!/bin/bash
# Search for group table pagination implementation
rg -A 10 "class=\"nc-group-table\""
Length of output: 1129
packages/nocodb/src/models/AIColumn.ts (3)
14-16
:
Ensure correct handling of 'null' or 'undefined' in castType
method
The castType
method should handle cases where data
might be null
or undefined
. Currently, if data
is falsy, the method returns data
, which could be undefined
instead of an AIColumn
instance.
Apply this diff to fix the issue:
- return data && new AIColumn(data);
+ return data ? new AIColumn(data) : null;
26-26
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. It's better to reference the class name directly for readability.
Apply this diff to fix the issue:
- return this._insert(
+ return AIColumn._insert(
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
40-40
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. It's better to reference the class name directly for readability.
Apply this diff to fix the issue:
- return this._update(
+ return AIColumn._update(
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/models/LongTextColumn.ts (2)
61-61
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. It's better to reference the class name directly for readability.
Apply this diff to fix the issue:
- return this.read(context, longTextColumn.fk_column_id, ncMeta);
+ return LongTextColumn.read(context, longTextColumn.fk_column_id, ncMeta);
🧰 Tools
🪛 Biome (1.9.4)
[error] 61-61: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
85-85
: 🛠️ Refactor suggestion
Avoid using 'this' in static methods; use the class name instead
Using this
in static methods can be unclear. It's better to reference the class name directly for readability.
Apply this diff to fix the issue:
- return column ? this.castType(column) : null;
+ return column ? LongTextColumn.castType(column) : null;
🧰 Tools
🪛 Biome (1.9.4)
[error] 85-85: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
packages/nocodb/src/utils/dataConversion.ts (1)
135-149
:
Avoid string concatenation in SQL queries to prevent SQL injection
In the MSSQL query, string concatenation is used to build the JSON string, which can lead to SQL injection vulnerabilities. It's important to construct SQL queries using parameterized statements.
Consider refactoring the query to use parameter bindings or MSSQL's JSON functions without concatenation. For example, you can use JSON_MODIFY
or pass the entire JSON object as a parameter.
Apply this diff to fix the issue:
- `UPDATE ??
- SET ?? = JSON_QUERY('{"value":' + ?? + ',"lastModifiedBy":' + ? + ',"lastModifiedTime":' + ? + ',"isStale":' + ? + '}')
- WHERE ?? is not null;`,
+ `UPDATE ??
+ SET ?? = JSON_QUERY(?)
+ WHERE ?? is not null;`,
[
baseModel.getTnPath(table.table_name),
column.column_name,
- column.column_name,
- commonRecord.lastModifiedBy.toString(),
- commonRecord.lastModifiedTime.toString(),
- commonRecord.isStale,
+ JSON.stringify({
+ value: sqlClient.raw('??', [column.column_name]),
+ lastModifiedBy: commonRecord.lastModifiedBy.toString(),
+ lastModifiedTime: commonRecord.lastModifiedTime.toString(),
+ isStale: commonRecord.isStale,
+ }),
column.column_name,
],
packages/nc-gui/composables/useNocoAi.ts (1)
20-28
: Code changes look good
The updates to the AI integration handling improve the logic and fix previous issues related to checking the availability of AI integrations.
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue (2)
48-58
: Initialize meta object before assignment
The isEnabledGenerateText
setter attempts to modify meta
without checking if it exists.
66-104
: Add error handling for generateRows
The generate
function lacks error handling for the asynchronous generateRows
call.
packages/nocodb/src/models/Integration.ts (2)
454-457
: LGTM: Safe initialization of config.models array
The check and initialization of config.models
as an empty array is a good defensive programming practice.
474-500
: Refactor duplicate unbinding logic
The unbinding logic is duplicated between delete
and softDelete
methods.
Also applies to: 522-547
packages/nc-gui/components/smartsheet/header/Menu.vue (2)
48-49
: LGTM!
Clean integration of the AI functionality through the useNocoAi hook.
52-61
: LGTM! Well-structured validation logic.
The columnInvalid computed property effectively handles validation for AI-related columns with proper null checks and a clean return type.
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue (3)
186-196
: LGTM! Clean implementation of AI prompt visibility control.
The hideAiPromptOnEdit condition effectively prevents editing of AI prompts and buttons in edit mode, maintaining a consistent user experience.
625-628
: LGTM! Clear and focused implementation.
The isAiPromptSelectOption function provides a clean way to identify AI prompt fields.
1165-1169
: LGTM! Good integration of AI functionality.
The SmartsheetColumnLongTextOptions component is properly enhanced with AI integration navigation capabilities.
packages/nocodb/src/db/conditionV2.ts (1)
596-613
: 🛠️ Refactor suggestion
Add error handling for JSON extraction
The JSON extraction logic should handle invalid JSON values to prevent runtime errors.
Apply this diff to add error handling:
if (isAIPromptCol(column)) {
+ const handleInvalidJson = (field) => {
+ return knex.raw('CASE WHEN JSON_VALID(??) THEN ? ELSE NULL END', [
+ column.column_name,
+ field
+ ]);
+ };
if (knex.clientType() === 'pg') {
- field = knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`TRIM('"' FROM (??::jsonb->>'value'))`, [column.column_name])
+ );
} else if (knex.clientType().startsWith('mysql')) {
- field = knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name]);
+ field = handleInvalidJson(
+ knex.raw(`JSON_UNQUOTE(JSON_EXTRACT(??, '$.value'))`, [column.column_name])
+ );
} // Apply similar changes for other DB types
}
packages/nocodb/src/models/Column.ts (4)
573-577
: LGTM!
The implementation correctly retrieves AI column options.
1101-1106
: LGTM!
The case statement for handling AI column type is correctly implemented.
452-469
: 🛠️ Refactor suggestion
Add error handling for AI column insertion
The AI column insertion lacks error handling. Consider adding try-catch and logging.
Apply this diff:
case UITypes.LongText: {
if (column.meta?.[LongTextAiMetaProp] === true) {
+ try {
await AIColumn.insert(
context,
{
fk_model_id: column.fk_model_id,
fk_column_id: colId,
fk_integration_id: column.fk_integration_id,
model: column.model,
prompt: column.prompt,
prompt_raw: column.prompt_raw,
error: column.error,
},
ncMeta,
);
+ } catch (error) {
+ logger.error(`Failed to insert AI column: ${error.message}`);
+ throw error;
+ }
}
break;
}
886-927
:
Fix incorrect placeholder matching in AI prompts
The code checks for a hardcoded {column_id}
placeholder in AI prompts, which is incorrect. It should check for the actual column ID.
Apply this diff:
- if (ai.prompt && ai.prompt.match(/{column_id}/)) {
+ if (ai.prompt && ai.prompt.includes(`{${col.id}}`)) {
🧰 Tools
🪛 Biome (1.9.4)
[error] 921-921: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts (2)
1113-1114
: LGTM!
The AI prompt column check is correctly added to the filter condition.
1281-1296
: 🛠️ Refactor suggestion
Add validation for required fields in AI prompt column import
The AI prompt column import lacks validation for required fields.
Apply this diff:
} else if (isAIPromptCol(col)) {
if (base.fk_workspace_id !== colOptions.fk_workspace_id) {
colOptions.fk_workspace_id = null;
colOptions.fk_integration_id = null;
colOptions.model = null;
}
+ // Validate required fields
+ if (!colOptions.prompt_raw) {
+ this.logger.warn(`Skipping AI prompt column import: Missing prompt_raw for column ${col.title}`);
+ continue;
+ }
+ try {
const freshModelData = await this.columnsService.columnAdd(context, {
tableId: getIdOrExternalId(getParentIdentifier(col.id)),
column: withoutId({
...flatCol,
...{
fk_integration_id: colOptions.fk_integration_id,
model: colOptions.model,
prompt_raw: colOptions.prompt_raw,
},
}) as any,
req: param.req,
user: param.user,
});
+ } catch (error) {
+ this.logger.error(`Failed to import AI prompt column ${col.title}: ${error.message}`);
+ throw error;
+ }
packages/nocodb/src/db/BaseModelSqlv2.ts (3)
10063-10105
: LGTM! Well-structured AI prompt data handling
The implementation properly handles AI prompt column data with metadata tracking for:
- Value changes
- Last modified info
- Staleness state
- System vs user modifications
10108-10138
: LGTM! Robust AI column staleness tracking
The implementation correctly:
- Tracks referenced column changes
- Updates staleness state
- Preserves existing metadata
10071-10076
:
Correct the type of isStale
to boolean
The isStale
property is used as a boolean in the code but typed as string in the interface.
const obj: {
value?: string;
lastModifiedBy?: string;
lastModifiedTime?: string;
- isStale?: string;
+ isStale?: boolean;
} = {};
Likely invalid or redundant comment.
62d57b8
to
36d6403
Compare
Change Summary
Provide summary of changes with issue number if any.
Change type
Test/ Verification
Provide summary of changes.
Additional information / screenshots (optional)
Anything for maintainers to be made aware of