This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new 2faa5c5074 refactor(frontend): extract HuggingFace field bindings into
a helper (#8946)
2faa5c5074 is described below
commit 2faa5c507461a1dfb589cac0699602b4a82479c8
Author: Matthew B. <[email protected]>
AuthorDate: Mon Oct 12 00:41:15 2026 +0000
refactor(frontend): extract HuggingFace field bindings into a helper (#8946)
### What changes were proposed in this PR?
- Extracted the ~185-line HuggingFace-specific field block out of
`operator-property-edit-frame.component.ts` into a pure helper
`applyHuggingFaceFieldBindings(field)` in
`workspace/util/hugging-face-field-bindings.ts`, beside the existing
`custom-formly-type.ts` that owns the sibling "which widget a field
renders as" concern.
- The component now calls the helper under a single `operatorType ===
"HuggingFace"` guard; the helper reproduces both original guards (`key
=== "task"` hide, then the string-key gate), so the task lists, `hide`
expressions, required-validators, and `validation.show` flags are
byte-identical to before.
- Added `hugging-face-field-bindings.spec.ts` with direct unit tests for
the hide/validator logic per task (image/audio inputs and their columns,
prompt column, text-generation-only controls, context, candidate labels,
sentences), coverage that was impossible while the logic was inline.
### Any related issues, documentation, discussions?
Closes: #8944
### How was this PR tested?
- Run `cd frontend && npx ng test
--include='**/hugging-face-field-bindings.spec.ts'`, expect 15 tests to
pass.
- Run `cd frontend && npx ng test
--include='**/operator-property-edit-frame.component.spec.ts'`, expect
the existing suite to pass unchanged (262 pass, 1 pre-existing skip).
- In the workflow editor, add a HuggingFace operator and change its
Task: confirm the image upload / input image column appear only for
image tasks, the audio upload / input audio column only for audio tasks,
the prompt column hides for image-only and audio tasks,
systemPrompt/maxNewTokens/temperature show only for text-generation, and
the required-field validators still block running when a needed input is
empty.
### Was this PR authored or co-authored using generative AI tooling?
Co-authored with Claude Opus 4.8 in compliance with ASF
---
.../operator-property-edit-frame.component.ts | 191 +-----------------
.../util/hugging-face-field-bindings.spec.ts | 178 ++++++++++++++++
.../workspace/util/hugging-face-field-bindings.ts | 223 +++++++++++++++++++++
3 files changed, 404 insertions(+), 188 deletions(-)
diff --git
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
index 6825eac1a0..f9c2f6f6c5 100644
---
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
+++
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
@@ -39,6 +39,7 @@ import {
} from "../../../types/custom-json-schema.interface";
import { isDefined } from "../../../../common/util/predicate";
import { customFormlyFieldType, NON_FORM_FIELD_TYPES } from
"../../../util/custom-formly-type";
+import { applyHuggingFaceFieldBindings } from
"../../../util/hugging-face-field-bindings";
import { ExecutionState, OperatorState } from
"src/app/workspace/types/execute-workflow.interface";
import { DynamicSchemaService } from
"../../../service/dynamic-schema/dynamic-schema.service";
import { WorkflowCompilingService } from
"../../../service/compile-workflow/workflow-compiling.service";
@@ -960,194 +961,8 @@ export class OperatorPropertyEditFrameComponent
implements OnInit, OnChanges, On
mappedField.type = customType;
}
- if (mappedField.key === "task" &&
this.currentOperatorSchema?.operatorType === "HuggingFace") {
- mappedField.hide = true;
- }
-
- // ── Dynamic field visibility for HuggingFace based on selected task ──
- if (this.currentOperatorSchema?.operatorType === "HuggingFace" && typeof
mappedField.key === "string") {
- const hfKey = mappedField.key;
- const imageOnlyTasks = ["image-classification", "object-detection",
"image-segmentation", "image-to-text"];
- const imageInputTasks = [
- ...imageOnlyTasks,
- "visual-question-answering",
- "document-question-answering",
- "zero-shot-image-classification",
- "image-text-to-text",
- "image-to-image",
- "image-to-video",
- ];
- const audioInputTasks = ["automatic-speech-recognition",
"audio-classification"];
- const promptRequiredTasks = [
- "text-generation",
- "text-classification",
- "token-classification",
- "question-answering",
- "table-question-answering",
- "zero-shot-classification",
- "translation",
- "summarization",
- "feature-extraction",
- "fill-mask",
- "sentence-similarity",
- "text-ranking",
- "visual-question-answering",
- "document-question-answering",
- "zero-shot-image-classification",
- ];
- const getSelectedTask = (field: FormlyFieldConfig): string | undefined
=> {
- const fromForm = field.form?.get("task")?.value ??
field.formControl?.parent?.get("task")?.value;
- if (typeof fromForm === "string" && fromForm.trim().length > 0) {
- return fromForm;
- }
- const fromModel = field.model?.task;
- if (typeof fromModel === "string" && fromModel.trim().length > 0) {
- return fromModel;
- }
- return undefined;
- };
- if (hfKey === "imageInput") {
- // type ("huggingface-image-upload") is set by customFormlyFieldType
above
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t === undefined || !imageInputTasks.includes(t);
- },
- };
- mappedField.validators = {
- ...mappedField.validators,
- requiredImageInput: {
- expression: (_control: AbstractControl, field:
FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- if (t === undefined || !imageInputTasks.includes(t)) {
- return true;
- }
- const inputImageCol = field.model?.inputImageColumn;
- if (typeof inputImageCol === "string" &&
inputImageCol.trim().length > 0) {
- return true;
- }
- const value = field.formControl?.value ??
field.model?.imageInput;
- return typeof value === "string" && value.trim().length > 0;
- },
- message: () => "Upload an image or select an Input Image Column
for this task.",
- },
- };
- mappedField.validation = {
- ...mappedField.validation,
- show: true,
- };
- }
- if (hfKey === "audioInput") {
- // type ("huggingface-audio-upload") is set by customFormlyFieldType
above
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t === undefined || !audioInputTasks.includes(t);
- },
- };
- mappedField.validators = {
- ...mappedField.validators,
- requiredAudioInput: {
- expression: (_control: AbstractControl, field:
FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- if (t === undefined || !audioInputTasks.includes(t)) {
- return true;
- }
- const inputAudioCol = field.model?.inputAudioColumn;
- if (typeof inputAudioCol === "string" &&
inputAudioCol.trim().length > 0) {
- return true;
- }
- const value = field.formControl?.value ??
field.model?.audioInput;
- return typeof value === "string" && value.trim().length > 0;
- },
- message: () => "Upload audio or select an Input Audio Column for
this task.",
- },
- };
- mappedField.validation = {
- ...mappedField.validation,
- show: true,
- };
- }
- if (hfKey === "inputImageColumn") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t === undefined || !imageInputTasks.includes(t);
- },
- };
- }
- if (hfKey === "inputAudioColumn") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t === undefined || !audioInputTasks.includes(t);
- },
- };
- }
- if (hfKey === "promptColumn") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t !== undefined && (imageOnlyTasks.includes(t) ||
audioInputTasks.includes(t));
- },
- };
- mappedField.validators = {
- ...mappedField.validators,
- requiredPromptColumn: {
- expression: (_control: AbstractControl, field:
FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- if (t === undefined || !promptRequiredTasks.includes(t)) {
- return true;
- }
- const value = field.formControl?.value ??
field.model?.promptColumn;
- return typeof value === "string" && value.trim().length > 0;
- },
- message: () => "Select a prompt column for this task.",
- },
- };
- mappedField.validation = {
- ...mappedField.validation,
- show: true,
- };
- }
- if (["systemPrompt", "maxNewTokens", "temperature"].includes(hfKey)) {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t !== "text-generation";
- },
- };
- }
- if (hfKey === "contextColumn") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => getSelectedTask(field) !==
"question-answering",
- };
- }
- if (hfKey === "candidateLabels") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t !== "zero-shot-classification" && t !==
"zero-shot-image-classification";
- },
- };
- }
- if (hfKey === "sentencesColumn") {
- mappedField.expressions = {
- ...mappedField.expressions,
- hide: (field: FormlyFieldConfig) => {
- const t = getSelectedTask(field);
- return t !== "sentence-similarity" && t !== "text-ranking";
- },
- };
- }
+ if (this.currentOperatorSchema?.operatorType === "HuggingFace") {
+ applyHuggingFaceFieldBindings(mappedField);
}
// Show the required marker for a field the schema requires
conditionally,
diff --git
a/frontend/src/app/workspace/util/hugging-face-field-bindings.spec.ts
b/frontend/src/app/workspace/util/hugging-face-field-bindings.spec.ts
new file mode 100644
index 0000000000..05de5e0266
--- /dev/null
+++ b/frontend/src/app/workspace/util/hugging-face-field-bindings.spec.ts
@@ -0,0 +1,178 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { AbstractControl } from "@angular/forms";
+import { FormlyFieldConfig } from "@ngx-formly/core";
+import { applyHuggingFaceFieldBindings } from "./hugging-face-field-bindings";
+
+// Builds a field with the given key, runs the bindings on it, and returns it
ready to probe.
+function bind(key: unknown): FormlyFieldConfig {
+ const field: FormlyFieldConfig = { key: key as FormlyFieldConfig["key"] };
+ applyHuggingFaceFieldBindings(field);
+ return field;
+}
+
+// Invokes the field's compiled hide expression against a model carrying the
selected task.
+function hiddenForTask(field: FormlyFieldConfig, task?: string): boolean {
+ const hide = field.expressions?.hide as (f: FormlyFieldConfig) => boolean;
+ return hide({ model: task === undefined ? {} : { task } } as
FormlyFieldConfig);
+}
+
+// Invokes a named validator's expression (true = valid) against a given field
state.
+function validatorPasses(
+ field: FormlyFieldConfig,
+ name: string,
+ state: { task?: string; value?: string; model?: Record<string, unknown> }
+): boolean {
+ const validator = (field.validators as Record<string, { expression: Function
}>)[name];
+ const probe = {
+ model: { task: state.task, ...(state.model ?? {}) },
+ formControl: { value: state.value },
+ } as unknown as FormlyFieldConfig;
+ return validator.expression({} as AbstractControl, probe);
+}
+
+describe("applyHuggingFaceFieldBindings", () => {
+ it("hides the task field itself", () => {
+ expect(bind("task").hide).toBe(true);
+ });
+
+ it("does nothing for a non-string key", () => {
+ const field = bind(undefined);
+ expect(field.expressions).toBeUndefined();
+ expect(field.validators).toBeUndefined();
+ });
+
+ it("does nothing for a key the operator does not drive", () => {
+ const field = bind("modelId");
+ expect(field.expressions).toBeUndefined();
+ expect(field.validators).toBeUndefined();
+ });
+
+ describe("imageInput", () => {
+ it("shows for an image task and hides otherwise", () => {
+ const field = bind("imageInput");
+ expect(hiddenForTask(field, "image-classification")).toBe(false);
+ expect(hiddenForTask(field, "visual-question-answering")).toBe(false);
+ expect(hiddenForTask(field, "text-generation")).toBe(true);
+ expect(hiddenForTask(field, undefined)).toBe(true);
+ });
+
+ it("requires an upload or an Input Image Column only for an image task",
() => {
+ const field = bind("imageInput");
+ // non-image task: always valid
+ expect(validatorPasses(field, "requiredImageInput", { task:
"text-generation" })).toBe(true);
+ // image task, nothing provided: invalid
+ expect(validatorPasses(field, "requiredImageInput", { task:
"image-classification" })).toBe(false);
+ // satisfied by an uploaded value
+ expect(validatorPasses(field, "requiredImageInput", { task:
"image-classification", value: "pic.png" })).toBe(
+ true
+ );
+ // satisfied by an Input Image Column instead
+ expect(
+ validatorPasses(field, "requiredImageInput", {
+ task: "image-classification",
+ model: { inputImageColumn: "col" },
+ })
+ ).toBe(true);
+ });
+ });
+
+ describe("audioInput", () => {
+ it("shows for an audio task and hides otherwise", () => {
+ const field = bind("audioInput");
+ expect(hiddenForTask(field, "automatic-speech-recognition")).toBe(false);
+ expect(hiddenForTask(field, "image-classification")).toBe(true);
+ });
+
+ it("requires an upload or an Input Audio Column only for an audio task",
() => {
+ const field = bind("audioInput");
+ expect(validatorPasses(field, "requiredAudioInput", { task:
"text-generation" })).toBe(true);
+ expect(validatorPasses(field, "requiredAudioInput", { task:
"audio-classification" })).toBe(false);
+ expect(validatorPasses(field, "requiredAudioInput", { task:
"audio-classification", value: "a.wav" })).toBe(true);
+ expect(
+ validatorPasses(field, "requiredAudioInput", {
+ task: "audio-classification",
+ model: { inputAudioColumn: "col" },
+ })
+ ).toBe(true);
+ });
+ });
+
+ it("hides inputImageColumn / inputAudioColumn by their task families", () =>
{
+ expect(hiddenForTask(bind("inputImageColumn"),
"object-detection")).toBe(false);
+ expect(hiddenForTask(bind("inputImageColumn"),
"audio-classification")).toBe(true);
+ expect(hiddenForTask(bind("inputAudioColumn"),
"audio-classification")).toBe(false);
+ expect(hiddenForTask(bind("inputAudioColumn"),
"object-detection")).toBe(true);
+ });
+
+ describe("promptColumn", () => {
+ it("hides only for image-only and audio tasks", () => {
+ const field = bind("promptColumn");
+ expect(hiddenForTask(field, "image-classification")).toBe(true);
+ expect(hiddenForTask(field, "audio-classification")).toBe(true);
+ expect(hiddenForTask(field, "text-generation")).toBe(false);
+ expect(hiddenForTask(field, "visual-question-answering")).toBe(false);
+ expect(hiddenForTask(field, undefined)).toBe(false);
+ });
+
+ it("requires a value only for a prompt-required task", () => {
+ const field = bind("promptColumn");
+ expect(validatorPasses(field, "requiredPromptColumn", { task:
"image-classification" })).toBe(true);
+ expect(validatorPasses(field, "requiredPromptColumn", { task:
"summarization" })).toBe(false);
+ expect(validatorPasses(field, "requiredPromptColumn", { task:
"summarization", value: "col" })).toBe(true);
+ });
+ });
+
+ it("shows text-generation-only controls solely for text-generation", () => {
+ for (const key of ["systemPrompt", "maxNewTokens", "temperature"]) {
+ const field = bind(key);
+ expect(hiddenForTask(field, "text-generation")).toBe(false);
+ expect(hiddenForTask(field, "summarization")).toBe(true);
+ }
+ });
+
+ it("shows contextColumn only for question-answering", () => {
+ const field = bind("contextColumn");
+ expect(hiddenForTask(field, "question-answering")).toBe(false);
+ expect(hiddenForTask(field, "text-generation")).toBe(true);
+ });
+
+ it("shows candidateLabels only for the zero-shot tasks", () => {
+ const field = bind("candidateLabels");
+ expect(hiddenForTask(field, "zero-shot-classification")).toBe(false);
+ expect(hiddenForTask(field, "zero-shot-image-classification")).toBe(false);
+ expect(hiddenForTask(field, "text-classification")).toBe(true);
+ });
+
+ it("shows sentencesColumn only for similarity and ranking tasks", () => {
+ const field = bind("sentencesColumn");
+ expect(hiddenForTask(field, "sentence-similarity")).toBe(false);
+ expect(hiddenForTask(field, "text-ranking")).toBe(false);
+ expect(hiddenForTask(field, "translation")).toBe(true);
+ });
+
+ it("preserves expressions already present on the field", () => {
+ const existing = () => false;
+ const field: FormlyFieldConfig = { key: "contextColumn", expressions: {
"props.required": existing } };
+ applyHuggingFaceFieldBindings(field);
+ expect(field.expressions?.["props.required"]).toBe(existing);
+ expect(typeof field.expressions?.hide).toBe("function");
+ });
+});
diff --git a/frontend/src/app/workspace/util/hugging-face-field-bindings.ts
b/frontend/src/app/workspace/util/hugging-face-field-bindings.ts
new file mode 100644
index 0000000000..a3f31e5629
--- /dev/null
+++ b/frontend/src/app/workspace/util/hugging-face-field-bindings.ts
@@ -0,0 +1,223 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { AbstractControl } from "@angular/forms";
+import { FormlyFieldConfig } from "@ngx-formly/core";
+
+const imageOnlyTasks = ["image-classification", "object-detection",
"image-segmentation", "image-to-text"];
+const imageInputTasks = [
+ ...imageOnlyTasks,
+ "visual-question-answering",
+ "document-question-answering",
+ "zero-shot-image-classification",
+ "image-text-to-text",
+ "image-to-image",
+ "image-to-video",
+];
+const audioInputTasks = ["automatic-speech-recognition",
"audio-classification"];
+const promptRequiredTasks = [
+ "text-generation",
+ "text-classification",
+ "token-classification",
+ "question-answering",
+ "table-question-answering",
+ "zero-shot-classification",
+ "translation",
+ "summarization",
+ "feature-extraction",
+ "fill-mask",
+ "sentence-similarity",
+ "text-ranking",
+ "visual-question-answering",
+ "document-question-answering",
+ "zero-shot-image-classification",
+];
+
+const getSelectedTask = (field: FormlyFieldConfig): string | undefined => {
+ const fromForm = field.form?.get("task")?.value ??
field.formControl?.parent?.get("task")?.value;
+ if (typeof fromForm === "string" && fromForm.trim().length > 0) {
+ return fromForm;
+ }
+ const fromModel = field.model?.task;
+ if (typeof fromModel === "string" && fromModel.trim().length > 0) {
+ return fromModel;
+ }
+ return undefined;
+};
+
+/**
+ * Applies the HuggingFace operator's task-driven field behaviour to a single
formly field: the
+ * `task` field is hidden, and each input field (image/audio uploads and their
column pickers, the
+ * prompt column, text-generation controls, context, candidate labels,
sentences) gets the hide
+ * expression and required-validator that the currently selected task calls
for. A no-op for any
+ * field whose key the HuggingFace operator does not drive. The widget a field
renders as is decided
+ * separately in {@link customFormlyFieldType}.
+ */
+export function applyHuggingFaceFieldBindings(mappedField: FormlyFieldConfig):
void {
+ if (mappedField.key === "task") {
+ mappedField.hide = true;
+ }
+
+ if (typeof mappedField.key !== "string") {
+ return;
+ }
+ const hfKey = mappedField.key;
+ if (hfKey === "imageInput") {
+ // type ("huggingface-image-upload") is set by customFormlyFieldType
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t === undefined || !imageInputTasks.includes(t);
+ },
+ };
+ mappedField.validators = {
+ ...mappedField.validators,
+ requiredImageInput: {
+ expression: (_control: AbstractControl, field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ if (t === undefined || !imageInputTasks.includes(t)) {
+ return true;
+ }
+ const inputImageCol = field.model?.inputImageColumn;
+ if (typeof inputImageCol === "string" && inputImageCol.trim().length
> 0) {
+ return true;
+ }
+ const value = field.formControl?.value ?? field.model?.imageInput;
+ return typeof value === "string" && value.trim().length > 0;
+ },
+ message: () => "Upload an image or select an Input Image Column for
this task.",
+ },
+ };
+ mappedField.validation = {
+ ...mappedField.validation,
+ show: true,
+ };
+ }
+ if (hfKey === "audioInput") {
+ // type ("huggingface-audio-upload") is set by customFormlyFieldType
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t === undefined || !audioInputTasks.includes(t);
+ },
+ };
+ mappedField.validators = {
+ ...mappedField.validators,
+ requiredAudioInput: {
+ expression: (_control: AbstractControl, field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ if (t === undefined || !audioInputTasks.includes(t)) {
+ return true;
+ }
+ const inputAudioCol = field.model?.inputAudioColumn;
+ if (typeof inputAudioCol === "string" && inputAudioCol.trim().length
> 0) {
+ return true;
+ }
+ const value = field.formControl?.value ?? field.model?.audioInput;
+ return typeof value === "string" && value.trim().length > 0;
+ },
+ message: () => "Upload audio or select an Input Audio Column for this
task.",
+ },
+ };
+ mappedField.validation = {
+ ...mappedField.validation,
+ show: true,
+ };
+ }
+ if (hfKey === "inputImageColumn") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t === undefined || !imageInputTasks.includes(t);
+ },
+ };
+ }
+ if (hfKey === "inputAudioColumn") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t === undefined || !audioInputTasks.includes(t);
+ },
+ };
+ }
+ if (hfKey === "promptColumn") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t !== undefined && (imageOnlyTasks.includes(t) ||
audioInputTasks.includes(t));
+ },
+ };
+ mappedField.validators = {
+ ...mappedField.validators,
+ requiredPromptColumn: {
+ expression: (_control: AbstractControl, field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ if (t === undefined || !promptRequiredTasks.includes(t)) {
+ return true;
+ }
+ const value = field.formControl?.value ?? field.model?.promptColumn;
+ return typeof value === "string" && value.trim().length > 0;
+ },
+ message: () => "Select a prompt column for this task.",
+ },
+ };
+ mappedField.validation = {
+ ...mappedField.validation,
+ show: true,
+ };
+ }
+ if (["systemPrompt", "maxNewTokens", "temperature"].includes(hfKey)) {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t !== "text-generation";
+ },
+ };
+ }
+ if (hfKey === "contextColumn") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => getSelectedTask(field) !==
"question-answering",
+ };
+ }
+ if (hfKey === "candidateLabels") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t !== "zero-shot-classification" && t !==
"zero-shot-image-classification";
+ },
+ };
+ }
+ if (hfKey === "sentencesColumn") {
+ mappedField.expressions = {
+ ...mappedField.expressions,
+ hide: (field: FormlyFieldConfig) => {
+ const t = getSelectedTask(field);
+ return t !== "sentence-similarity" && t !== "text-ranking";
+ },
+ };
+ }
+}