From a7abc57f68e1c91c18fea067962e2b0d4f6b673a Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 18 Sep 2025 09:29:33 +0800 Subject: [PATCH] Feat: Add SliderInputFormField story #9869 (#10138) ### What problem does this PR solve? Feat: Add SliderInputFormField story #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/locales/en.ts | 4 + web/src/locales/zh.ts | 4 + .../pages/data-flow/hooks/use-show-drawer.tsx | 8 +- web/src/pages/data-flow/run-sheet/index.tsx | 13 +- .../pages/data-flow/run-sheet/uploader.tsx | 108 +++++++++++ .../slider-input-form-field.stories.tsx | 171 ++++++++++++++++++ 6 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 web/src/pages/data-flow/run-sheet/uploader.tsx create mode 100644 web/src/stories/slider-input-form-field.stories.tsx diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 9a4dd3014..0e73bf246 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1636,6 +1636,10 @@ This delimiter is used to split the input text into several text pieces echo of chunkerDescription: 'Chunker', tokenizer: 'Tokenizer', tokenizerDescription: 'Tokenizer', + splitter: 'Splitter', + splitterDescription: 'Splitter', + hierarchicalMergerDescription: 'Hierarchical merger', + hierarchicalMerger: 'Hierarchical merger', outputFormat: 'Output format', lang: 'Language', fileFormats: 'File formats', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index b81c39fac..de3b11d9c 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1544,6 +1544,10 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 chunkerDescription: '分块器', tokenizer: '分词器', tokenizerDescription: '分词器', + splitter: '拆分器', + splitterDescription: '拆分器', + hierarchicalMergerDesription: '分层合并', + hierarchicalMerger: '分层合并', outputFormat: '输出格式', lang: '语言', fileFormats: '文件格式', diff --git a/web/src/pages/data-flow/hooks/use-show-drawer.tsx b/web/src/pages/data-flow/hooks/use-show-drawer.tsx index 6789e86ba..383eddd65 100644 --- a/web/src/pages/data-flow/hooks/use-show-drawer.tsx +++ b/web/src/pages/data-flow/hooks/use-show-drawer.tsx @@ -91,13 +91,7 @@ export function useShowDrawer({ useEffect(() => { if (drawerVisible) { - if (inputs.length > 0) { - showRunModal(); - hideChatModal(); - } else { - showChatModal(); - hideRunModal(); - } + showRunModal(); } }, [ hideChatModal, diff --git a/web/src/pages/data-flow/run-sheet/index.tsx b/web/src/pages/data-flow/run-sheet/index.tsx index cac62d008..18d043ffa 100644 --- a/web/src/pages/data-flow/run-sheet/index.tsx +++ b/web/src/pages/data-flow/run-sheet/index.tsx @@ -9,12 +9,11 @@ import { cn } from '@/lib/utils'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { BeginId } from '../constant'; -import DebugContent from '../debug-content'; -import { useGetBeginNodeDataInputs } from '../hooks/use-get-begin-query'; import { useSaveGraphBeforeOpeningDebugDrawer } from '../hooks/use-save-graph'; import { BeginQuery } from '../interface'; import useGraphStore from '../store'; import { buildBeginQueryWithObject } from '../utils'; +import { Uploader } from './uploader'; const RunSheet = ({ hideModal, @@ -23,8 +22,6 @@ const RunSheet = ({ const { t } = useTranslation(); const { updateNodeForm, getNode } = useGraphStore((state) => state); - const inputs = useGetBeginNodeDataInputs(); - const { handleRun, loading } = useSaveGraphBeforeOpeningDebugDrawer( showChatModal!, ); @@ -51,15 +48,11 @@ const RunSheet = ({ ); return ( - + {t('flow.testRun')} - + diff --git a/web/src/pages/data-flow/run-sheet/uploader.tsx b/web/src/pages/data-flow/run-sheet/uploader.tsx new file mode 100644 index 000000000..44969df25 --- /dev/null +++ b/web/src/pages/data-flow/run-sheet/uploader.tsx @@ -0,0 +1,108 @@ +'use client'; + +import { + FileUpload, + FileUploadDropzone, + FileUploadItem, + FileUploadItemDelete, + FileUploadItemMetadata, + FileUploadItemPreview, + FileUploadItemProgress, + FileUploadList, + FileUploadTrigger, + type FileUploadProps, +} from '@/components/file-upload'; +import { Button } from '@/components/ui/button'; +import { Upload, X } from 'lucide-react'; +import * as React from 'react'; +import { toast } from 'sonner'; + +export function Uploader() { + const [isUploading, setIsUploading] = React.useState(false); + const [files, setFiles] = React.useState([]); + + const onUpload: NonNullable = React.useCallback( + async (files, { onProgress }) => { + try { + setIsUploading(true); + // const res = await uploadFiles('imageUploader', { + // files, + // onUploadProgress: ({ file, progress }) => { + // onProgress(file, progress); + // }, + // }); + } catch (error) { + setIsUploading(false); + + // if (error instanceof UploadThingError) { + // const errorMessage = + // error.data && 'error' in error.data + // ? error.data.error + // : 'Upload failed'; + // toast.error(errorMessage); + // return; + // } + + toast.error( + error instanceof Error ? error.message : 'An unknown error occurred', + ); + } finally { + setIsUploading(false); + } + }, + [], + ); + + const onFileReject = React.useCallback((file: File, message: string) => { + toast(message, { + description: `"${file.name.length > 20 ? `${file.name.slice(0, 20)}...` : file.name}" has been rejected`, + }); + }, []); + + return ( + setFiles(files)} + onUpload={onUpload} + onFileReject={onFileReject} + multiple + disabled={isUploading} + > + +
+
+ +
+

Drag & drop images here

+

+ Or click to browse (max 2 files, up to 4MB each) +

+
+ + + +
+ + {files.map((file, index) => ( + +
+ + + + + +
+ +
+ ))} +
+
+ ); +} diff --git a/web/src/stories/slider-input-form-field.stories.tsx b/web/src/stories/slider-input-form-field.stories.tsx new file mode 100644 index 000000000..90bb13260 --- /dev/null +++ b/web/src/stories/slider-input-form-field.stories.tsx @@ -0,0 +1,171 @@ +import { Form } from '@/components/ui/form'; +import type { Meta, StoryObj } from '@storybook/react-webpack5'; +import { useForm } from 'react-hook-form'; + +import { SliderInputFormField } from '@/components/slider-input-form-field'; +import { FormLayout } from '@/constants/form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Example/SliderInputFormField', + component: SliderInputFormField, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +## Component Description + +SliderInputFormField is a form field component that combines a slider and a numeric input field. +It provides a user-friendly way to select numeric values within a specified range. `, + }, + }, + }, + tags: ['autodocs'], + argTypes: { + name: { control: 'text' }, + label: { control: 'text' }, + min: { control: 'number' }, + max: { control: 'number' }, + step: { control: 'number' }, + defaultValue: { control: 'number' }, + layout: { + control: 'select', + options: [FormLayout.Vertical, FormLayout.Horizontal], + }, + }, + args: { + name: 'sliderValue', + label: 'Slider Value', + min: 0, + max: 100, + step: 1, + defaultValue: 50, + }, +} satisfies Meta; + +// Form wrapper decorator +const WithFormProvider = ({ children }: { children: React.ReactNode }) => { + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(z.object({})), + }); + return
{children}
; +}; + +const withFormProvider = (Story: any) => ( + + + +); + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Default: Story = { + decorators: [withFormProvider], + args: { + name: 'sliderValue', + label: 'Slider Value', + min: 0, + max: 100, + step: 1, + defaultValue: 50, + }, + parameters: { + docs: { + description: { + story: ` +### Basic Usage + +\`\`\`tsx +import { SliderInputFormField } from '@/components/slider-input-form-field'; + + +\`\`\` + `, + }, + }, + }, +}; + +export const HorizontalLayout: Story = { + decorators: [withFormProvider], + args: { + name: 'horizontalSlider', + label: 'Horizontal Slider', + min: 0, + max: 200, + step: 5, + defaultValue: 100, + layout: FormLayout.Horizontal, + }, + parameters: { + docs: { + description: { + story: ` +### Horizontal Layout + +\`\`\`tsx +import { SliderInputFormField } from '@/components/slider-input-form-field'; +import { FormLayout } from '@/constants/form'; + + +\`\`\` + `, + }, + }, + }, +}; + +export const CustomRange: Story = { + decorators: [withFormProvider], + args: { + name: 'customRange', + label: 'Custom Range (0-1000)', + min: 0, + max: 1000, + step: 10, + defaultValue: 500, + }, + parameters: { + docs: { + description: { + story: ` +### Custom Range + +\`\`\`tsx +import { SliderInputFormField } from '@/components/slider-input-form-field'; + + +\`\`\` + `, + }, + }, + }, +};