|
import { useState, useEffect } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
import { toast } from 'sonner' |
|
import { updateEntity, updateRelation, checkEntityNameExists } from '@/api/lightrag' |
|
import { useGraphStore } from '@/stores/graph' |
|
import { PropertyName, EditIcon, PropertyValue } from './PropertyRowComponents' |
|
import PropertyEditDialog from './PropertyEditDialog' |
|
|
|
|
|
|
|
|
|
interface EditablePropertyRowProps { |
|
name: string |
|
value: any |
|
onClick?: () => void |
|
nodeId?: string |
|
entityId?: string |
|
edgeId?: string |
|
dynamicId?: string |
|
entityType?: 'node' | 'edge' |
|
sourceId?: string |
|
targetId?: string |
|
onValueChange?: (newValue: any) => void |
|
isEditable?: boolean |
|
tooltip?: string |
|
} |
|
|
|
|
|
|
|
|
|
|
|
const EditablePropertyRow = ({ |
|
name, |
|
value: initialValue, |
|
onClick, |
|
nodeId, |
|
edgeId, |
|
entityId, |
|
dynamicId, |
|
entityType, |
|
sourceId, |
|
targetId, |
|
onValueChange, |
|
isEditable = false, |
|
tooltip |
|
}: EditablePropertyRowProps) => { |
|
const { t } = useTranslation() |
|
const [isEditing, setIsEditing] = useState(false) |
|
const [isSubmitting, setIsSubmitting] = useState(false) |
|
const [currentValue, setCurrentValue] = useState(initialValue) |
|
|
|
useEffect(() => { |
|
setCurrentValue(initialValue) |
|
}, [initialValue]) |
|
|
|
const handleEditClick = () => { |
|
if (isEditable && !isEditing) { |
|
setIsEditing(true) |
|
} |
|
} |
|
|
|
const handleCancel = () => { |
|
setIsEditing(false) |
|
} |
|
|
|
const handleSave = async (value: string) => { |
|
if (isSubmitting || value === String(currentValue)) { |
|
setIsEditing(false) |
|
return |
|
} |
|
|
|
setIsSubmitting(true) |
|
|
|
try { |
|
if (entityType === 'node' && entityId && nodeId) { |
|
let updatedData = { [name]: value } |
|
|
|
if (name === 'entity_id') { |
|
const exists = await checkEntityNameExists(value) |
|
if (exists) { |
|
toast.error(t('graphPanel.propertiesView.errors.duplicateName')) |
|
return |
|
} |
|
updatedData = { 'entity_name': value } |
|
} |
|
|
|
await updateEntity(entityId, updatedData, true) |
|
try { |
|
await useGraphStore.getState().updateNodeAndSelect(nodeId, entityId, name, value) |
|
} catch (error) { |
|
console.error('Error updating node in graph:', error) |
|
throw new Error('Failed to update node in graph') |
|
} |
|
toast.success(t('graphPanel.propertiesView.success.entityUpdated')) |
|
} else if (entityType === 'edge' && sourceId && targetId && edgeId && dynamicId) { |
|
const updatedData = { [name]: value } |
|
await updateRelation(sourceId, targetId, updatedData) |
|
try { |
|
await useGraphStore.getState().updateEdgeAndSelect(edgeId, dynamicId, sourceId, targetId, name, value) |
|
} catch (error) { |
|
console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error) |
|
throw new Error('Failed to update edge in graph') |
|
} |
|
toast.success(t('graphPanel.propertiesView.success.relationUpdated')) |
|
} |
|
|
|
setIsEditing(false) |
|
setCurrentValue(value) |
|
onValueChange?.(value) |
|
} catch (error) { |
|
console.error('Error updating property:', error) |
|
toast.error(t('graphPanel.propertiesView.errors.updateFailed')) |
|
} finally { |
|
setIsSubmitting(false) |
|
} |
|
} |
|
|
|
return ( |
|
<div className="flex items-center gap-1 overflow-hidden"> |
|
<PropertyName name={name} /> |
|
<EditIcon onClick={handleEditClick} />: |
|
<PropertyValue |
|
value={currentValue} |
|
onClick={onClick} |
|
tooltip={tooltip || (typeof currentValue === 'string' ? currentValue : JSON.stringify(currentValue, null, 2))} |
|
/> |
|
<PropertyEditDialog |
|
isOpen={isEditing} |
|
onClose={handleCancel} |
|
onSave={handleSave} |
|
propertyName={name} |
|
initialValue={String(currentValue)} |
|
isSubmitting={isSubmitting} |
|
/> |
|
</div> |
|
) |
|
} |
|
|
|
export default EditablePropertyRow |
|
|