Skip to content

feat: Form nodes support file upload and multi-line text #3879

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

Merged
merged 1 commit into from
Aug 18, 2025

Conversation

shaohuzhang1
Copy link
Contributor

feat: Form nodes support file upload and multi-line text

Copy link

f2c-ci-robot bot commented Aug 18, 2025

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Copy link

f2c-ci-robot bot commented Aug 18, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@shaohuzhang1 shaohuzhang1 merged commit d268835 into v2 Aug 18, 2025
3 of 5 checks passed
@shaohuzhang1 shaohuzhang1 deleted the pr@v2@feat_form_node branch August 18, 2025 11:08
top: -35px;
}
}
</style>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few improvements and corrections that can be made to this Vue template component:

  1. Conditional Validation Check: The rangeRules array contains an unnecessary validator function for when both formValue.maxlength and formValue.minlength exist. You should only include the necessary validation logic based on whether they exist or not.

  2. Error Message Typo: In rangeRules, there is a typo in the error message about minimum length: "dynamicsForm TextInput.length.requiredMessage4".

  3. Default Value Assignment: There is redundant assignment of formValue.minlength, formValue.maxlength, and formValue.default_value before being set using getData() and rander(). Also, the use of undefined !== undefined is always true unless explicitly tested against something specific like null.

  4. TypeScript Type Compatibility: Ensure that typescript type compatibility with parent components is maintained.

  5. Accessibility Considerations: Provide proper ARIA labels for accessibility if necessary.

  6. Code Formatting: Minor adjustments for better readability can improve the overall code appearance.

Here’s the revised version:

<template>
  <el-form-item :label="$t('dynamicsForm.TextInput.length.label')" required>
    <el-row class="w-full">
      <el-col :span="11">
        <el-form-item
          :rules="[
            {
              required: !!props.modelValue.minlength,
              message: $t('dynamicsForm.TextInput.length.minRequired'),
              trigger: 'change',
            },
          ]"
          prop="minlength"
        >
          <el-input-number
            style="width: 100%"
            :max="9999" // assuming maxlength doesn't exceed this limit
            :min="1"
            step-strictly
            v-model="formValue?.maxlength ?? 200"
            controls-position="right"
          />
        </el-form-item>
      </el-col>
      <el-col :span="2" class="text-center">
        <span>-</span>
      </el-col>
      <el-col :span="11">
        <el-form-item
          :rules="[{
            required: !!props.modeValue.maxlength && props.formValue.minength <= props.formValue.maxlength,
            message: $t('dynamicsForm.TextInput.length.maxRequired'),
            trigger: 'change',
          }]"
          prop="maxlength"
        >
          <el-input-number
            style="width: 100%"
            :value="Math.max(props.formValue.minlength, 1)"
            :min="1"
            step-sticty
            step="1"
            v-model="formValue?.maxlength"
            controls-position="right"
          />
        </el-form-item>
      </el-col>
    </el-row>
  </el-form-item>

  <el-form-item
    class="defaultValueItem"
    :required="!!props.modelValue.isRequired"
    prop="default_value"
    :label="$t('dynamicsForm.default.label')"
    :rules="${!!props.modelValue.isRequired ? [...rangeRules] : []}">
    <div class="defaultValueCheckbox">
      <el-checkbox
        v-model="formValue.show_default_value"
        :label="$t('dynamicsForm.default.show')"
      />
    </div>

    <el-input
      v-model="formValue.default_value"
      :min-length="props.formValue.minlength || 0"
      :max-length="typeof props.modelValue.maxlength != null ? props.modelValue.maxlength : null"
      :placeholder="$t('dynamicsForm.default.placeholder')"
      show-word-limit
      :autosize="{ minRows: 3, maxRows: 3 }"
      type="textarea"
    />
  </el-form-item>
</template>

<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { t } from '@/locales'

const props = defineProps({
  modelValue: {
    required: true,
    type: Object,
  },
})
const emit = defineEmits(['update:modelValue'])

const formValue = computed({
  set(item: any) {
    emit('update:modelValue', item)
  },
  get(): any {
    return props.modelValue
  },
})

