-
+
{positions.map((position) => {
return (
diff --git a/web/src/pages/agent/form/categorize-form/index.test.tsx b/web/src/pages/agent/form/categorize-form/index.test.tsx
new file mode 100644
index 0000000000..f255ab3485
--- /dev/null
+++ b/web/src/pages/agent/form/categorize-form/index.test.tsx
@@ -0,0 +1,94 @@
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import CategorizeForm from './index';
+
+const mockUpdateNodeForm = jest.fn();
+const mockModels = [
+ {
+ model_id: 'chat-id',
+ model_type: ['chat'],
+ name: 'chat-model',
+ provider_name: 'OpenAI',
+ instance_name: 'default',
+ },
+ {
+ model_id: 'decision-id',
+ model_type: ['decision'],
+ name: 'decision-model-preview',
+ provider_name: 'SystemOne',
+ instance_name: 'default',
+ },
+];
+const mockFetchModels = jest.fn().mockReturnValue({
+ data: mockModels,
+ isFetched: true,
+ isError: false,
+ loading: false,
+});
+
+jest.mock('@/hooks/use-llm-request', () => ({
+ useFetchAllAddedModels: (...args: unknown[]) => mockFetchModels(...args),
+}));
+jest.mock('@/components/svg-icon', () => ({ LlmIcon: () => null }));
+jest.mock('@/components/llm-select/llm-label', () => ({
+ __esModule: true,
+ default: ({ value }: { value: string }) => {value},
+ MissingModelLabel: ({ value }: { value: string }) => (
+ Missing: {value}
+ ),
+}));
+jest.mock('@/components/llm-setting-items/next', () => ({
+ LlmSettingSchema: { llm_id: require('zod').z.string() },
+}));
+jest.mock('@/components/message-history-window-size-item', () => ({
+ MessageHistoryWindowSizeFormField: () => null,
+}));
+jest.mock('../../constant', () => ({
+ initialCategorizeValues: { outputs: {} },
+}));
+jest.mock('../../context', () => ({ useOwnerTenantId: () => 'canvas-owner' }));
+jest.mock('../../store', () => ({
+ __esModule: true,
+ default: (select: any) => select({ updateNodeForm: mockUpdateNodeForm }),
+}));
+jest.mock('../components/output', () => ({ Output: () => null }));
+jest.mock('../components/query-variable', () => ({
+ QueryVariable: () => null,
+}));
+jest.mock('./dynamic-categorize', () => ({
+ __esModule: true,
+ default: () => null,
+}));
+
+it('offers chat and decision models and saves the selected decision model ID', async () => {
+ render(
+ ,
+ );
+
+ fireEvent.click(
+ screen.getByRole('button', { name: 'chat-model@default@OpenAI' }),
+ );
+ expect(screen.getByText('chat-model')).toBeInTheDocument();
+ fireEvent.click(screen.getByText('decision-model-preview'));
+
+ await waitFor(() => {
+ expect(mockUpdateNodeForm).toHaveBeenLastCalledWith(
+ 'Categorize:1',
+ expect.objectContaining({ llm_id: 'decision-id' }),
+ );
+ });
+ expect(screen.queryByText('Missing: decision-id')).toBeNull();
+ expect(mockFetchModels).toHaveBeenCalledWith(undefined, 'canvas-owner');
+});
diff --git a/web/src/pages/agent/form/categorize-form/index.tsx b/web/src/pages/agent/form/categorize-form/index.tsx
index 1c3a403eca..31ad6764c6 100644
--- a/web/src/pages/agent/form/categorize-form/index.tsx
+++ b/web/src/pages/agent/form/categorize-form/index.tsx
@@ -1,10 +1,11 @@
-import { LargeModelFormField } from '@/components/large-model-form-field';
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
+import { ModelTreeSelectFormField } from '@/components/model-tree-select';
import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
import { initialCategorizeValues } from '../../constant';
import { useOwnerTenantId } from '../../context';
import { INextOperatorForm } from '../../interface';
@@ -20,6 +21,7 @@ import { useWatchFormChange } from './use-watch-change';
const outputList = buildOutputList(initialCategorizeValues.outputs);
function CategorizeForm({ node }: INextOperatorForm) {
+ const { t } = useTranslation();
const values = useValues(node);
const FormSchema = useCreateCategorizeFormSchema();
@@ -37,9 +39,13 @@ function CategorizeForm({ node }: INextOperatorForm) {