import React, { useMemo, useState } from "react"; import equal from "react-fast-compare" import { CMSType, EditorAIController, FieldProps, MarkdownEditorFieldBinding, PluginFieldBuilderParams, useTranslation } from "@firecms/core"; import { AutoAwesomeIcon, CircularProgress, cls, IconButton, Menu, MenuItem, SendIcon, TextField, Tooltip, Typography } from "@firecms/ui"; import { useDataEnhancementController } from "./DataEnhancementControllerProvider"; import { SUPPORTED_FIELDS_ENHANCEMENT } from "../utils/fields"; import { EnhanceTextFieldBinding } from "./fields/EnhanceTextField"; import { EnhancedDataResult, EnhanceParams } from "../types/data_enhancement_controller"; import { countStringCharacters } from "../utils/strings_counter"; export function fieldBuilder (params: PluginFieldBuilderParams): React.ComponentType> | null { const { fieldConfigId, property } = params; const wrappedComponent = React.useMemo(() => function FieldWrapper(props: FieldProps) { const { enabled, suggestions, enhance, loadingSuggestions, editorAIController } = useDataEnhancementController(); const loading = loadingSuggestions?.includes(props.propertyKey); const suggestedValue = suggestions?.[props.propertyKey]; const filledCharacters = countStringCharacters(props.context.values, props.context.collection?.properties ?? {}); const enoughData = filledCharacters > 5; return } enhance={enhance} editorAIController={editorAIController} /> }, []); if (property.disabled || property.readOnly || property.Field) { return null; } if (SUPPORTED_FIELDS_ENHANCEMENT.includes(fieldConfigId)) { return wrappedComponent; } return null; } interface FieldInnerParams = any> { loading: boolean; props: FieldProps; suggestedValue: string | number; enabled: boolean; enoughData: boolean; Field: React.ComponentType>; enhance: (props: EnhanceParams) => Promise; editorAIController?: EditorAIController; } const FieldInner = React.memo(function FieldInner = any>({ loading, props, suggestedValue, enabled, enoughData, Field, enhance, editorAIController }: FieldInnerParams) { const { t } = useTranslation(); const [dataLoading, setDataLoading] = useState(false); const [tooltipOpen, setTooltipOpen] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); const [propertyInstructions, setPropertyInstructions] = useState(); const property = props.property; const topClass = useMemo(() => { if (props.partOfBlock) { return "-top-4"; } if (property.widthPercentage !== undefined) { return "top-4"; } else { if (property.dataType === "array" && property.of?.dataType === "string") { return "top-4"; } return property.dataType === "string" && property.markdown ? "top-3" : "-top-4"; } }, []); const rightClass = props.partOfBlock ? "right-8" : (props.partOfArray ? "right-12" : "right-2"); if (!enabled) { return } const showEnhanceIcon = !props.disabled && ( !props.value || (property.dataType === "string" && (property.multiline || property.markdown)) || (property.dataType === "array" && property.of?.dataType === "string") ); const indexOfSuggestion = props.value && typeof props.value === "string" && typeof suggestedValue === "string" && props.value.endsWith(suggestedValue) ? props.value.indexOf(suggestedValue) + 1 : undefined; const highlightRange = indexOfSuggestion && typeof suggestedValue === "string" ? { from: indexOfSuggestion, to: suggestedValue.length + indexOfSuggestion } : undefined; let fieldBinding: React.ReactElement; if (property.dataType === "string" && property.markdown) { fieldBinding = } customProps={{ highlight: highlightRange, editorProps: { aiController: editorAIController, } }} />; } else if (property.dataType === "string" && !property.enumValues) { fieldBinding = } highlight={suggestedValue as string} />; } else { fieldBinding = ; } const enhanceData = (instructions?: string) => { if (!props.context.entityId) return; if (!enoughData) return; setMenuOpen(false); setDataLoading(true); return enhance({ entityId: props.context.entityId, propertyKey: props.propertyKey, propertyInstructions: instructions, values: props.context.values, replaceValues: false }).finally(() => setDataLoading(false)); }; const allowInstructions = property.dataType === "string" && !property.enumValues; return <> {fieldBinding} {showEnhanceIcon &&
{ if (!props.context.entityId) return; if (!enoughData) return; setTooltipOpen(false); setDataLoading(true); return enhance({ entityId: props.context.entityId, propertyKey: props.propertyKey, values: props.context.values, replaceValues: false }).finally(() => setDataLoading(false)); }}> {dataLoading || loading ? : } }> enhanceData()}>
{t("autofill_property", { property: property.name ?? t("this_field") })} {t("based_on_rest_of_entity")}
{allowInstructions &&
{ if (e.key === "Enter") { enhanceData(propertyInstructions); } }} placeholder={t("instructions")} onChange={(e) => setPropertyInstructions(e.target.value)} endAdornment={ enhanceData(propertyInstructions)} disabled={!propertyInstructions}> }>
}
} }, (prevProps, nextProps) => { return prevProps.loading === nextProps.loading && prevProps.suggestedValue === nextProps.suggestedValue && prevProps.enabled === nextProps.enabled && prevProps.props.value === nextProps.props.value && prevProps.props.error === nextProps.props.error && prevProps.props.showError === nextProps.props.showError && prevProps.props.disabled === nextProps.props.disabled && equal(prevProps.props.property, nextProps.props.property) && prevProps.Field === nextProps.Field && prevProps.enoughData === nextProps.enoughData; });