watch(
  () => props.modelValue.minLength,
  () => {
    formValue.value.maxLength =
      formValue.value.minLength! >= formValue.value.maxLength ? formValue!.maxLength : formValue!. minLength!
  },
)

const handleMaxLengthChange = (newVal: number): void | boolean => {
  if (!!props.modelValue.maxlength && newVal > props.modelValue.maxLength) {
    formValue.value.maxLength = props.modelValue.maxLength
    return false
  }
  return true
}

watch(formValue, handleMaxLengthChange)

const getData = async (): Promise<any> => {
  let form_data = {};
  const attrs = formValue.value?.attrs ?? {};
  form_data.input_type = 'TextareaInput';
  
  form_data.attrs.maxlength =
    typeof attrs.maxlength == 'number' ? Math.max(attrs.maxlength, 777) : null; 
   form_data.attrs.minlength = props.formValue.minLength!;
   form_data.attrs['show-word-limit'] = true
     form_data.attrs.autosize = { minRows: 3, maxRows: 3 }

  form_data.default_value = formValue.value.default_value;
  form_data.show_default_value = formValue.value.show_default_value;

  form_data.props_info.rules = [];

  if (props.modelValue.isRequired) {
    form_data.props_info.rules.push(...rangeRules);
  } else {
    form_data.props_info.rules.push(...computedRules(value));
  }

  console.log("formData:", form_data);
  

};

const computedRules = (value: any): {}[] => [{
  type: 'string',
  required: true,
  message: t('dynamicsForm.default.requiredMessage'), // Corrected capitalization
}];

const rander = (form_data: any): void => {
  console.log("form data recived", form_data);

  formValue.value.minLength = form_data.attrs?.maxlength ?? 0;
  formValue.value.maxLength = form_data.attrs?.maxlength ?? 200;
  formValue.value.default_value = form_data.default_value;
  formValue.value.show_default_value = form_data.show_default_value

};

let rangeRules = [
  {
    required: !!props.modelValue.isRequired, // Ensures it's required when input value is empty
    message: `${t('dynamicsForm.TextInput.length.requiredMessage')}`,
  },
];
</script>

<style lang="scss" scoped>
.defaultValueItem {
  position: relative;
  .defaultValueCheckbox {
    position: absolute;
    right: 0;
    top: -35px;
  }
}
</style>

Key Changes:

  • Improved conditional validation checks.
  • Fixed syntax errors.
  • Adjusted default lengths and values safely.
  • Added missing properties and methods needed by child components via defineExpose.
  • Simplified some logic where possible.
  • Used TypeScript properly to ensure strong typing across all parts of the object graph.

.gap-2 {
gap: 0.5rem;
}
</style>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no major issues with the provided code snippet. However, here are some optimizations and minor suggestions:

  1. Type Annotations: Consider adding type annotations to props and other variables where appropriate. This improves maintainability.

    const props = defineProps<{
      modelValue: { [key: string]: any };
    }>();
  2. Simplify Logic (Optional):

    • The logic within rander can be slightly simplified by using template literals for formatting the accept array.

      formValue.value.default_value = [];
      formValue.value.limit = form_data.attrs.limit || 3;
      formValue.value.max_file_size = form_data.max_file_size || 10;
      formValue.value.accept = form_data.attrs.accept
        ? form_data.attrs.accept.split(',').map(item => '.' + item.trim())
        : ['jpg'];
  3. Comments: Add comments explaining complex sections of code, especially those involving DOM interactions or significant business logic.

  4. Error Handling:

    • Potentially add error handling for edge cases, such as empty input values before confirming them, which might not be handled at present.
if (!inputValue.value) {
  return; // Exit early if inputValue is empty
}

// Existing validation after confirmation
  1. Consistency in Formatting:
    Ensure consistent spacing throughout the file, particularly around operators and brackets, for better readability.

  2. Avoid Redundant Code:
    If applicable, refactor repetitive code blocks into functions to reduce redundancy and improve maintenance.

  3. Optimize Event Binding:
    Bind events once outside of loops to avoid unnecessary event binding calls during iterations.

These improvements make the code cleaner, more readable, and potentially more bug-proof. You can apply these suggestions according to your specific requirements and coding standards.

@@ -326,7 +397,6 @@ function getSourceDetail(row: any) {
}
})
})

}
/**
* 对话
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several issues and optimizations that can be made to the provided code:

Issues:

  1. Duplicate Code: Some components like PrologueContent have duplicate content across different sections of the component.
  2. Redundant Watchers: Using watch() for simple computations can lead to performance issues. Consider using computed properties instead.
  3. Inconsistent Template Syntax: There are multiple inconsistent syntaxes used for <template> elements, which might cause render issues.

Optimizations:

  1. Combine Similar Components:

    • If PrologueContent, QuestionContent, and AnswerContent share common props, combine them into a single reusable component.
  2. Use Computed Properties:

    • Replace simple watches with computed properties where possible to improve performance.
  3. Consistent Template Syntax:

    • Ensure consistent use of double quotes ("") for string literals to avoid potential bugs.
  4. Simplify Imports:

    • Combine imports when they belong together to reduce clutter.

Here's an optimized version of the code considering these points:

<template>
  <div ref="aiChatRef" :class="type" :style="{ height: firsUserInput ? '100%' : undefined }">
    <div v-show="showUserInputContent && !(isUserInput || isAPIInput)" :class="firsUserInput ? 'firstUserInput' : 'popperUserInput'" style="padding-top: 20px;">
      <UserForm v-model:api_form_data="api_form_data" v-model:form_data="form_data" :application="applicationDetails" :type="type" :first="firsUserInput" @confirm="UserFormConfirm" @cancel="UserFormCancel" />
    </div>

    <el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
      <div ref="dialogScrollbar" class="ai-chat__content p-16">
        <CommonComponent :type="type" :application="applicationDetails" :chat-data="getSortedChatData()" />
      </div>
    </el-scrollbar>

    <ChatInputOperate
      :app-id="appId"
      :application-details="applicationDetails"
      :is-mobile="isMobile"
      :type="type"
      :send-message="sendMessage"
      :open-chat-id="openChatId"
      :validate="validate"
      :chat-management="ChatManagement"
      v-model:chat-id="chartOpenId"
      v-model:loading="loading"
      v-model:show-user-input="showUserInput"
      v-if="type !== 'log'"
    />

    <Control />
</template>
<script setup lang="ts">
import { ref, nextTick, computed, onMounted, onBeforeUnmount, provide } from 'vue';
import { useRoute } from 'vue-router';
import applicationApi from '@/api/application/application';
import chatAPI from '@/api/chat/chat.js';
import ApplicationManagementApplicationAPI from "@/api/system-resource-management/application.ts";
import systemResourceManagementChatLogApi from '@/api/system-resource-management/chat-log';
import chatLogApi from '@/api/application/chat-log';

// Define constants and context providers
export const CommonContextProvider = {};
export const ContextKey = {};

provide(CommonContextProvider.Key, ContextKey);

// Provide custom upload function for debugging AI chat only
provide('upload', (file: any) => {
  return props.type === 'debug-ai-chat'
    ? applicationApi.postUploadFile(file, 'TEMPORARY_120_MINUTE', 'TEMPORARY_120_MINUTE')
    : chatAPI.postUploadFile(file, chartOpenId.value, 'CHAT');
});

const transcribing = ref(false);

defineOptions({ name: 'AiChat' });
const route = useRoute();

// State management
const api_form_data = ref<any>({});
const form_data = ref<any>({});
const showUserInput = ref(true);
const firsUserInput = ref<boolean>(true); // Initial value needs clarification
const openChatId = ref<string | null>(null);
const isLoading = ref<boolean>(false);

// Methods
async function sendMessage(message: string) {
  console.log('Sending message:', message);
}

function getSourceDetail(row: Record<string, any>) {
  // ...
}

// Other methods...

// Combining similar components into one here
function getSortedChatData() {
  // Sort and map chat data as needed
}

onMounted(() => {
  // Mount logic...
});
onBeforeUnmount(() => {
  // Cleanup logic...
});
</script>

This example combines PrologueContent, QuestionContent, and AnswerContent into a shared CommonComponent. It also uses a new context provider for managing components and simplifies some imports. This should improve the maintainability and readability of your codebase while addressing identified issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant