Skip to content

[Feature] Enhance table suggestion handling #93

@JonnyTran

Description

@JonnyTran

Description

Enhance table suggestion handling to properly support AI-generated suggestions for table data, leveraging workspace schema configuration for intelligent suggestion processing, validation, and user review. This improvement enables better integration of AI suggestions with the table reference resolution system.

Problem

  • Suggestions for table data are not well-supported in the current system
  • Complex to manage suggestions for tables with references and schema relationships
  • Difficult to merge or partially accept table suggestions
  • No clear UI for reviewing and accepting table suggestions with schema context
  • Limited validation for table suggestions against workspace schema configuration
  • No integration with workspace-level reference resolution

Proposed Solution

  1. Leverage Workspace Schema Configuration: Use schema definitions for intelligent suggestion processing
  2. Enhance Backend Suggestion Model: Improve suggestion handling for table data with schema awareness
  3. Implement Schema-Aware Validation: Validate table suggestions against workspace schema definitions
  4. Create Advanced UI Components: Build intuitive interfaces for table suggestion review and acceptance
  5. Integrate with Reference Resolution: Handle references in table suggestions using the enhanced resolution system

Implementation Details

Dependencies

Backend Changes

  1. Enhance Suggestion model for table data:

    class Suggestion(DatabaseModel):
        # Existing fields...
        
        def validate_table_suggestion(self, workspace_id: UUID) -> List[ValidationError]:
            """Validate table suggestion against workspace schema configuration"""
            if not self.question.is_table:
                return []
            
            schema_service = SchemaService(workspace_id)
            return schema_service.validate_table_suggestion(
                self.question.name, 
                self.value, 
                workspace_id
            )
        
        def resolve_suggestion_references(self, workspace_id: UUID) -> dict:
            """Resolve references in table suggestion data"""
            if not self.question.is_table:
                return self.value
            
            schema_service = SchemaService(workspace_id)
            return schema_service.resolve_table_references(
                self.value, 
                workspace_id
            )
  2. Enhance SchemaService for suggestion handling:

    class SchemaService:
        def validate_table_suggestion(self, 
                                    question_name: str, 
                                    suggestion_data: dict, 
                                    workspace_id: UUID) -> List[ValidationError]:
            """Validate table suggestion against question schema definition"""
            question_schema = self.get_question_schema(question_name)
            validation_errors = []
            
            # Validate table structure
            structure_errors = self._validate_table_structure(suggestion_data, question_schema)
            validation_errors.extend(structure_errors)
            
            # Validate references
            reference_errors = self._validate_suggestion_references(suggestion_data, workspace_id)
            validation_errors.extend(reference_errors)
            
            return validation_errors
        
        def merge_table_suggestions(self, 
                                  current_data: dict, 
                                  suggestion_data: dict,
                                  merge_strategy: str = "append") -> dict:
            """Merge table suggestion with current data using specified strategy"""
            if merge_strategy == "append":
                return self._append_suggestion_rows(current_data, suggestion_data)
            elif merge_strategy == "replace":
                return suggestion_data
            elif merge_strategy == "merge":
                return self._merge_suggestion_data(current_data, suggestion_data)
            else:
                raise ValueError(f"Unknown merge strategy: {merge_strategy}")
        
        def generate_suggestion_conflicts(self, 
                                        current_data: dict, 
                                        suggestion_data: dict,
                                        workspace_id: UUID) -> List[dict]:
            """Identify conflicts between current data and suggestions"""
            conflicts = []
            
            # Check for reference conflicts
            ref_conflicts = self._check_reference_conflicts(current_data, suggestion_data, workspace_id)
            conflicts.extend(ref_conflicts)
            
            # Check for data conflicts
            data_conflicts = self._check_data_conflicts(current_data, suggestion_data)
            conflicts.extend(data_conflicts)
            
            return conflicts
  3. Add suggestion processing API endpoints:

    @router.post("/workspaces/{workspace_id}/suggestions/{suggestion_id}/validate")
    async def validate_table_suggestion(
        workspace_id: UUID,
        suggestion_id: UUID,
        db: AsyncSession = Depends(get_async_db)
    ):
        suggestion = await get_suggestion_by_id(db, suggestion_id)
        validation_errors = suggestion.validate_table_suggestion(workspace_id)
        
        return {
            "valid": len(validation_errors) == 0,
            "errors": validation_errors,
            "resolved_data": suggestion.resolve_suggestion_references(workspace_id)
        }
    
    @router.post("/workspaces/{workspace_id}/suggestions/{suggestion_id}/merge")
    async def merge_table_suggestion(
        workspace_id: UUID,
        suggestion_id: UUID,
        merge_request: TableSuggestionMergeRequest,
        db: AsyncSession = Depends(get_async_db)
    ):
        suggestion = await get_suggestion_by_id(db, suggestion_id)
        record = await get_record_by_id(db, suggestion.record_id)
        
        schema_service = SchemaService(workspace_id, db)
        
        # Get current table data
        current_data = record.responses[0].values.get(suggestion.question.name, {})
        
        # Merge with suggestion
        merged_data = schema_service.merge_table_suggestions(
            current_data,
            suggestion.value,
            merge_request.strategy
        )
        
        # Validate merged result
        validation_errors = schema_service.validate_table_suggestion(
            suggestion.question.name,
            merged_data,
            workspace_id
        )
        
        if validation_errors:
            return {"success": False, "errors": validation_errors}
        
        # Update record with merged data
        await update_record_response(db, record.id, {
            suggestion.question.name: merged_data
        })
        
        return {"success": True, "merged_data": merged_data}
    
    @router.get("/workspaces/{workspace_id}/suggestions/{suggestion_id}/conflicts")
    async def get_suggestion_conflicts(
        workspace_id: UUID,
        suggestion_id: UUID,
        db: AsyncSession = Depends(get_async_db)
    ):
        suggestion = await get_suggestion_by_id(db, suggestion_id)
        record = await get_record_by_id(db, suggestion.record_id)
        
        schema_service = SchemaService(workspace_id, db)
        current_data = record.responses[0].values.get(suggestion.question.name, {})
        
        conflicts = schema_service.generate_suggestion_conflicts(
            current_data,
            suggestion.value,
            workspace_id
        )
        
        return {"conflicts": conflicts}

Frontend Changes

  1. Enhance Suggestion entity for table handling:

    export class Suggestion implements Answer {
        // Existing properties...
        
        async getResolvedTableValue(workspaceId: string): Promise<TableData> {
            if (!this.questionType.isTableType) {
                return this.value as TableData;
            }
            
            const response = await suggestionService.validateTableSuggestion(
                workspaceId,
                this.id
            );
            
            return new TableData(
                response.resolved_data.data,
                response.resolved_data.schema,
                response.resolved_data.reference
            );
        }
        
        async getConflicts(workspaceId: string): Promise<SuggestionConflict[]> {
            if (!this.questionType.isTableType) {
                return [];
            }
            
            const response = await suggestionService.getSuggestionConflicts(
                workspaceId,
                this.id
            );
            
            return response.conflicts.map(conflict => new SuggestionConflict(conflict));
        }
    }
  2. Create advanced table suggestion UI components:

    <!-- TableSuggestionReview.vue -->
    <template>
      <div class="table-suggestion-review">
        <div class="suggestion-header">
          <h3>Table Suggestion Review</h3>
          <div class="suggestion-meta">
            <span>Agent: {{ suggestion.agent }}</span>
            <span>Score: {{ suggestion.score.fixed }}</span>
            <span>Updated: {{ suggestion.updatedAt | formatDate }}</span>
          </div>
        </div>
        
        <div v-if="conflicts.length > 0" class="conflicts-section">
          <h4>Conflicts Detected</h4>
          <div v-for="conflict in conflicts" :key="conflict.id" class="conflict-item">
            <ConflictResolver :conflict="conflict" @resolve="handleConflictResolve" />
          </div>
        </div>
        
        <div class="data-comparison">
          <div class="current-data">
            <h4>Current Data</h4>
            <TableRenderer :table-data="currentData" :readonly="true" />
          </div>
          
          <div class="suggested-data">
            <h4>Suggested Data</h4>
            <TableRenderer :table-data="suggestedData" :readonly="true" />
          </div>
          
          <div class="merged-preview" v-if="mergedData">
            <h4>Merged Preview</h4>
            <TableRenderer :table-data="mergedData" :readonly="true" />
          </div>
        </div>
        
        <div class="merge-options">
          <div class="merge-strategy">
            <label>Merge Strategy:</label>
            <select v-model="selectedStrategy" @change="updateMergePreview">
              <option value="append">Append new rows</option>
              <option value="replace">Replace all data</option>
              <option value="merge">Smart merge</option>
            </select>
          </div>
          
          <div class="partial-selection" v-if="selectedStrategy === 'append'">
            <h5>Select rows to accept:</h5>
            <div v-for="(row, index) in suggestedData.data" :key="index" class="row-selector">
              <input 
                type="checkbox" 
                :id="`row-${index}`" 
                v-model="selectedRows[index]"
                @change="updateMergePreview"
              />
              <label :for="`row-${index}`">Row {{ index + 1 }}</label>
              <TableRowPreview :row="row" :schema="suggestedData.schema" />
            </div>
          </div>
        </div>
        
        <div class="action-buttons">
          <button @click="acceptSuggestion" :disabled="!canAccept" class="btn-accept">
            Accept Suggestion
          </button>
          <button @click="rejectSuggestion" class="btn-reject">
            Reject Suggestion
          </button>
          <button @click="requestModification" class="btn-modify">
            Request Modification
          </button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        suggestion: Object,
        currentData: Object,
        workspaceId: String,
      },
      data() {
        return {
          suggestedData: null,
          mergedData: null,
          conflicts: [],
          selectedStrategy: 'append',
          selectedRows: {},
          loading: false,
        };
      },
      async mounted() {
        await this.loadSuggestionData();
      },
      computed: {
        canAccept() {
          return this.conflicts.length === 0 && this.mergedData !== null;
        },
      },
      methods: {
        async loadSuggestionData() {
          this.loading = true;
          try {
            // Load resolved suggestion data
            this.suggestedData = await this.suggestion.getResolvedTableValue(this.workspaceId);
            
            // Load conflicts
            this.conflicts = await this.suggestion.getConflicts(this.workspaceId);
            
            // Generate initial merge preview
            await this.updateMergePreview();
          } catch (error) {
            this.$toast.error('Failed to load suggestion data');
          } finally {
            this.loading = false;
          }
        },
        
        async updateMergePreview() {
          try {
            const response = await suggestionService.previewMerge(
              this.workspaceId,
              this.suggestion.id,
              {
                strategy: this.selectedStrategy,
                selectedRows: this.selectedRows,
              }
            );
            this.mergedData = new TableData(
              response.merged_data.data,
              response.merged_data.schema,
              response.merged_data.reference
            );
          } catch (error) {
            this.$toast.error('Failed to generate merge preview');
          }
        },
        
        async acceptSuggestion() {
          try {
            await suggestionService.mergeSuggestion(
              this.workspaceId,
              this.suggestion.id,
              {
                strategy: this.selectedStrategy,
                selectedRows: this.selectedRows,
              }
            );
            this.$emit('accepted', this.mergedData);
            this.$toast.success('Suggestion accepted successfully');
          } catch (error) {
            this.$toast.error('Failed to accept suggestion');
          }
        },
        
        // Additional methods...
      },
    };
    </script>
  3. Create suggestion conflict resolution components:

    <!-- ConflictResolver.vue -->
    <template>
      <div class="conflict-resolver">
        <div class="conflict-description">
          <h5>{{ conflict.type }} Conflict</h5>
          <p>{{ conflict.description }}</p>
        </div>
        
        <div class="conflict-options">
          <div class="option" v-for="option in conflict.options" :key="option.id">
            <input 
              type="radio" 
              :id="option.id" 
              :value="option.value"
              v-model="selectedResolution"
            />
            <label :for="option.id">{{ option.label }}</label>
            <div class="option-preview">{{ option.preview }}</div>
          </div>
        </div>
        
        <button @click="resolveConflict" :disabled="!selectedResolution">
          Resolve Conflict
        </button>
      </div>
    </template>

Related Files

  • extralit/argilla-server/src/argilla_server/models/database.py - Enhanced Suggestion model
  • extralit/argilla-server/src/argilla_server/services/SchemaService.py - Suggestion validation and merging logic
  • extralit/argilla-server/src/argilla_server/api/handlers/v1/suggestions/ - Enhanced suggestion handlers
  • extralit/argilla-frontend/v1/domain/entities/question/Suggestion.ts - Enhanced Suggestion entity
  • extralit/argilla-frontend/components/features/table-suggestions/ - New suggestion UI components
  • extralit/argilla-frontend/v1/infrastructure/services/SuggestionService.ts - Suggestion API client

Acceptance Criteria

  • Table suggestions are properly validated against workspace schema configuration
  • Backend provides APIs for suggestion validation, conflict detection, and merging
  • Partial acceptance of table suggestions is supported with multiple merge strategies
  • UI clearly displays table suggestions with resolved references and schema context
  • Reference conflicts in table suggestions are properly identified and resolvable
  • Users can efficiently review table suggestions with side-by-side comparison
  • Suggestion merging maintains data integrity and reference consistency
  • Multi-user suggestion scenarios are handled correctly
  • Performance is optimized for large table suggestions
  • Integration tests verify table suggestion functionality
  • Error handling provides meaningful feedback for suggestion conflicts
  • The system maintains backward compatibility with existing suggestion data

Related Issues

This is part of the strategic workspace-level schema management enhancement:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions