feat(dify-ui): add textarea primitive (#36547)

This commit is contained in:
yyh
2026-05-26 10:33:32 +08:00
committed by GitHub
parent a728e0ac69
commit 884e2b864b
94 changed files with 795 additions and 936 deletions

View File

@@ -1757,14 +1757,6 @@
"count": 1
}
},
"web/app/components/base/textarea/index.stories.tsx": {
"no-console": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
},
"web/app/components/base/voice-input/__tests__/index.spec.tsx": {
"ts/no-explicit-any": {
"count": 3

View File

@@ -34,6 +34,7 @@ import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
import { Textarea } from '@langgenius/dify-ui/textarea'
import '@langgenius/dify-ui/styles.css' // once, in the app root
```
@@ -41,17 +42,17 @@ Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported
## Primitives
| Category | Subpath | Notes |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Actions | `./button` | Design-system CTA primitive with `cva` variants. |
| Controls | `./segmented-control` | SegmentedControl for mode, filter, and view selection. |
| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. |
| Form | `./form`, `./field`, `./fieldset`, `./input`, `./checkbox`, `./checkbox-group`, `./radio`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. |
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
| Media | `./avatar` | Avatar root, image, and fallback primitives. |
| Navigation | `./pagination`, `./tabs` | Pagination for page navigation; Tabs for panels. |
| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. |
| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. |
| Category | Subpath | Notes |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| Actions | `./button` | Design-system CTA primitive with `cva` variants. |
| Controls | `./segmented-control` | SegmentedControl for mode, filter, and view selection. |
| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. |
| Form | `./form`, `./field`, `./fieldset`, `./input`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. |
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
| Media | `./avatar` | Avatar root, image, and fallback primitives. |
| Navigation | `./pagination`, `./tabs` | Pagination for page navigation; Tabs for panels. |
| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. |
| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. |
Utilities:
@@ -72,7 +73,7 @@ Use `Form` for the submit boundary. It renders a native `<form>`, preserves Ente
Use `FieldRoot` for each standalone named field. A field must have a stable `name`, a label relationship, and either a `FieldControl` or another control that participates in the same Base UI field context. Prefer a visible label for normal form rows; when the surrounding UI already supplies the visible text, use the matching label primitive visually hidden or put `aria-label` on the actual interactive control. `FieldDescription` and `FieldError` provide the message relationships that screen readers need, while the Dify wrapper adds the default Form Input Set styling from the design system.
Choose the label primitive by the control semantics. Text-like inputs, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels.
Choose the label primitive by the control semantics. Text-like inputs, `Textarea`, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels.
Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label:

View File

@@ -129,6 +129,10 @@
"types": "./src/tabs/index.tsx",
"import": "./src/tabs/index.tsx"
},
"./textarea": {
"types": "./src/textarea/index.tsx",
"import": "./src/textarea/index.tsx"
},
"./toast": {
"types": "./src/toast/index.tsx",
"import": "./src/toast/index.tsx"

View File

@@ -0,0 +1,187 @@
import type { FocusEvent } from 'react'
import { render } from 'vitest-browser-react'
import {
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../../field'
import { Form } from '../../form'
import { Textarea } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
const setTextareaValue = (element: HTMLElement | SVGElement, value: string) => {
const textarea = asHTMLElement(element) as HTMLTextAreaElement
const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set
valueSetter?.call(textarea, value)
textarea.dispatchEvent(new Event('input', { bubbles: true }))
}
describe('Textarea', () => {
it('should render a labelled textarea through Base UI Field.Control', async () => {
const screen = await render(
<FieldRoot name="description">
<FieldLabel>Description</FieldLabel>
<Textarea defaultValue="A workspace for support automation." />
<FieldDescription>Shown to workspace members.</FieldDescription>
</FieldRoot>,
)
const textarea = screen.getByRole('textbox', { name: 'Description' })
await expect.element(textarea).toHaveValue('A workspace for support automation.')
await expect.element(textarea).toHaveAccessibleDescription('Shown to workspace members.')
await expect.element(textarea).toHaveClass('min-h-20', 'overflow-auto', 'rounded-lg', 'system-sm-regular')
expect(asHTMLElement(textarea.element()).tagName).toBe('TEXTAREA')
})
it('should apply size variants and custom classes', async () => {
const screen = await render(
<label>
Prompt
<Textarea size="large" className="resize-none" />
</label>,
)
await expect.element(screen.getByRole('textbox', { name: 'Prompt' })).toHaveClass(
'rounded-[10px]',
'px-4',
'py-2',
'system-md-regular',
'resize-none',
)
})
it('should call onValueChange and stay controlled until value changes', async () => {
const onValueChange = vi.fn()
const screen = await render(
<label>
Notes
<Textarea value="" onValueChange={onValueChange} />
</label>,
)
const textarea = screen.getByRole('textbox', { name: 'Notes' })
setTextareaValue(textarea.element(), 'a')
expect(onValueChange).toHaveBeenCalledWith('a', expect.any(Object))
await expect.element(textarea).toHaveValue('')
await screen.rerender(
<label>
Notes
<Textarea value="a" onValueChange={onValueChange} />
</label>,
)
await expect.element(screen.getByRole('textbox', { name: 'Notes' })).toHaveValue('a')
})
it('should submit valid values and show validation errors through Base UI Form', async () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
<FieldRoot name="summary">
<FieldLabel>Summary</FieldLabel>
<Textarea required minLength={10} />
<FieldError match="valueMissing">Summary is required.</FieldError>
<FieldError match="tooShort">Summary is too short.</FieldError>
</FieldRoot>
<button type="submit">Save</button>
</Form>,
)
const saveButton = asHTMLElement(screen.getByRole('button', { name: 'Save' }).element())
saveButton.click()
await vi.waitFor(async () => {
await expect.element(screen.getByText('Summary is required.')).toBeInTheDocument()
await expect.element(screen.getByRole('textbox', { name: 'Summary' })).toHaveAttribute('aria-invalid', 'true')
})
expect(onFormSubmit).not.toHaveBeenCalled()
await screen.rerender(
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
<FieldRoot name="summary">
<FieldLabel>Summary</FieldLabel>
<Textarea key="valid-summary" required minLength={10} defaultValue="Long enough summary" />
<FieldError match="valueMissing">Summary is required.</FieldError>
<FieldError match="tooShort">Summary is too short.</FieldError>
</FieldRoot>
<button type="submit">Save</button>
</Form>,
)
asHTMLElement(screen.getByRole('button', { name: 'Save' }).element()).click()
expect(onFormSubmit).toHaveBeenCalledTimes(1)
expect(onFormSubmit.mock.calls[0]?.[0]).toMatchObject({ summary: 'Long enough summary' })
})
it('should pass maxLength to the textarea without rendering a counter', async () => {
const screen = await render(
<label>
Release notes
<Textarea defaultValue="Draft" maxLength={20} />
</label>,
)
const textarea = screen.getByRole('textbox', { name: 'Release notes' })
await expect.element(textarea).toHaveAttribute('maxLength', '20')
expect(screen.container.textContent).not.toContain('5/20')
})
it('should route field props through Base UI Field.Control and textarea-only props to textarea', async () => {
const onFormSubmit = vi.fn()
const onBlur = vi.fn((event: FocusEvent<HTMLTextAreaElement>) => {
expect(event.currentTarget.tagName).toBe('TEXTAREA')
})
const screen = await render(
<Form aria-label="profile form" onFormSubmit={onFormSubmit}>
<FieldRoot name="profileSummary">
<FieldLabel>Profile summary</FieldLabel>
<Textarea
id="profile-summary"
name="ignoredControlName"
defaultValue="Long enough summary"
rows={6}
cols={40}
wrap="soft"
maxLength={80}
onBlur={onBlur}
/>
</FieldRoot>
<FieldRoot disabled>
<FieldLabel>Disabled note</FieldLabel>
<Textarea name="disabledNote" defaultValue="Disabled value" />
</FieldRoot>
<button type="submit">Save</button>
</Form>,
)
const profileSummary = screen.getByRole('textbox', { name: 'Profile summary' })
expect(
asHTMLElement(screen.getByText('Profile summary').element()).getAttribute('for'),
).toBe('profile-summary')
await expect.element(profileSummary).toHaveAttribute('id', 'profile-summary')
await expect.element(profileSummary).toHaveAttribute('name', 'profileSummary')
await expect.element(profileSummary).toHaveAttribute('rows', '6')
await expect.element(profileSummary).toHaveAttribute('cols', '40')
await expect.element(profileSummary).toHaveAttribute('wrap', 'soft')
await expect.element(profileSummary).toHaveAttribute('maxLength', '80')
await expect.element(screen.getByRole('textbox', { name: 'Disabled note' })).toBeDisabled()
asHTMLElement(profileSummary.element()).focus()
const saveButton = asHTMLElement(screen.getByRole('button', { name: 'Save' }).element())
saveButton.focus()
expect(onBlur).toHaveBeenCalledTimes(1)
saveButton.click()
expect(onFormSubmit).toHaveBeenCalledTimes(1)
expect(onFormSubmit.mock.calls[0]?.[0]).toMatchObject({
profileSummary: 'Long enough summary',
})
expect(onFormSubmit.mock.calls[0]?.[0]).not.toHaveProperty('ignoredControlName')
expect(onFormSubmit.mock.calls[0]?.[0]).not.toHaveProperty('disabledNote')
})
})

View File

@@ -0,0 +1,193 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import { Button } from '../button'
import {
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../field'
import { Form } from '../form'
import { Textarea } from './index'
const meta = {
title: 'Base/Form/Textarea',
component: Textarea,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Multiline text control built on Base UI Field.Control. Use it with FieldRoot for labelled, described, and validated form fields.',
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof Textarea>
export default meta
type Story = StoryObj<typeof meta>
export const Basic: Story = {
render: () => (
<div className="w-80">
<label htmlFor="workspace-description" className="mb-1 block w-fit py-1 text-text-secondary system-sm-medium">
Workspace description
</label>
<Textarea
id="workspace-description"
name="workspaceDescription"
placeholder="Describe how this workspace is used..."
/>
</div>
),
}
export const Sizes: Story = {
render: () => (
<div className="grid w-80 gap-3">
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="small-textarea">
Small
<Textarea id="small-textarea" size="small" name="smallTextarea" placeholder="Short note..." rows={3} />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="medium-textarea">
Medium
<Textarea id="medium-textarea" name="mediumTextarea" placeholder="Add context..." rows={3} />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="large-textarea">
Large
<Textarea id="large-textarea" size="large" name="largeTextarea" placeholder="Write a longer instruction..." rows={3} />
</label>
</div>
),
}
export const States: Story = {
render: () => (
<div className="grid w-80 gap-3">
<FieldRoot name="placeholderState">
<FieldLabel>Placeholder</FieldLabel>
<Textarea placeholder="Add a description..." rows={3} />
</FieldRoot>
<FieldRoot name="filledState">
<FieldLabel>Filled</FieldLabel>
<Textarea defaultValue="Use this dataset for support articles and product FAQs." rows={3} />
</FieldRoot>
<FieldRoot name="invalidState" invalid>
<FieldLabel>Invalid</FieldLabel>
<Textarea defaultValue="Too short" rows={3} />
<FieldError match>Use at least 20 characters.</FieldError>
</FieldRoot>
<FieldRoot name="disabledState">
<FieldLabel>Disabled</FieldLabel>
<Textarea disabled placeholder="Editing is unavailable..." rows={3} />
</FieldRoot>
<FieldRoot name="readonlyState">
<FieldLabel>Read-only</FieldLabel>
<Textarea readOnly defaultValue="Generated from the published workflow configuration." rows={3} />
</FieldRoot>
</div>
),
}
const FormDemo = () => {
const [savedDescription, setSavedDescription] = useState<string | null>(null)
return (
<Form
aria-label="Dataset settings"
className="grid w-80 gap-4"
onFormSubmit={(values) => {
setSavedDescription(String(values.description ?? ''))
}}
>
<FieldRoot name="description">
<FieldLabel>Description</FieldLabel>
<Textarea
required
minLength={20}
maxLength={160}
placeholder="Describe what this dataset contains..."
rows={4}
className="resize-y"
/>
<FieldDescription>Shown to teammates when they choose a knowledge source.</FieldDescription>
<FieldError match="valueMissing">Description is required.</FieldError>
<FieldError match="tooShort">Use at least 20 characters.</FieldError>
</FieldRoot>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button>
</div>
{savedDescription && (
<div className="rounded-lg bg-background-section px-3 py-2 text-text-secondary system-xs-regular">
Saved:
{' '}
{savedDescription}
</div>
)}
</Form>
)
}
export const WithField: Story = {
render: () => <FormDemo />,
}
const ControlledDemo = () => {
const [value, setValue] = useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="prompt">
<FieldLabel>Prompt</FieldLabel>
<Textarea
value={value}
onValueChange={nextValue => setValue(nextValue)}
rows={4}
className="resize-y"
/>
<FieldDescription>The saved value is updated from the controlled state.</FieldDescription>
</FieldRoot>
)
}
export const Controlled: Story = {
render: () => (
<div className="w-80">
<ControlledDemo />
</div>
),
}
const CharacterCounterDemo = () => {
const maxLength = 120
const [value, setValue] = useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="limitedPrompt">
<FieldLabel>Prompt</FieldLabel>
<div className="relative">
<Textarea
value={value}
onValueChange={nextValue => setValue(nextValue)}
maxLength={maxLength}
rows={4}
className="resize-y pb-8"
/>
<div className="pointer-events-none absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-text-quaternary system-xs-medium">
<span>{value.length}</span>
/
<span className="text-text-tertiary">{maxLength}</span>
</div>
</div>
<FieldDescription>Character counters are composed at the usage site when the workflow needs one.</FieldDescription>
</FieldRoot>
)
}
export const WithCharacterCounter: Story = {
render: () => (
<div className="w-80">
<CharacterCounterDemo />
</div>
),
}

View File

@@ -0,0 +1,103 @@
'use client'
import type { Field as BaseFieldNS } from '@base-ui/react/field'
import type { VariantProps } from 'class-variance-authority'
import type { ComponentPropsWithRef } from 'react'
import { Field as BaseField } from '@base-ui/react/field'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
const textareaVariants = cva(
[
'min-h-20 w-full appearance-none overflow-auto border border-transparent bg-components-input-bg-normal text-components-input-text-filled caret-primary-600 outline-hidden transition-[background-color,border-color,box-shadow]',
'placeholder:text-components-input-text-placeholder',
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
'focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs',
'data-invalid:border-components-input-border-destructive data-invalid:bg-components-input-bg-destructive',
'read-only:cursor-default read-only:shadow-none read-only:hover:border-transparent read-only:hover:bg-components-input-bg-normal read-only:focus:border-transparent read-only:focus:bg-components-input-bg-normal read-only:focus:shadow-none',
'disabled:cursor-not-allowed disabled:border-transparent disabled:bg-components-input-bg-disabled disabled:text-components-input-text-filled-disabled',
'disabled:hover:border-transparent disabled:hover:bg-components-input-bg-disabled',
'motion-reduce:transition-none',
],
{
variants: {
size: {
small: 'rounded-md px-2 py-1 system-xs-regular',
medium: 'rounded-lg px-3 py-2 system-sm-regular',
large: 'rounded-[10px] px-4 py-2 system-md-regular',
},
},
defaultVariants: {
size: 'medium',
},
},
)
type TextareaValue = string | number
export type TextareaSize = NonNullable<VariantProps<typeof textareaVariants>['size']>
export type TextareaChangeEventDetails = BaseFieldNS.Control.ChangeEventDetails
type TextareaOnValueChange = (value: string, eventDetails: TextareaChangeEventDetails) => void
type ControlledTextareaProps = {
value: TextareaValue
defaultValue?: never
onValueChange: TextareaOnValueChange
}
type UncontrolledTextareaProps = {
value?: never
defaultValue?: TextareaValue
onValueChange?: TextareaOnValueChange
}
type TextareaNativeProps = ComponentPropsWithRef<'textarea'>
type TextareaOnlyProps = Pick<TextareaNativeProps, 'cols' | 'rows' | 'wrap'>
type TextareaElementProps = Omit<
TextareaNativeProps,
'children' | 'className' | 'cols' | 'defaultValue' | 'onChange' | 'rows' | 'size' | 'value' | 'wrap'
>
type TextareaControlProps = ControlledTextareaProps | UncontrolledTextareaProps
type TextareaVariantProps = VariantProps<typeof textareaVariants>
type FieldControlTextareaProps = Omit<
BaseFieldNS.Control.Props,
'className' | 'defaultValue' | 'onValueChange' | 'render' | 'value'
>
export type TextareaProps
= TextareaElementProps
& TextareaOnlyProps
& TextareaControlProps
& TextareaVariantProps
& {
children?: never
className?: string
}
export function Textarea({
className,
cols,
defaultValue,
onValueChange,
ref,
rows,
size = 'medium',
value,
wrap,
...controlProps
}: TextareaProps) {
// Base UI types Field.Control as an input even when render replaces it with a textarea.
const fieldControlProps = controlProps as FieldControlTextareaProps
return (
<BaseField.Control
{...fieldControlProps}
className={cn(textareaVariants({ size }), className)}
defaultValue={defaultValue}
onValueChange={onValueChange}
ref={ref}
render={<textarea cols={cols} rows={rows} wrap={wrap} />}
value={value}
/>
)
}

View File

@@ -493,8 +493,8 @@ describe('Capacity Full Components Integration', () => {
expect(screen.getByText(/upgradeBtn\.encourageShort/i)).toBeInTheDocument()
// Should show usage/total fraction "5/5"
expect(screen.getByText(/5\/5/)).toBeInTheDocument()
// Should have a meter rendered
expect(screen.getByRole('meter')).toBeInTheDocument()
// Should have an accessible meter rendered
expect(screen.getByRole('meter', { name: /usagePage\.buildApps/i })).toBeInTheDocument()
})
it('should display upgrade tip and upgrade button for professional plan', () => {

View File

@@ -1,10 +1,10 @@
'use client'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
import { useAppContext } from '@/context/app-context'
import { useRouter } from '@/next/navigation'
import { useLogout } from '@/service/use-common'
@@ -63,11 +63,12 @@ export default function FeedBack(props: DeleteAccountProps) {
</DialogTitle>
<label className="mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary">{t('account.feedbackLabel', { ns: 'common' })}</label>
<Textarea
aria-label={t('account.feedbackLabel', { ns: 'common' }) as string}
rows={6}
value={userFeedback}
placeholder={t('account.feedbackPlaceholder', { ns: 'common' }) as string}
onChange={(e) => {
setUserFeedback(e.target.value)
onValueChange={(value) => {
setUserFeedback(value)
}}
/>
<div className="mt-3 flex w-full flex-col gap-2">

View File

@@ -1,9 +1,9 @@
'use client'
import type { FC } from 'react'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Robot, User } from '@/app/components/base/icons/src/public/avatar'
import Textarea from '@/app/components/base/textarea'
export enum EditItemType {
Query = 'query',
@@ -33,8 +33,9 @@ const EditItem: FC<Props> = ({
<div className="grow">
<div className="mb-1 system-xs-semibold text-text-primary">{name}</div>
<Textarea
aria-label={name}
value={content}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}
onValueChange={value => onChange(value)}
placeholder={placeholder}
autoFocus
/>

View File

@@ -2,12 +2,12 @@
import type { FC } from 'react'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiDeleteBinLine, RiEditFill, RiEditLine } from '@remixicon/react'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Robot, User } from '@/app/components/base/icons/src/public/avatar'
import Textarea from '@/app/components/base/textarea'
export enum EditItemType {
Query = 'query',
@@ -130,8 +130,9 @@ const EditItem: FC<Props> = ({
<div className="mt-3">
<EditTitle title={editTitle} />
<Textarea
aria-label={editTitle}
value={newContent}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setNewContent(e.target.value)}
onValueChange={value => setNewContent(value)}
placeholder={placeholder}
autoFocus
/>

View File

@@ -3,12 +3,12 @@ import type { VersionHistory } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '../../base/textarea'
type VersionInfoModalProps = {
isOpen: boolean
@@ -57,8 +57,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
onClose()
}
const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
setReleaseNotes(e.target.value)
const handleDescriptionChange = useCallback((value: string) => {
setReleaseNotes(value)
}, [])
return (
@@ -95,17 +95,16 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
onValueChange={setTitle}
/>
</FieldRoot>
<div className="flex flex-col gap-y-1">
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
<FieldRoot name="releaseNotes" invalid={releaseNotesError} className="gap-y-1">
<FieldLabel className="flex h-6 items-center py-0 system-sm-semibold text-text-secondary">
{t('versionHistory.editField.releaseNotes', { ns: 'workflow' })}
</div>
</FieldLabel>
<Textarea
value={releaseNotes}
placeholder={`${t('versionHistory.releaseNotesPlaceholder', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
onChange={handleDescriptionChange}
destructive={releaseNotesError}
onValueChange={handleDescriptionChange}
/>
</div>
</FieldRoot>
</div>
<div className="flex justify-end p-6 pt-5">
<div className="flex items-center gap-x-3">

View File

@@ -13,12 +13,12 @@ import {
SelectTrigger,
SelectValue,
} from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { Trans } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { Infotip } from '@/app/components/base/infotip'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/file-upload-setting'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -121,8 +121,9 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
{type === InputVarType.paragraph && (
<Field title={t('variableConfig.defaultValue', { ns: 'appDebug' })}>
<Textarea
aria-label={t('variableConfig.defaultValue', { ns: 'appDebug' })}
value={String(tempPayload.default ?? '')}
onChange={e => onPayloadChange('default')(e.target.value || undefined)}
onValueChange={value => onPayloadChange('default')(value || undefined)}
placeholder={t('variableConfig.inputPlaceholder', { ns: 'appDebug' })}
/>
</Field>

View File

@@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
import Textarea from '@/app/components/base/textarea'
const i18nPrefix = 'generate'
@@ -40,10 +40,11 @@ const IdeaOutput: FC<Props> = ({
</div>
{!isFoldIdeaOutput && (
<Textarea
aria-label={t(`${i18nPrefix}.idealOutput`, { ns: 'appDebug' })}
className="h-[80px]"
placeholder={t(`${i18nPrefix}.idealOutputPlaceholder`, { ns: 'appDebug' })}
value={value}
onChange={e => onChange(e.target.value)}
onValueChange={value => onChange(value)}
/>
)}
</div>

View File

@@ -4,13 +4,13 @@ import type { DataSet } from '@/models/datasets'
import type { RetrievalConfig } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
import { isEqual } from 'es-toolkit/predicate'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import { IndexingType } from '@/app/components/datasets/create/step-two'
import IndexMethod from '@/app/components/datasets/settings/index-method'
@@ -224,8 +224,9 @@ const SettingsModal: FC<SettingsModalProps> = ({
</div>
<div className="w-full">
<Textarea
aria-label={t('form.desc', { ns: 'datasetSettings' })}
value={localeCurrentDataset.description || ''}
onChange={e => handleValueChange('description', e.target.value)}
onValueChange={value => handleValueChange('description', value)}
className="resize-none"
placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''}
/>

View File

@@ -84,25 +84,6 @@ vi.mock('@langgenius/dify-ui/select', async () => {
}
})
vi.mock('@/app/components/base/textarea', () => ({
default: ({ value, onChange, placeholder, readOnly, className }: {
value: string
onChange: (e: { target: { value: string } }) => void
placeholder?: string
readOnly?: boolean
className?: string
}) => (
<textarea
data-testid={`textarea-${placeholder}`}
value={value}
onChange={onChange}
placeholder={placeholder}
readOnly={readOnly}
className={className}
/>
),
}))
vi.mock('@/app/components/workflow/nodes/_base/components/before-run-form/bool-input', () => ({
default: ({ name, value, required, onChange, readonly }: {
name: string
@@ -223,7 +204,7 @@ describe('ChatUserInput', () => {
}))
render(<ChatUserInput inputs={{}} />)
expect(screen.getByTestId('textarea-Description')).toBeInTheDocument()
expect(screen.getByRole('textbox', { name: 'Description' })).toBeInTheDocument()
})
it('should render select input type', () => {
@@ -275,7 +256,7 @@ describe('ChatUserInput', () => {
render(<ChatUserInput inputs={{}} />)
expect(screen.getByTestId('input-Name')).toBeInTheDocument()
expect(screen.getByTestId('textarea-Description')).toBeInTheDocument()
expect(screen.getByRole('textbox', { name: 'Description' })).toBeInTheDocument()
expect(screen.getByTestId('select-input')).toBeInTheDocument()
})
@@ -334,7 +315,7 @@ describe('ChatUserInput', () => {
}))
render(<ChatUserInput inputs={{ desc: 'Long text here' }} />)
expect(screen.getByTestId('textarea-Description')).toHaveValue('Long text here')
expect(screen.getByRole('textbox', { name: 'Description' })).toHaveValue('Long text here')
})
it('should display existing input values for number type', () => {
@@ -418,7 +399,7 @@ describe('ChatUserInput', () => {
}))
render(<ChatUserInput inputs={{}} />)
fireEvent.change(screen.getByTestId('textarea-Description'), { target: { value: 'New Description' } })
fireEvent.change(screen.getByRole('textbox', { name: 'Description' }), { target: { value: 'New Description' } })
expect(mockSetInputs).toHaveBeenCalledWith({ desc: 'New Description' })
})
@@ -526,7 +507,7 @@ describe('ChatUserInput', () => {
}))
render(<ChatUserInput inputs={{}} />)
expect(screen.getByTestId('textarea-Description')).toHaveAttribute('readonly')
expect(screen.getByRole('textbox', { name: 'Description' })).toHaveAttribute('readonly')
})
it('should disable select when readonly is true', () => {

View File

@@ -1,12 +1,12 @@
import type { Inputs } from '@/models/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import ConfigContext from '@/context/debug-configuration'
@@ -94,9 +94,10 @@ const ChatUserInput = ({
{type === 'paragraph' && (
<Textarea
className="h-[120px] grow"
aria-label={name || key}
placeholder={name}
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
onValueChange={(value) => { handleInputValueChange(key, value) }}
readOnly={readonly}
/>
)}

View File

@@ -5,6 +5,7 @@ import type { VisionFile, VisionSettings } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowDownSLine,
@@ -19,7 +20,6 @@ import { useStore as useAppStore } from '@/app/components/app/store'
import FeatureBar from '@/app/components/base/features/new-feature-panel/feature-bar'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType } from '@/types/app'
@@ -151,10 +151,11 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
)}
{type === 'paragraph' && (
<Textarea
aria-label={name}
className="h-[120px] grow"
placeholder={name}
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
onValueChange={(value) => { handleInputValueChange(key, value) }}
readOnly={readonly}
/>
)}

View File

@@ -4,6 +4,7 @@ import type { AppIconSelection } from '../../base/app-icon-picker'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiArrowRightLine, RiArrowRightSLine, RiExchange2Fill } from '@remixicon/react'
import { useDebounceFn, useKeyPress } from 'ahooks'
@@ -13,7 +14,6 @@ import AppIcon from '@/app/components/base/app-icon'
import Divider from '@/app/components/base/divider'
import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
@@ -241,10 +241,11 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
</span>
</div>
<Textarea
aria-label={t('newApp.captionDescription', { ns: 'app' })}
className="resize-none"
placeholder={t('newApp.appDescriptionPlaceholder', { ns: 'app' }) || ''}
value={description}
onChange={e => setDescription(e.target.value)}
onValueChange={value => setDescription(value)}
/>
</div>
</div>

View File

@@ -8,6 +8,7 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
@@ -18,7 +19,6 @@ import AppIconPicker from '@/app/components/base/app-icon-picker'
import Divider from '@/app/components/base/divider'
import Input from '@/app/components/base/input'
import { PremiumBadgeButton } from '@/app/components/base/premium-badge'
import Textarea from '@/app/components/base/textarea'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
@@ -289,9 +289,10 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<div className="relative">
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}</div>
<Textarea
aria-label={t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}
className="mt-1"
value={inputInfo.desc}
onChange={e => onDesChange(e.target.value)}
onValueChange={onDesChange}
placeholder={t(`${prefixSettings}.webDescPlaceholder`, { ns: 'appOverview' }) as string}
/>
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.webDescTip`, { ns: 'appOverview' })}</p>
@@ -464,9 +465,10 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>{t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}</div>
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`, { ns: 'appOverview' })}</p>
<Textarea
aria-label={t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}
className="mt-1"
value={inputInfo.customDisclaimer}
onChange={onChange('customDisclaimer')}
onValueChange={value => setInputInfo(item => ({ ...item, customDisclaimer: value }))}
placeholder={t(`${prefixSettings}.more.customDisclaimerPlaceholder`, { ns: 'appOverview' }) as string}
/>
</div>

View File

@@ -7,8 +7,8 @@ import {
SelectTrigger,
SelectValue,
} from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { InputVarType } from '@/app/components/workflow/types'
@@ -74,7 +74,7 @@ const WorkflowHiddenInputFields = ({
<Textarea
id={fieldId}
value={typeof fieldValue === 'string' ? fieldValue : ''}
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => onValueChange(variable.variable, event.target.value)}
onValueChange={value => onValueChange(variable.variable, value)}
placeholder={label}
maxLength={variable.max_length}
className="min-h-24"

View File

@@ -1,10 +1,10 @@
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -71,8 +71,9 @@ const InputsFormContent = ({ showTip }: Props) => {
)}
{form.type === InputVarType.paragraph && (
<Textarea
aria-label={form.label}
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
onValueChange={value => handleFormChange(form.variable, value)}
placeholder={form.label}
/>
)}

View File

@@ -1,8 +1,8 @@
import type { ContentItemProps } from './type'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useMemo } from 'react'
import { Markdown } from '@/app/components/base/markdown'
import Textarea from '@/app/components/base/textarea'
const ContentItem = ({
content,
@@ -42,9 +42,10 @@ const ContentItem = ({
<div className="py-3">
{formInputField.type === 'paragraph' && (
<Textarea
aria-label={fieldName}
className="h-[104px] sm:text-xs"
value={inputs[fieldName]!}
onChange={(e) => { onInputChange(fieldName, e.target.value) }}
onValueChange={(value) => { onInputChange(fieldName, value) }}
data-testid="content-item-textarea"
/>
)}

View File

@@ -6,6 +6,7 @@ import type {
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import copy from 'copy-to-clipboard'
@@ -21,7 +22,6 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
import Log from '@/app/components/base/chat/chat/log'
import AnnotationCtrlButton from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button'
import NewAudioButton from '@/app/components/base/new-audio-button'
import Textarea from '@/app/components/base/textarea'
import { useChatContext } from '../context'
type OperationProps = {
@@ -394,7 +394,7 @@ function Operation({
id={feedbackTextareaId}
name="feedback-content"
value={feedbackContent}
onChange={e => setFeedbackContent(e.target.value)}
onValueChange={value => setFeedbackContent(value)}
placeholder={t('feedback.placeholder', { ns: 'common' }) || 'Please describe what went wrong or how we can improve…'}
rows={4}
className="w-full"

View File

@@ -1,10 +1,10 @@
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -71,8 +71,9 @@ const InputsFormContent = ({ showTip }: Props) => {
)}
{form.type === InputVarType.paragraph && (
<Textarea
aria-label={form.label}
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
onValueChange={value => handleFormChange(form.variable, value)}
placeholder={form.label}
/>
)}

View File

@@ -12,10 +12,10 @@ import { FieldItem, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { RadioControl, RadioRoot } from '@langgenius/dify-ui/radio'
import { RadioGroup } from '@langgenius/dify-ui/radio-group'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { produce } from 'immer'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
@@ -220,9 +220,10 @@ const FollowUpSettingModal = ({
</div>
{promptMode === PROMPT_MODE.custom && (
<Textarea
aria-label={t('feature.suggestedQuestionsAfterAnswer.modal.customPromptOption', { ns: 'appDebug' })}
className="mt-3 min-h-32 resize-y border-components-input-border-active bg-components-input-bg-normal"
value={prompt}
onChange={e => setPrompt(e.target.value)}
onValueChange={value => setPrompt(value)}
maxLength={CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH}
placeholder={t('feature.suggestedQuestionsAfterAnswer.modal.promptPlaceholder', { ns: 'appDebug' }) || ''}
/>

View File

@@ -2,7 +2,7 @@ import type { FC } from 'react'
import type { CodeBasedExtensionForm } from '@/models/common'
import type { ModerationConfig } from '@/models/debug'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import Textarea from '@/app/components/base/textarea'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useLocale } from '@/context/i18n'
type FormGenerationProps = {
@@ -55,10 +55,11 @@ const FormGeneration: FC<FormGenerationProps> = ({
form.type === 'paragraph' && (
<div className="relative">
<Textarea
aria-label={locale === 'zh-Hans' ? form.label['zh-Hans'] : form.label['en-US']}
className="resize-none"
value={value?.[form.variable] || ''}
placeholder={form.placeholder}
onChange={e => handleFormChange(form.variable, e.target.value)}
onValueChange={value => handleFormChange(form.variable, value)}
/>
</div>
)

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react'
import type { ModerationContentConfig } from '@/models/debug'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useTranslation } from 'react-i18next'
type ModerationContentProps = {
@@ -50,12 +51,14 @@ const ModerationContent: FC<ModerationContentProps> = ({
{t('feature.moderation.modal.content.preset', { ns: 'appDebug' })}
<span className="text-xs font-normal text-text-tertiary">{t('feature.moderation.modal.content.supportMarkdown', { ns: 'appDebug' })}</span>
</div>
<div className="relative h-20 rounded-lg bg-components-input-bg-normal px-3 py-2">
<textarea
{/* Keep this counter composed locally; extract only if more textarea counter cases repeat. */}
<div className="relative h-20">
<Textarea
aria-label={t('feature.moderation.modal.content.preset', { ns: 'appDebug' }) as string}
value={config.preset_response || ''}
className="block size-full resize-none appearance-none bg-transparent text-sm text-text-secondary outline-hidden"
className="size-full resize-none pb-8"
placeholder={t('feature.moderation.modal.content.placeholder', { ns: 'appDebug' }) || ''}
onChange={e => handleConfigChange('preset_response', e.target.value)}
onValueChange={value => handleConfigChange('preset_response', value)}
/>
<div className="absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-xs font-medium text-text-quaternary">
<span>{(config.preset_response || '').length}</span>

View File

@@ -1,9 +1,10 @@
import type { ChangeEvent, FC } from 'react'
import type { FC } from 'react'
import type { CodeBasedExtensionItem } from '@/models/common'
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -103,9 +104,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
})
}
const handleDataKeywordsChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value
const handleDataKeywordsChange = (value: string) => {
const arr = value.split('\n').reduce((prev: string[], next: string) => {
if (next !== '')
prev.push(next.slice(0, 100))
@@ -292,11 +291,13 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
<div className="py-2">
<div className="mb-1 text-sm font-medium text-text-primary">{t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' })}</div>
<div className="mb-2 text-xs text-text-tertiary">{t('feature.moderation.modal.keywords.tip', { ns: 'appDebug' })}</div>
<div className="relative h-[88px] rounded-lg bg-components-input-bg-normal px-3 py-2">
<textarea
{/* Keep this counter composed locally; extract only if more textarea counter cases repeat. */}
<div className="relative h-[88px]">
<Textarea
aria-label={t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' }) as string}
value={localeData.config?.keywords || ''}
onChange={handleDataKeywordsChange}
className="block size-full resize-none appearance-none bg-transparent text-sm text-text-secondary outline-hidden"
onValueChange={handleDataKeywordsChange}
className="size-full resize-none pb-8"
placeholder={t('feature.moderation.modal.keywords.placeholder', { ns: 'appDebug' }) || ''}
/>
<div className="absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-xs font-medium text-text-quaternary">

View File

@@ -1,3 +1,4 @@
import type { ComponentProps } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import TextAreaField from '../text-area'
@@ -30,4 +31,20 @@ describe('TextAreaField', () => {
fireEvent.change(screen.getByLabelText('Note'), { target: { value: 'Updated note' } })
expect(mockField.handleChange).toHaveBeenCalledWith('Updated note')
})
it('should keep form writeback when external props contain onValueChange', () => {
const externalOnValueChange = vi.fn()
render(
<TextAreaField
label="Note"
{...({ onValueChange: externalOnValueChange } as Partial<ComponentProps<typeof TextAreaField>>)}
/>,
)
fireEvent.change(screen.getByLabelText('Note'), { target: { value: 'Updated note' } })
expect(mockField.handleChange).toHaveBeenCalledWith('Updated note')
expect(externalOnValueChange).not.toHaveBeenCalled()
})
})

View File

@@ -1,16 +1,16 @@
import type { TextareaProps } from '../../../textarea'
import type { TextareaProps } from '@langgenius/dify-ui/textarea'
import type { LabelProps } from '../label'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useFieldContext } from '../..'
import Textarea from '../../../textarea'
import Label from '../label'
type TextAreaFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
} & Omit<TextareaProps, 'className' | 'onChange' | 'onBlur' | 'value' | 'id'>
} & Omit<TextareaProps, 'className' | 'defaultValue' | 'onBlur' | 'onValueChange' | 'value' | 'id'>
const TextAreaField = ({
label,
@@ -28,11 +28,11 @@ const TextAreaField = ({
{...(labelOptions ?? {})}
/>
<Textarea
{...inputProps}
id={field.name}
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
onValueChange={value => field.handleChange(value)}
onBlur={field.handleBlur}
{...inputProps}
/>
</div>
)

View File

@@ -3,6 +3,7 @@ import type { Dayjs } from 'dayjs'
import { Button } from '@langgenius/dify-ui/button'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useChatContext } from '@/app/components/base/chat/chat/context'
@@ -10,7 +11,6 @@ import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
import { formatDateForOutput, toDayjs } from '@/app/components/base/date-and-time-picker/utils/dayjs'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
const DATA_FORMAT = {
TEXT: 'text',
@@ -372,11 +372,12 @@ const MarkdownForm = ({ node }: { node: HastElement }) => {
return null
return (
<Textarea
aria-label={name}
key={key}
name={name}
placeholder={str(child.properties.placeholder)}
value={str(formValues[name])}
onChange={e => updateValue(name, e.target.value)}
onValueChange={value => updateValue(name, value)}
/>
)
}

View File

@@ -2,12 +2,12 @@
import type { FC } from 'react'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import { VarType } from '@/app/components/workflow/types'
import Textarea from '../../../textarea'
import TagLabel from './tag-label'
import TypeSwitch from './type-switch'
@@ -72,6 +72,7 @@ const PrePopulate: FC<Props> = ({
value,
onValueChange,
}) => {
const { t } = useTranslation()
const [onPlaceholderClicked, setOnPlaceholderClicked] = useState(false)
const handleTypeChange = useCallback((isVar: boolean) => {
setOnPlaceholderClicked(true)
@@ -127,9 +128,10 @@ const PrePopulate: FC<Props> = ({
return (
<div className={cn('relative min-h-[80px] rounded-lg border border-transparent bg-components-input-bg-normal pb-1', isFocus && 'border-components-input-border-active bg-components-input-bg-active shadow-xs')}>
<Textarea
aria-label={t(`${i18nPrefix}.staticContent`, { ns: 'workflow' })}
value={value || ''}
className="h-[43px] min-h-[43px] rounded-none border-none bg-transparent px-3 hover:bg-transparent focus:bg-transparent focus:shadow-none"
onChange={e => onValueChange?.(e.target.value)}
onValueChange={value => onValueChange?.(value)}
onFocus={() => {
setOnPlaceholderClicked(true)
setIsFocus(true)

View File

@@ -1,77 +0,0 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import TextArea from '../index'
describe('TextArea', () => {
it('should render correctly with default props', () => {
render(<TextArea value="" onChange={vi.fn()} />)
const textarea = screen.getByTestId('text-area')
expect(textarea).toBeInTheDocument()
expect(textarea).toHaveValue('')
})
it('should handle value and onChange correctly', async () => {
const user = userEvent.setup()
const handleChange = vi.fn()
const { rerender } = render(<TextArea value="initial" onChange={handleChange} />)
const textarea = screen.getByTestId('text-area')
expect(textarea).toHaveValue('initial')
await user.type(textarea, ' updated')
expect(handleChange).toHaveBeenCalled()
rerender(<TextArea value="initial updated" onChange={handleChange} />)
expect(textarea).toHaveValue('initial updated')
})
it('should handle autoFocus correctly', () => {
render(<TextArea value="" onChange={vi.fn()} autoFocus />)
const textarea = screen.getByTestId('text-area')
expect(textarea).toHaveFocus()
})
it('should handle disabled state', () => {
render(<TextArea value="" onChange={vi.fn()} disabled />)
const textarea = screen.getByTestId('text-area')
expect(textarea).toBeDisabled()
expect(textarea).toHaveClass('cursor-not-allowed')
})
it('should handle placeholder', () => {
render(<TextArea value="" onChange={vi.fn()} placeholder="Enter text here" />)
expect(screen.getByPlaceholderText('Enter text here')).toBeInTheDocument()
})
it('should handle className', () => {
render(<TextArea value="" onChange={vi.fn()} className="custom-class" />)
expect(screen.getByTestId('text-area')).toHaveClass('custom-class')
})
it('should handle size variants', () => {
const { rerender } = render(<TextArea value="" onChange={vi.fn()} size="small" />)
expect(screen.getByTestId('text-area')).toHaveClass('py-1')
rerender(<TextArea value="" onChange={vi.fn()} size="large" />)
expect(screen.getByTestId('text-area')).toHaveClass('px-4')
})
it('should handle destructive state', () => {
render(<TextArea value="" onChange={vi.fn()} destructive />)
expect(screen.getByTestId('text-area')).toHaveClass('border-components-input-border-destructive')
})
it('should handle onFocus and onBlur', async () => {
const user = userEvent.setup()
const handleFocus = vi.fn()
const handleBlur = vi.fn()
render(<TextArea value="" onChange={vi.fn()} onFocus={handleFocus} onBlur={handleBlur} />)
const textarea = screen.getByTestId('text-area')
await user.click(textarea)
expect(handleFocus).toHaveBeenCalled()
await user.tab()
expect(handleBlur).toHaveBeenCalled()
})
})

View File

@@ -1,562 +0,0 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { useState } from 'react'
import Textarea from '.'
const meta = {
title: 'Base/Data Entry/Textarea',
component: Textarea,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Textarea component with multiple sizes (small, regular, large). Built with class-variance-authority for consistent styling.',
},
},
},
tags: ['autodocs'],
argTypes: {
size: {
control: 'select',
options: ['small', 'regular', 'large'],
description: 'Textarea size',
},
value: {
control: 'text',
description: 'Textarea value',
},
placeholder: {
control: 'text',
description: 'Placeholder text',
},
disabled: {
control: 'boolean',
description: 'Disabled state',
},
destructive: {
control: 'boolean',
description: 'Error/destructive state',
},
rows: {
control: 'number',
description: 'Number of visible text rows',
},
},
} satisfies Meta<typeof Textarea>
export default meta
type Story = StoryObj<typeof meta>
// Interactive demo wrapper
const TextareaDemo = (args: any) => {
const [value, setValue] = useState(args.value || '')
return (
<div style={{ width: '500px' }}>
<Textarea
{...args}
value={value}
onChange={(e) => {
setValue(e.target.value)
console.log('Textarea changed:', e.target.value)
}}
/>
{value && (
<div className="mt-3 text-sm text-gray-600">
Character count:
{' '}
<span className="font-semibold">{value.length}</span>
</div>
)}
</div>
)
}
// Default state
export const Default: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'regular',
placeholder: 'Enter text...',
rows: 4,
value: '',
},
}
// Small size
export const SmallSize: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'small',
placeholder: 'Small textarea...',
rows: 3,
value: '',
},
}
// Large size
export const LargeSize: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'large',
placeholder: 'Large textarea...',
rows: 5,
value: '',
},
}
// With initial value
export const WithInitialValue: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'regular',
value: 'This is some initial text content.\n\nIt spans multiple lines.',
rows: 4,
},
}
// Disabled state
export const Disabled: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'regular',
value: 'This textarea is disabled and cannot be edited.',
disabled: true,
rows: 3,
},
}
// Destructive/error state
export const DestructiveState: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'regular',
value: 'This content has an error.',
destructive: true,
rows: 3,
},
}
// Size comparison
const SizeComparisonDemo = () => {
const [small, setSmall] = useState('')
const [regular, setRegular] = useState('')
const [large, setLarge] = useState('')
return (
<div style={{ width: '600px' }} className="space-y-4">
<div>
<label className="mb-2 block text-xs font-medium text-gray-600">Small</label>
<Textarea
size="small"
value={small}
onChange={e => setSmall(e.target.value)}
placeholder="Small textarea..."
rows={3}
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-gray-600">Regular</label>
<Textarea
size="regular"
value={regular}
onChange={e => setRegular(e.target.value)}
placeholder="Regular textarea..."
rows={4}
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-gray-600">Large</label>
<Textarea
size="large"
value={large}
onChange={e => setLarge(e.target.value)}
placeholder="Large textarea..."
rows={5}
/>
</div>
</div>
)
}
export const SizeComparison: Story = {
render: () => <SizeComparisonDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// State comparison
const StateComparisonDemo = () => {
const [normal, setNormal] = useState('Normal state')
const [error, setError] = useState('Error state')
return (
<div style={{ width: '500px' }} className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Normal</label>
<Textarea
value={normal}
onChange={e => setNormal(e.target.value)}
rows={3}
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Destructive</label>
<Textarea
value={error}
onChange={e => setError(e.target.value)}
destructive
rows={3}
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Disabled</label>
<Textarea
value="Disabled state"
onChange={() => undefined}
disabled
rows={3}
/>
</div>
</div>
)
}
export const StateComparison: Story = {
render: () => <StateComparisonDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Comment form
const CommentFormDemo = () => {
const [comment, setComment] = useState('')
const maxLength = 500
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Leave a Comment</h3>
<Textarea
value={comment}
onChange={e => setComment(e.target.value)}
placeholder="Share your thoughts..."
rows={5}
maxLength={maxLength}
/>
<div className="mt-2 flex items-center justify-between">
<span className="text-xs text-gray-500">
{comment.length}
{' '}
/
{maxLength}
{' '}
characters
</span>
<button
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
disabled={comment.trim().length === 0}
>
Post Comment
</button>
</div>
</div>
)
}
export const CommentForm: Story = {
render: () => <CommentFormDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Feedback form
const FeedbackFormDemo = () => {
const [feedback, setFeedback] = useState('')
const [email, setEmail] = useState('')
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-2 text-lg font-semibold">Send Feedback</h3>
<p className="mb-4 text-sm text-gray-600">Help us improve our product</p>
<div className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Your Email</label>
<input
type="email"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="email@example.com"
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Your Feedback</label>
<Textarea
value={feedback}
onChange={e => setFeedback(e.target.value)}
placeholder="Tell us what you think..."
rows={6}
/>
</div>
<button className="w-full rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700">
Submit Feedback
</button>
</div>
</div>
)
}
export const FeedbackForm: Story = {
render: () => <FeedbackFormDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Code snippet
const CodeSnippetDemo = () => {
const [code, setCode] = useState(`function hello() {
console.log("Hello, world!");
}`)
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Code Editor</h3>
<Textarea
value={code}
onChange={e => setCode(e.target.value)}
className="font-mono"
rows={8}
/>
<div className="mt-4 flex gap-2">
<button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Run Code
</button>
<button className="rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300">
Copy
</button>
</div>
</div>
)
}
export const CodeSnippet: Story = {
render: () => <CodeSnippetDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Message composer
const MessageComposerDemo = () => {
const [message, setMessage] = useState('')
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Compose Message</h3>
<div className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">To</label>
<input
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
placeholder="Recipient name"
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Subject</label>
<input
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
placeholder="Message subject"
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Message</label>
<Textarea
value={message}
onChange={e => setMessage(e.target.value)}
placeholder="Type your message here..."
rows={8}
/>
</div>
<div className="flex gap-2">
<button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Send Message
</button>
<button className="rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300">
Save Draft
</button>
</div>
</div>
</div>
)
}
export const MessageComposer: Story = {
render: () => <MessageComposerDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Bio editor
const BioEditorDemo = () => {
const [bio, setBio] = useState('Software developer passionate about building great products.')
const maxLength = 200
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Edit Your Bio</h3>
<Textarea
value={bio}
onChange={e => setBio(e.target.value.slice(0, maxLength))}
placeholder="Tell us about yourself..."
rows={4}
/>
<div className="mt-2 flex items-center justify-between text-xs">
<span className={bio.length > maxLength * 0.9 ? 'text-orange-600' : 'text-gray-500'}>
{bio.length}
{' '}
/
{maxLength}
{' '}
characters
</span>
{bio.length > maxLength * 0.9 && (
<span className="text-orange-600">
{maxLength - bio.length}
{' '}
characters remaining
</span>
)}
</div>
<div className="mt-4 rounded-lg bg-gray-50 p-4">
<div className="mb-2 text-xs font-medium text-gray-600">Preview:</div>
<p className="text-sm text-gray-800">{bio || 'Your bio will appear here...'}</p>
</div>
</div>
)
}
export const BioEditor: Story = {
render: () => <BioEditorDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - JSON editor
const JSONEditorDemo = () => {
const [json, setJson] = useState(`{
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}`)
const [isValid, setIsValid] = useState(true)
const validateJSON = (value: string) => {
try {
JSON.parse(value)
setIsValid(true)
}
catch {
setIsValid(false)
}
}
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-semibold">JSON Editor</h3>
<span className={`rounded-sm px-2 py-1 text-xs ${isValid ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{isValid ? '✓ Valid' : '✗ Invalid'}
</span>
</div>
<Textarea
value={json}
onChange={(e) => {
setJson(e.target.value)
validateJSON(e.target.value)
}}
className="font-mono"
destructive={!isValid}
rows={10}
/>
<div className="mt-4 flex gap-2">
<button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50" disabled={!isValid}>
Save JSON
</button>
<button
className="rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300"
onClick={() => {
try {
const formatted = JSON.stringify(JSON.parse(json), null, 2)
setJson(formatted)
}
catch {
// Invalid JSON, do nothing
}
}}
>
Format
</button>
</div>
</div>
)
}
export const JSONEditor: Story = {
render: () => <JSONEditorDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Real-world example - Task description
const TaskDescriptionDemo = () => {
const [title, setTitle] = useState('Implement user authentication')
const [description, setDescription] = useState('Add login and registration functionality with JWT tokens.')
return (
<div style={{ width: '600px' }} className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Create New Task</h3>
<div className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Task Title</label>
<input
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
value={title}
onChange={e => setTitle(e.target.value)}
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Description</label>
<Textarea
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Describe the task in detail..."
rows={6}
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-gray-700">Priority</label>
<select className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
<option>Low</option>
<option>Medium</option>
<option>High</option>
<option>Urgent</option>
</select>
</div>
<button className="w-full rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Create Task
</button>
</div>
</div>
)
}
export const TaskDescription: Story = {
render: () => <TaskDescriptionDemo />,
parameters: { controls: { disable: true } },
} as unknown as Story
// Interactive playground
export const Playground: Story = {
render: args => <TextareaDemo {...args} />,
args: {
size: 'regular',
placeholder: 'Enter text...',
rows: 4,
disabled: false,
destructive: false,
value: '',
},
}

View File

@@ -1,60 +0,0 @@
import type { VariantProps } from 'class-variance-authority'
import type { CSSProperties } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { cva } from 'class-variance-authority'
import * as React from 'react'
const textareaVariants = cva(
'',
{
variants: {
size: {
small: 'rounded-md py-1 system-xs-regular',
regular: 'rounded-md px-3 system-sm-regular',
large: 'rounded-lg px-4 system-md-regular',
},
},
defaultVariants: {
size: 'regular',
},
},
)
export type TextareaProps = {
value: string | number
disabled?: boolean
destructive?: boolean
styleCss?: CSSProperties
ref?: React.Ref<HTMLTextAreaElement>
onFocus?: React.FocusEventHandler<HTMLTextAreaElement>
onBlur?: React.FocusEventHandler<HTMLTextAreaElement>
} & React.TextareaHTMLAttributes<HTMLTextAreaElement> & VariantProps<typeof textareaVariants>
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, value, onChange, disabled, size, destructive, styleCss, onFocus, onBlur, ...props }, ref) => {
return (
<textarea
ref={ref}
onFocus={onFocus}
onBlur={onBlur}
style={styleCss}
className={cn(
'min-h-20 w-full appearance-none border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs',
textareaVariants({ size }),
disabled && 'cursor-not-allowed border-transparent bg-components-input-bg-disabled text-components-input-text-filled-disabled hover:border-transparent hover:bg-components-input-bg-disabled',
destructive && 'border-components-input-border-destructive bg-components-input-bg-destructive text-components-input-text-filled hover:border-components-input-border-destructive hover:bg-components-input-bg-destructive focus:border-components-input-border-destructive focus:bg-components-input-bg-destructive',
className,
)}
value={value ?? ''}
onChange={onChange}
disabled={disabled}
data-testid="text-area"
{...props}
>
</textarea>
)
},
)
Textarea.displayName = 'Textarea'
export default Textarea

View File

@@ -25,6 +25,7 @@ const AppsFull: FC<{ loc: string, className?: string }> = ({
const total = plan.total.buildApps
const percent = total > 0 ? (usage / total) * 100 : 0
const tone: MeterTone = percent >= 80 ? 'error' : percent >= 50 ? 'warning' : 'neutral'
const buildAppsLabel = t('usagePage.buildApps', { ns: 'billing' })
return (
<div className={cn(
'flex flex-col gap-3 rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-4 shadow-xs backdrop-blur-xs',
@@ -61,14 +62,14 @@ const AppsFull: FC<{ loc: string, className?: string }> = ({
</div>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between system-xs-medium text-text-secondary">
<div>{t('usagePage.buildApps', { ns: 'billing' })}</div>
<div>{buildAppsLabel}</div>
<div>
{usage}
/
{total}
</div>
</div>
<MeterRoot value={Math.min(percent, 100)} max={100}>
<MeterRoot value={Math.min(percent, 100)} max={100} aria-label={buildAppsLabel}>
<MeterTrack>
<MeterIndicator tone={tone} />
</MeterTrack>

View File

@@ -229,7 +229,7 @@ describe('UsageInfo', () => {
/>,
)
expect(screen.getByRole('meter')).toBeInTheDocument()
expect(screen.getByRole('meter', { name: 'Storage' })).toBeInTheDocument()
expect(container.querySelector('[aria-hidden="true"]')).toBeNull()
})
@@ -270,7 +270,7 @@ describe('UsageInfo', () => {
/>,
)
expect(screen.getByRole('meter')).toBeInTheDocument()
expect(screen.getByRole('meter', { name: 'Storage' })).toBeInTheDocument()
expect(container.querySelector('[aria-hidden="true"]')).toBeNull()
})

View File

@@ -144,13 +144,13 @@ const UsageInfo: FC<Props> = ({
<div
className={cn(
'h-1 rounded-md bg-progress-bar-indeterminate-stripe',
isSandboxPlan ? 'w-full' : 'w-[30px]',
isSandboxPlan ? 'w-full' : 'w-7.5',
)}
/>
</div>
)
: (
<MeterRoot value={effectivePercent} max={100}>
<MeterRoot value={effectivePercent} max={100} aria-label={name}>
<MeterTrack>
<MeterIndicator tone={tone} />
</MeterTrack>
@@ -162,7 +162,7 @@ const UsageInfo: FC<Props> = ({
return (
<Tooltip>
<TooltipTrigger render={<div className="cursor-default">{children}</div>} />
<TooltipContent className="w-[200px] max-w-[200px]">
<TooltipContent className="w-50 max-w-50">
{storageTooltip}
</TooltipContent>
</Tooltip>

View File

@@ -1,6 +1,7 @@
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import type { PipelineTemplate } from '@/models/pipeline'
import { Button } from '@langgenius/dify-ui/button'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
import * as React from 'react'
@@ -9,7 +10,6 @@ import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { useInvalidCustomizedTemplateList, useUpdateTemplateInfo } from '@/service/use-pipeline'
type EditPipelineInfoProps = {
@@ -45,8 +45,7 @@ const EditPipelineInfo = ({
setAppIcon(icon)
}, [])
const handleDescriptionChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = event.target.value
const handleDescriptionChange = useCallback((value: string) => {
setDescription(value)
}, [])
@@ -121,7 +120,8 @@ const EditPipelineInfo = ({
{t('knowledgeDescription', { ns: 'datasetPipeline' })}
</label>
<Textarea
onChange={handleDescriptionChange}
aria-label={t('knowledgeDescription', { ns: 'datasetPipeline' })}
onValueChange={handleDescriptionChange}
value={description}
placeholder={t('knowledgeDescriptionPlaceholder', { ns: 'datasetPipeline' })}
/>

View File

@@ -244,6 +244,15 @@ describe('DatasetCard Component', () => {
expect(mockPush).toHaveBeenCalledWith('/datasets/dataset-1/documents')
})
it('should not change background color on hover', () => {
const dataset = createMockDataset()
render(<DatasetCard dataset={dataset} />)
const card = screen.getByText('Test Dataset').closest('[data-disable-nprogress]')
expect(card).toHaveClass('bg-components-card-bg')
expect(card).not.toHaveClass('hover:bg-components-card-bg-alt')
})
it('should navigate to hitTesting for external provider', () => {
const dataset = createMockDataset({ provider: 'external' })
render(<DatasetCard dataset={dataset} />)

View File

@@ -63,7 +63,7 @@ const DatasetCard = ({
return (
<>
<div
className="group relative col-span-1 flex h-[190px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3 transition-all duration-200 ease-in-out hover:bg-components-card-bg-alt hover:shadow-md hover:shadow-shadow-shadow-5"
className="group relative col-span-1 flex h-47.5 cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3 transition-all duration-200 ease-in-out hover:shadow-md hover:shadow-shadow-shadow-5"
data-disable-nprogress={true}
onClick={handleCardClick}
>

View File

@@ -5,12 +5,12 @@ import type { DataSet } from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { updateDatasetSetting } from '@/service/datasets'
import AppIcon from '../../base/app-icon'
import AppIconPicker from '../../base/app-icon-picker'
@@ -108,7 +108,7 @@ const RenameDatasetModal = ({ show, dataset, onSuccess, onClose }: RenameDataset
{t('form.desc', { ns: 'datasetSettings' })}
</div>
<div className="w-full">
<Textarea value={description} onChange={e => setDescription(e.target.value)} className="resize-none" placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''} />
<Textarea aria-label={t('form.desc', { ns: 'datasetSettings' })} value={description} onValueChange={value => setDescription(value)} className="resize-none" placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''} />
</div>
</div>
</div>

View File

@@ -3,11 +3,11 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import type { Member } from '@/models/common'
import type { DataSet, DatasetPermission, IconInfo } from '@/models/datasets'
import type { AppIconType } from '@/types/app'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import PermissionSelector from '../../permission-selector'
const rowClass = 'flex gap-x-1'
@@ -85,11 +85,12 @@ const BasicInfoSection = ({
</div>
<div className="grow">
<Textarea
aria-label={t('form.desc', { ns: 'datasetSettings' })}
disabled={!currentDataset?.embedding_available}
className="resize-none"
placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''}
value={description}
onChange={e => setDescription(e.target.value)}
onValueChange={value => setDescription(value)}
/>
</div>
</div>

View File

@@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react'
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import {
memo,
useCallback,
@@ -9,7 +9,6 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import Textarea from '@/app/components/base/textarea'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
@@ -53,9 +52,9 @@ const SummaryIndexSetting = ({
})
}, [onSummaryIndexSettingChange])
const handleSummaryIndexPromptChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
const handleSummaryIndexPromptChange = useCallback((value: string) => {
onSummaryIndexSettingChange?.({
summary_prompt: e.target.value,
summary_prompt: value,
})
}, [onSummaryIndexSettingChange])
@@ -95,8 +94,9 @@ const SummaryIndexSetting = ({
{t('form.summaryInstructions', { ns: 'datasetSettings' })}
</div>
<Textarea
aria-label={t('form.summaryInstructions', { ns: 'datasetSettings' })}
value={summaryIndexSetting?.summary_prompt ?? ''}
onChange={handleSummaryIndexPromptChange}
onValueChange={handleSummaryIndexPromptChange}
disabled={readonly}
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
/>
@@ -166,8 +166,9 @@ const SummaryIndexSetting = ({
</div>
<div className="grow">
<Textarea
aria-label={t('form.summaryInstructions', { ns: 'datasetSettings' })}
value={summaryIndexSetting?.summary_prompt ?? ''}
onChange={handleSummaryIndexPromptChange}
onValueChange={handleSummaryIndexPromptChange}
disabled={readonly}
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
/>
@@ -214,8 +215,9 @@ const SummaryIndexSetting = ({
{t('form.summaryInstructions', { ns: 'datasetSettings' })}
</div>
<Textarea
aria-label={t('form.summaryInstructions', { ns: 'datasetSettings' })}
value={summaryIndexSetting?.summary_prompt ?? ''}
onChange={handleSummaryIndexPromptChange}
onValueChange={handleSummaryIndexPromptChange}
disabled={readonly}
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
/>

View File

@@ -3,6 +3,7 @@ import type { AppIconType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { useDebounceFn, useKeyPress } from 'ahooks'
import * as React from 'react'
@@ -10,7 +11,6 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import { useProviderContext } from '@/context/provider-context'
import { AppModeEnum } from '@/types/app'
@@ -145,10 +145,11 @@ const CreateAppModal = ({
<div className="pt-2">
<div className="py-2 text-sm leading-[20px] font-medium text-text-primary">{t('newApp.captionDescription', { ns: 'app' })}</div>
<Textarea
aria-label={t('newApp.captionDescription', { ns: 'app' })}
className="resize-none"
placeholder={t('newApp.appDescriptionPlaceholder', { ns: 'app' }) || ''}
value={description}
onChange={e => setDescription(e.target.value)}
onValueChange={value => setDescription(value)}
/>
</div>
{/* answer icon */}

View File

@@ -1,9 +1,9 @@
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { InputVarType } from '@/app/components/workflow/types'
type Props = {
@@ -55,8 +55,9 @@ const AppInputsForm = ({
if (form.type === InputVarType.paragraph) {
return (
<Textarea
aria-label={label}
value={inputs[variable] || ''}
onChange={e => handleFormChange(variable, e.target.value)}
onValueChange={value => handleFormChange(variable, value)}
placeholder={label}
/>
)

View File

@@ -523,9 +523,7 @@ describe('useToolSelectorState Hook', () => {
)
act(() => {
result.current.handleDescriptionChange({
target: { value: 'new description' },
} as React.ChangeEvent<HTMLTextAreaElement>)
result.current.handleDescriptionChange('new description')
})
expect(onSelect).toHaveBeenCalledWith(
@@ -1724,9 +1722,7 @@ describe('Edge Cases', () => {
)
act(() => {
result.current.handleDescriptionChange({
target: { value: '' },
} as React.ChangeEvent<HTMLTextAreaElement>)
result.current.handleDescriptionChange('')
})
expect(onSelect).toHaveBeenCalledWith(

View File

@@ -1,24 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@/app/components/base/textarea', () => ({
default: ({ value, onChange, disabled, placeholder }: {
value?: string
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
disabled?: boolean
placeholder?: string
}) => (
<textarea
data-testid="description-textarea"
value={value || ''}
onChange={onChange}
disabled={disabled}
placeholder={placeholder}
/>
),
}))
vi.mock('../../../../readme-panel/entrance', () => ({
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
}))
@@ -68,28 +50,28 @@ describe('ToolBaseForm', () => {
it('should render description textarea', () => {
render(<ToolBaseForm {...defaultProps} />)
expect(screen.getByTestId('description-textarea')).toBeInTheDocument()
expect(screen.getByRole('textbox')).toBeInTheDocument()
})
it('should disable textarea when no provider_name in value', () => {
render(<ToolBaseForm {...defaultProps} />)
expect(screen.getByTestId('description-textarea')).toBeDisabled()
expect(screen.getByRole('textbox')).toBeDisabled()
})
it('should enable textarea when value has provider_name', () => {
const value = { provider_name: 'test-provider', tool_name: 'test', extra: { description: 'Hello' } } as never
render(<ToolBaseForm {...defaultProps} value={value} />)
expect(screen.getByTestId('description-textarea')).not.toBeDisabled()
expect(screen.getByRole('textbox')).not.toBeDisabled()
})
it('should call onDescriptionChange when textarea content changes', () => {
const value = { provider_name: 'test-provider', tool_name: 'test', extra: { description: 'Hello' } } as never
render(<ToolBaseForm {...defaultProps} value={value} />)
fireEvent.change(screen.getByTestId('description-textarea'), { target: { value: 'Updated' } })
expect(mockOnDescriptionChange).toHaveBeenCalled()
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Updated' } })
expect(mockOnDescriptionChange).toHaveBeenCalledWith('Updated', expect.any(Object))
})
it('should show ReadmeEntrance when provider has plugin_unique_identifier', () => {

View File

@@ -4,8 +4,8 @@ import type { FC } from 'react'
import type { PluginDetail } from '@/app/components/plugins/types'
import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import { ReadmeEntrance } from '../../../readme-panel/entrance'
import ToolTrigger from './tool-trigger'
@@ -23,7 +23,7 @@ type ToolBaseFormProps = {
onPanelShowStateChange?: (state: boolean) => void
onSelectTool: (tool: ToolDefaultValue) => void
onSelectMultipleTool: (tools: ToolDefaultValue[]) => void
onDescriptionChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
onDescriptionChange: (value: string) => void
}
const ToolBaseForm: FC<ToolBaseFormProps> = ({
@@ -85,9 +85,10 @@ const ToolBaseForm: FC<ToolBaseFormProps> = ({
</div>
<Textarea
className="resize-none"
aria-label={t('detailPanel.toolSelector.descriptionLabel', { ns: 'plugin' })}
placeholder={t('detailPanel.toolSelector.descriptionPlaceholder', { ns: 'plugin' })}
value={value?.extra?.description || ''}
onChange={onDescriptionChange}
onValueChange={onDescriptionChange}
disabled={!value?.provider_name}
/>
</div>

View File

@@ -1,4 +1,3 @@
import type * as React from 'react'
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -161,9 +160,8 @@ describe('useToolSelectorState', () => {
useToolSelectorState({ value: toolValue, onSelect: mockOnSelect }),
)
const event = { target: { value: 'New description' } } as React.ChangeEvent<HTMLTextAreaElement>
act(() => {
result.current.handleDescriptionChange(event)
result.current.handleDescriptionChange('New description')
})
expect(mockOnSelect).toHaveBeenCalledWith(expect.objectContaining({

View File

@@ -144,14 +144,14 @@ export const useToolSelectorState = ({
onSelectMultiple?.(toolValues)
}, [getToolValue, onSelectMultiple])
const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
const handleDescriptionChange = useCallback((description: string) => {
if (!value)
return
onSelect({
...value,
extra: {
...value.extra,
description: e.target.value || '',
description: description || '',
},
})
}, [value, onSelect])

View File

@@ -47,17 +47,6 @@ vi.mock('@/app/components/base/input', () => ({
),
}))
vi.mock('@/app/components/base/textarea', () => ({
default: ({ value, onChange, ...props }: Record<string, unknown>) => (
<textarea
data-testid="description-textarea"
value={value as string}
onChange={onChange as () => void}
{...props}
/>
),
}))
vi.mock('@/app/components/base/app-icon', () => ({
default: ({ onClick }: { onClick?: () => void }) => (
<div data-testid="app-icon" onClick={onClick} />
@@ -102,7 +91,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
it('should initialize description as empty', () => {
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
const textarea = screen.getByTestId('description-textarea') as HTMLTextAreaElement
const textarea = screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.description' }) as HTMLTextAreaElement
expect(textarea.value).toBe('')
})
@@ -146,7 +135,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
it('should update description when textarea changes', () => {
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
const textarea = screen.getByTestId('description-textarea')
const textarea = screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.description' })
fireEvent.change(textarea, { target: { value: 'My description' } })
expect((textarea as HTMLTextAreaElement).value).toBe('My description')
@@ -225,7 +214,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
const nameInput = screen.getByTestId('name-input')
fireEvent.change(nameInput, { target: { value: ' Trimmed Name ' } })
const textarea = screen.getByTestId('description-textarea')
const textarea = screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.description' })
fireEvent.change(textarea, { target: { value: ' Some desc ' } })
fireEvent.click(screen.getByText('workflow.common.publish'))

View File

@@ -3,13 +3,13 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import type { IconInfo } from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiCloseLine } from '@remixicon/react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { useWorkflowStore } from '@/app/components/workflow/store'
type PublishAsKnowledgePipelineModalProps = {
@@ -108,9 +108,10 @@ const PublishAsKnowledgePipelineModal = ({
</div>
<Textarea
className="resize-none"
aria-label={t('common.publishAsPipeline.description', { ns: 'pipeline' })}
placeholder={t('common.publishAsPipeline.descriptionPlaceholder', { ns: 'pipeline' }) || ''}
value={description}
onChange={e => setDescription(e.target.value)}
onValueChange={value => setDescription(value)}
/>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import type { VisionFile, VisionSettings } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import {
RiLoader2Line,
RiPlayLargeLine,
@@ -18,7 +19,6 @@ import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uplo
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -159,10 +159,11 @@ const RunOnce: FC<IRunOnceProps> = ({
)}
{item.type === 'paragraph' && (
<Textarea
aria-label={item.name}
className="h-[104px] sm:text-xs"
placeholder={item.name}
value={inputs[item.key] as string}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
onValueChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
/>
)}
{item.type === 'number' && (

View File

@@ -13,6 +13,7 @@ import {
DrawerTitle,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiSettings2Line } from '@remixicon/react'
import { useDebounce, useGetState } from 'ahooks'
@@ -23,7 +24,6 @@ import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import EmojiPicker from '@/app/components/base/emoji-picker'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import LabelSelector from '@/app/components/tools/labels/selector'
import { parseParamsSchema } from '@/service/tools'
import { LinkExternal02 } from '../../base/icons/src/vender/line/general'
@@ -280,9 +280,10 @@ const EditCustomCollectionModal: FC<Props> = ({
</div>
<Textarea
aria-label={t('createTool.schema', { ns: 'tools' })}
className="h-[240px] resize-none"
value={schema}
onChange={e => setSchema(e.target.value)}
onValueChange={value => setSchema(value)}
placeholder={t('createTool.schemaPlaceHolder', { ns: 'tools' })!}
/>
</div>

View File

@@ -4,11 +4,11 @@ import type {
} from '@/app/components/tools/types'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiCloseLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import Textarea from '@/app/components/base/textarea'
import MCPServerParamItem from '@/app/components/tools/mcp/mcp-server-param-item'
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
import {
@@ -154,12 +154,12 @@ const MCPServerModal = ({
<div className="system-xs-regular text-text-destructive-secondary">*</div>
</div>
<Textarea
aria-label={t('mcp.server.modal.description', { ns: 'tools' })}
className="h-[96px] resize-none"
value={description}
placeholder={t('mcp.server.modal.descriptionPlaceholder', { ns: 'tools' })}
onChange={e => setDescription(e.target.value)}
>
</Textarea>
onValueChange={value => setDescription(value)}
/>
</div>
{latestParams.length > 0 && (

View File

@@ -1,7 +1,7 @@
'use client'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
type Props = {
data?: any
@@ -25,12 +25,12 @@ const MCPServerParamItem = ({
<div className="max-w-full min-w-0 system-xs-medium wrap-break-word text-text-tertiary">{data.type}</div>
</div>
<Textarea
aria-label={data.label}
className="h-8 resize-none"
value={value}
placeholder={t('mcp.server.modal.parametersPlaceholder', { ns: 'tools' })}
onChange={e => onChange(e.target.value)}
>
</Textarea>
onValueChange={value => onChange(value)}
/>
</div>
)
}

View File

@@ -13,6 +13,7 @@ import {
DrawerTitle,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { produce } from 'immer'
@@ -23,7 +24,6 @@ import AppIcon from '@/app/components/base/app-icon'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import { Infotip } from '@/app/components/base/infotip'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import LabelSelector from '@/app/components/tools/labels/selector'
import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal'
import MethodSelector from '@/app/components/tools/workflow-tool/method-selector'
@@ -256,9 +256,10 @@ export function WorkflowToolDrawer({
<div>
<div className="py-2 system-sm-medium text-text-primary">{t('createTool.description', { ns: 'tools' })}</div>
<Textarea
aria-label={t('createTool.description', { ns: 'tools' })}
placeholder={t('createTool.descriptionPlaceholder', { ns: 'tools' }) || ''}
value={description}
onChange={e => setDescription(e.target.value)}
onValueChange={value => setDescription(value)}
/>
</div>
{/* Tool Input */}

View File

@@ -4,6 +4,7 @@ import type { InputVar } from '../../../../types'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Textarea } from '@langgenius/dify-ui/textarea'
import {
RiDeleteBinLine,
} from '@remixicon/react'
@@ -18,7 +19,6 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import Input from '@/app/components/base/input'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import Textarea from '@/app/components/base/textarea'
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { Resolution, TransferMethod } from '@/types/app'
@@ -170,8 +170,9 @@ const FormItem: FC<Props> = ({
{
type === InputVarType.paragraph && (
<Textarea
aria-label={typeof payload.label === 'object' ? payload.label.variable : payload.label}
value={value || ''}
onChange={e => onChange(e.target.value)}
onValueChange={value => onChange(value)}
placeholder={typeof payload.label === 'object' ? payload.label.variable : payload.label}
autoFocus={autoFocus}
/>

View File

@@ -2,6 +2,7 @@
import type { FC } from 'react'
import type { AssignerNodeOperation } from '../../types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiDeleteBinLine } from '@remixicon/react'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
@@ -10,7 +11,6 @@ import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import ListNoDataPlaceholder from '@/app/components/workflow/nodes/_base/components/list-no-data-placeholder'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
@@ -190,8 +190,9 @@ const VarList: FC<Props> = ({
)}
{assignedVarType === 'string' && (
<Textarea
aria-label={item.variable_selector?.join('.') || t('nodes.assigner.setParameter', { ns: 'workflow' })}
value={item.value as string}
onChange={e => handleToAssignedVarChange(index)(e.target.value)}
onValueChange={value => handleToAssignedVarChange(index)(value)}
className="w-full"
/>
)}

View File

@@ -3,11 +3,11 @@ import type { FC } from 'react'
import type { HttpNodeType } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
import { useNodesInteractions } from '@/app/components/workflow/hooks'
import { parseCurl } from './curl-parser'
@@ -56,9 +56,10 @@ const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
<div>
<Textarea
aria-label={t('nodes.http.curl.title', { ns: 'workflow' })}
value={inputString}
className="my-3 h-40 w-full grow"
onChange={e => setInputString(e.target.value)}
onValueChange={value => setInputString(value)}
placeholder={t('nodes.http.curl.placeholder', { ns: 'workflow' })!}
/>
</div>

View File

@@ -2,12 +2,12 @@ import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Model } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiCloseLine, RiSparklingFill } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import Textarea from '@/app/components/base/textarea'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
type ModelInfo = {
@@ -38,8 +38,8 @@ const PromptEditor: FC<PromptEditorProps> = ({
}) => {
const { t } = useTranslation()
const handleInstructionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
onInstructionChange(e.target.value)
const handleInstructionChange = useCallback((value: string) => {
onInstructionChange(value)
}, [onInstructionChange])
return (
@@ -90,10 +90,11 @@ const PromptEditor: FC<PromptEditorProps> = ({
</div>
<div className="flex items-center">
<Textarea
aria-label={t('nodes.llm.jsonSchema.instruction', { ns: 'workflow' })}
className="h-[364px] resize-none px-2 py-1"
value={instruction}
placeholder={t('nodes.llm.jsonSchema.promptPlaceholder', { ns: 'workflow' })}
onChange={handleInstructionChange}
onValueChange={handleInstructionChange}
/>
</div>
</div>

View File

@@ -1,9 +1,9 @@
import type { FC } from 'react'
import { Textarea } from '@langgenius/dify-ui/textarea'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import Textarea from '@/app/components/base/textarea'
export type AdvancedOptionsType = {
enum: string
@@ -22,8 +22,8 @@ const AdvancedOptions: FC<AdvancedOptionsProps> = ({
// const [showAdvancedOptions, setShowAdvancedOptions] = useState(false)
const [enumValue, setEnumValue] = useState(options.enum)
const handleEnumChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
setEnumValue(e.target.value)
const handleEnumChange = useCallback((value: string) => {
setEnumValue(value)
}, [])
const handleEnumBlur = useCallback((e: React.FocusEvent<HTMLTextAreaElement>) => {
@@ -48,13 +48,14 @@ const AdvancedOptions: FC<AdvancedOptionsProps> = ({
</div>
<div className="flex flex-col">
<div className="flex h-6 items-center system-xs-medium text-text-secondary">
Enum
{t('nodes.llm.jsonSchema.enum', { ns: 'workflow' })}
</div>
<Textarea
aria-label={t('nodes.llm.jsonSchema.enum', { ns: 'workflow' })}
size="small"
className="min-h-6"
value={enumValue}
onChange={handleEnumChange}
onValueChange={handleEnumChange}
onBlur={handleEnumBlur}
placeholder="abcd, 1, 1.5, etc."
/>

View File

@@ -4,13 +4,13 @@ import type {
import type {
Var,
} from '@/app/components/workflow/types'
import { Textarea } from '@langgenius/dify-ui/textarea'
import {
useCallback,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -49,6 +49,10 @@ const FormItem = ({
onChange(e.target.value)
}, [onChange])
const handleValueChange = useCallback((value: string) => {
onChange(value)
}, [onChange])
const handleChange = useCallback((value: any) => {
onChange(value)
}, [onChange])
@@ -92,8 +96,9 @@ const FormItem = ({
{
value_type === ValueType.constant && var_type === VarType.string && (
<Textarea
aria-label={item.label}
value={value}
onChange={handleInputChange}
onValueChange={handleValueChange}
className="min-h-12 w-full"
/>
)

View File

@@ -6,6 +6,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { useBoolean } from 'ahooks'
import * as React from 'react'
@@ -14,7 +15,6 @@ import { useTranslation } from 'react-i18next'
import Field from '@/app/components/app/configuration/config-var/config-modal/field'
import ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { ChangeType } from '@/app/components/workflow/types'
import { checkKeys } from '@/utils/var'
import { ParamType } from '../../types'
@@ -175,8 +175,9 @@ const AddExtractParameter: FC<Props> = ({
)}
<Field title={t(`${i18nPrefix}.addExtractParameterContent.description`, { ns: 'workflow' })}>
<Textarea
aria-label={t(`${i18nPrefix}.addExtractParameterContent.description`, { ns: 'workflow' })}
value={param.description}
onChange={e => handleParamChange('description')(e.target.value)}
onValueChange={value => handleParamChange('description')(value)}
placeholder={t(`${i18nPrefix}.addExtractParameterContent.descriptionPlaceholder`, { ns: 'workflow' })!}
/>
</Field>

View File

@@ -3,10 +3,10 @@ import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-c
import type { ParentMode } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Markdown } from '@/app/components/base/markdown'
import Textarea from '@/app/components/base/textarea'
import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list'
import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor'
import { ChunkingMode } from '@/models/datasets'
@@ -98,11 +98,12 @@ export function DisplayContent(props: DisplayContentProps) {
previewType === PreviewType.Markdown
? (
<Textarea
aria-label={t('debug.variableInspect.markdownContent', { ns: 'workflow' })}
readOnly={readonly}
disabled={readonly}
className="h-full border-none bg-transparent p-0 text-text-secondary hover:bg-transparent focus:bg-transparent focus:shadow-none"
value={mdString as any}
onChange={e => handleTextChange?.(e.target.value)}
onValueChange={value => handleTextChange?.(value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>

View File

@@ -2,9 +2,10 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { FileUploadConfigResponse } from '@/models/common'
import type { VarInInspect } from '@/types/workflow'
import { cn } from '@langgenius/dify-ui/cn'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useTranslation } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import Textarea from '@/app/components/base/textarea'
import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message'
import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
@@ -30,6 +31,8 @@ export const TextEditorSection = ({
isTruncated,
onTextChange,
}: TextEditorSectionProps) => {
const { t } = useTranslation()
return (
<>
{isTruncated && <LargeDataAlert className="absolute inset-x-3 top-1" />}
@@ -46,11 +49,12 @@ export const TextEditorSection = ({
)
: (
<Textarea
aria-label={t('errorMsg.fields.variableValue', { ns: 'workflow' })}
readOnly={textEditorDisabled}
disabled={textEditorDisabled || isTruncated}
className={cn('h-full', isTruncated && 'pt-[48px]')}
value={typeof value === 'number' ? value : String(value ?? '')}
onChange={e => onTextChange(e.target.value)}
onValueChange={value => onTextChange(value)}
/>
)}
</>

View File

@@ -108,13 +108,14 @@ describe('DatasetCardTags', () => {
expect(wrapper).not.toHaveClass('opacity-30')
})
it('should hide mask with CSS when the tag area is hovered', () => {
it('should keep the overflow mask independent from dataset card hover', () => {
const { container } = render(<DatasetCardTags {...defaultProps} />)
const maskDiv = container.querySelector('.bg-tag-selector-mask-bg')
expect(maskDiv).toBeInTheDocument()
expect(maskDiv).toHaveClass('group-hover/tag-area:hidden')
expect(maskDiv).toHaveClass('group-focus-within/tag-area:hidden')
expect(maskDiv).toHaveClass('group-hover:bg-tag-selector-mask-hover-bg')
expect(maskDiv).not.toHaveClass('group-hover:bg-tag-selector-mask-hover-bg')
expect(maskDiv?.parentElement).toHaveClass('relative', 'w-full', 'overflow-hidden')
})
it('should keep TagSelector visible when tags are empty', () => {

View File

@@ -49,6 +49,16 @@ describe('AppCardTags', () => {
value: tags,
}))
})
it('should keep the overflow mask independent from app card hover', () => {
const { container } = render(<AppCardTags appId="app-1" tags={tags} />)
const mask = container.querySelector('.bg-tag-selector-mask-bg')
expect(mask).toBeInTheDocument()
expect(mask).toHaveClass('group-hover/tag-area:hidden')
expect(mask).toHaveClass('group-focus-within/tag-area:hidden')
expect(mask).not.toHaveClass('group-hover:bg-tag-selector-mask-hover-bg')
})
})
describe('Callbacks', () => {

View File

@@ -24,7 +24,7 @@ export const AppCardTags = ({
onOpenTagManagement={onOpenTagManagement}
onTagsChange={onTagsChange}
/>
<div className="pointer-events-none absolute top-0 right-0 h-full w-20 bg-tag-selector-mask-bg group-focus-within/tag-area:hidden group-hover:bg-tag-selector-mask-hover-bg group-hover/tag-area:hidden" />
<div className="pointer-events-none absolute top-0 right-0 h-full w-20 bg-tag-selector-mask-bg group-focus-within/tag-area:hidden group-hover/tag-area:hidden" />
</div>
)
}

View File

@@ -21,10 +21,10 @@ export const DatasetCardTags = ({
onTagsChange,
}: DatasetCardTagsProps) => (
<div
className={cn('group/tag-area relative w-full px-3', !embeddingAvailable && 'opacity-30')}
className={cn('group/tag-area w-full px-3', !embeddingAvailable && 'opacity-30')}
onClick={onClick}
>
<div className="w-full">
<div className="relative w-full overflow-hidden">
<TagSelector
placement="bottom-start"
type="knowledge"
@@ -33,9 +33,9 @@ export const DatasetCardTags = ({
onOpenTagManagement={onOpenTagManagement}
onTagsChange={onTagsChange}
/>
<div
className="pointer-events-none absolute top-0 right-0 h-full w-20 bg-tag-selector-mask-bg group-focus-within/tag-area:hidden group-hover/tag-area:hidden"
/>
</div>
<div
className="absolute top-0 right-0 h-full w-20 bg-tag-selector-mask-bg group-focus-within/tag-area:hidden group-hover:bg-tag-selector-mask-hover-bg group-hover/tag-area:hidden"
/>
</div>
)

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "الآن يمكنك إنشاء أحداث في {{- pluginName}}، واسترجاع المخرجات من هذه الأحداث في فحص المتغير.",
"debug.variableInspect.listening.tipSchedule": "الاستماع للأحداث من مشغلات الجدول.\nالتشغيل المجدول التالي: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "الاستماع للأحداث من المشغلات...",
"debug.variableInspect.markdownContent": "محتوى Markdown",
"debug.variableInspect.reset": "إعادة تعيين إلى قيمة آخر تشغيل",
"debug.variableInspect.resetConversationVar": "إعادة تعيين متغير المحادثة إلى القيمة الافتراضية",
"debug.variableInspect.systemNode": "النظام",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "رجوع",
"nodes.llm.jsonSchema.descriptionPlaceholder": "إضافة وصف",
"nodes.llm.jsonSchema.doc": "معرفة المزيد عن الإخراج المنظم",
"nodes.llm.jsonSchema.enum": "تعداد",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "اسم الخاصية موجود بالفعل",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "اسم الحقل",
"nodes.llm.jsonSchema.generate": "توليد",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Jetzt können Sie in {{- pluginName}} Ereignisse erstellen und Ausgaben dieser Ereignisse im Variableninspektor abrufen.",
"debug.variableInspect.listening.tipSchedule": "Hört auf Ereignisse von Zeitplan-Auslösern. Nächster geplanter Lauf: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Höre auf Ereignisse von Triggern...",
"debug.variableInspect.markdownContent": "Markdown-Inhalt",
"debug.variableInspect.reset": "Auf den letzten Ausführungswert zurücksetzen",
"debug.variableInspect.resetConversationVar": "Setze die Gesprächsvariable auf den Standardwert zurück",
"debug.variableInspect.systemNode": "System",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Zurück",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Fügen Sie eine Beschreibung hinzu.",
"nodes.llm.jsonSchema.doc": "Erfahren Sie mehr über strukturierten Output.",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Eigenschaftsname existiert bereits",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Feldname",
"nodes.llm.jsonSchema.generate": "Generieren",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Now you can create events in {{- pluginName}}, and retrieve outputs from these events in the Variable Inspector.",
"debug.variableInspect.listening.tipSchedule": "Listening for events from schedule triggers.\nNext scheduled run: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Listening for events from triggers...",
"debug.variableInspect.markdownContent": "Markdown content",
"debug.variableInspect.reset": "Reset to last run value",
"debug.variableInspect.resetConversationVar": "Reset conversation variable to default value",
"debug.variableInspect.systemNode": "System",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Back",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Add description",
"nodes.llm.jsonSchema.doc": "Learn more about structured output",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Property name already exists",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Field Name",
"nodes.llm.jsonSchema.generate": "Generate",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Ahora puedes crear eventos en {{- pluginName}} y obtener los resultados de estos eventos en el Inspector de Variables.",
"debug.variableInspect.listening.tipSchedule": "Escuchando eventos de los desencadenadores de programación.\nPróxima ejecución programada: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Escuchando eventos desde los activadores...",
"debug.variableInspect.markdownContent": "Contenido Markdown",
"debug.variableInspect.reset": "Restablecer al último valor ejecutado",
"debug.variableInspect.resetConversationVar": "Restablecer la variable de conversación al valor predeterminado",
"debug.variableInspect.systemNode": "Sistema",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Atrás",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Agregar descripción",
"nodes.llm.jsonSchema.doc": "Aprender más sobre la salida estructurada",
"nodes.llm.jsonSchema.enum": "Enumeración",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "El nombre de la propiedad ya existe",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nombre del campo",
"nodes.llm.jsonSchema.generate": "Generar",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "اکنون می‌توانید در {{- pluginName}} رویداد ایجاد کنید و خروجی‌ها را در بازرسی متغیر مشاهده کنید.",
"debug.variableInspect.listening.tipSchedule": "گوش دادن به رویدادها از تریگرهای زمان‌بندی‌شده.\nزمان اجرای بعدی: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "در انتظار رویدادها از تریگرها...",
"debug.variableInspect.markdownContent": "محتوای Markdown",
"debug.variableInspect.reset": "بازنشانی به آخرین مقدار اجراشده",
"debug.variableInspect.resetConversationVar": "بازنشانی متغیر مکالمه به مقدار پیش‌فرض",
"debug.variableInspect.systemNode": "سیستم",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "بازگشت",
"nodes.llm.jsonSchema.descriptionPlaceholder": "افزودن توضیحات",
"nodes.llm.jsonSchema.doc": "درباره خروجی ساختاریافته بیشتر بدانید",
"nodes.llm.jsonSchema.enum": "شمارش",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "نام ویژگی از قبل وجود دارد",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "نام فیلد",
"nodes.llm.jsonSchema.generate": "تولید",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Vous pouvez maintenant créer des événements dans {{- pluginName}} et récupérer les résultats de ces événements dans l'Inspecteur de Variables.",
"debug.variableInspect.listening.tipSchedule": "Écoute des événements des déclencheurs de planification.\nProchaine exécution planifiée : {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "En attente d'événements provenant des déclencheurs...",
"debug.variableInspect.markdownContent": "Contenu Markdown",
"debug.variableInspect.reset": "Réinitialiser à la dernière valeur d'exécution",
"debug.variableInspect.resetConversationVar": "Réinitialiser la variable de conversation à la valeur par défaut",
"debug.variableInspect.systemNode": "Système",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Retour",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Ajouter une description",
"nodes.llm.jsonSchema.doc": "En savoir plus sur la sortie structurée",
"nodes.llm.jsonSchema.enum": "Énumération",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Le nom de la propriété existe déjà",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nom du champ",
"nodes.llm.jsonSchema.generate": "Générer",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "अब आप {{- pluginName}} में ईवेंट बना सकते हैं, और वैरिएबल इंस्पेक्टर में इन ईवेंट्स के आउटपुट प्राप्त कर सकते हैं।",
"debug.variableInspect.listening.tipSchedule": "अनुसूची ट्रिगर्स से घटनाओं के लिए सुनना।\nअगली निर्धारित रन: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "ट्रिगर से घटनाओं को सुनना...",
"debug.variableInspect.markdownContent": "Markdown सामग्री",
"debug.variableInspect.reset": "अंतिम रन मान पर रीसेट करें",
"debug.variableInspect.resetConversationVar": "संवाद चर को डिफ़ॉल्ट मान पर रीसेट करें",
"debug.variableInspect.systemNode": "प्रणाली",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "पीछे",
"nodes.llm.jsonSchema.descriptionPlaceholder": "विवरण जोड़ें",
"nodes.llm.jsonSchema.doc": "संरचित आउटपुट के बारे में अधिक जानें",
"nodes.llm.jsonSchema.enum": "एनम",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "प्रॉपर्टी नाम पहले से मौजूद है",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "क्षेत्र नाम",
"nodes.llm.jsonSchema.generate": "जनरेट करें",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Sekarang Anda dapat membuat acara di {{- pluginName}}, dan mengambil hasil dari acara ini di Inspektur Variabel.",
"debug.variableInspect.listening.tipSchedule": "Mendengarkan acara dari pemicu jadwal.\nJalankan berikutnya yang dijadwalkan: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Mendengarkan peristiwa dari pemicu...",
"debug.variableInspect.markdownContent": "Konten Markdown",
"debug.variableInspect.reset": "Atur ulang ke nilai eksekusi terakhir",
"debug.variableInspect.resetConversationVar": "Mengatur ulang variabel percakapan ke nilai default",
"debug.variableInspect.systemNode": "Sistem",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Belakang",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Tambahkan deskripsi",
"nodes.llm.jsonSchema.doc": "Pelajari output terstruktur lebih lanjut",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Nama properti sudah ada",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nama Bidang",
"nodes.llm.jsonSchema.generate": "Menghasilkan",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Ora puoi creare eventi in {{- pluginName}} e recuperare i risultati di questi eventi nell'Ispettore Variabili.",
"debug.variableInspect.listening.tipSchedule": "Ascolto degli eventi dai trigger del programma.\nProssima esecuzione programmata: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "In ascolto degli eventi dai trigger...",
"debug.variableInspect.markdownContent": "Contenuto Markdown",
"debug.variableInspect.reset": "Ripristina il valore dell'ultima esecuzione",
"debug.variableInspect.resetConversationVar": "Reimposta la variabile della conversazione al valore predefinito",
"debug.variableInspect.systemNode": "Sistema",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Indietro",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Aggiungi descrizione",
"nodes.llm.jsonSchema.doc": "Scopri di più sull'output strutturato",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Il nome della proprietà esiste già",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nome del campo",
"nodes.llm.jsonSchema.generate": "Genera",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "{{- pluginName}} でイベントを作成し、これらのイベントの出力を Variable Inspector で取得できます。",
"debug.variableInspect.listening.tipSchedule": "スケジュールトリガーからのイベントを待機しています。\n次回の予定実行: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "トリガーからのイベントを待機中…",
"debug.variableInspect.markdownContent": "Markdown コンテンツ",
"debug.variableInspect.reset": "最後の実行値にリセットする",
"debug.variableInspect.resetConversationVar": "会話の変数をデフォルト値にリセットする",
"debug.variableInspect.systemNode": "システム",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "前に戻る",
"nodes.llm.jsonSchema.descriptionPlaceholder": "説明を入力",
"nodes.llm.jsonSchema.doc": "構造化出力の詳細を見る",
"nodes.llm.jsonSchema.enum": "列挙型",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "プロパティ名はすでに存在します",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "フィールド名",
"nodes.llm.jsonSchema.generate": "生成",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "이제 {{- pluginName}}에서 이벤트를 생성하고, 변수 검사기에서 이러한 이벤트의 출력을 확인할 수 있습니다.",
"debug.variableInspect.listening.tipSchedule": "스케줄 트리거의 이벤트를 수신 대기 중입니다.\n다음 예약 실행: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "트리거 이벤트 수신 대기 중...",
"debug.variableInspect.markdownContent": "Markdown 콘텐츠",
"debug.variableInspect.reset": "마지막 실행 값으로 재설정",
"debug.variableInspect.resetConversationVar": "대화 변수를 기본 값으로 재설정합니다.",
"debug.variableInspect.systemNode": "시스템",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "뒤",
"nodes.llm.jsonSchema.descriptionPlaceholder": "설명을 추가하세요.",
"nodes.llm.jsonSchema.doc": "구조화된 출력에 대해 더 알아보세요.",
"nodes.llm.jsonSchema.enum": "열거형",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "속성 이름이 이미 존재합니다",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "필드 이름",
"nodes.llm.jsonSchema.generate": "생성",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Now you can create events in {{- pluginName}}, and retrieve outputs from these events in the Variable Inspector.",
"debug.variableInspect.listening.tipSchedule": "Listening for events from schedule triggers.\nNext scheduled run: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Listening for events from triggers...",
"debug.variableInspect.markdownContent": "Markdown-inhoud",
"debug.variableInspect.reset": "Reset to last run value",
"debug.variableInspect.resetConversationVar": "Reset conversation variable to default value",
"debug.variableInspect.systemNode": "System",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Back",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Add description",
"nodes.llm.jsonSchema.doc": "Learn more about structured output",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Eigenschapsnaam bestaat al",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Field Name",
"nodes.llm.jsonSchema.generate": "Generate",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Teraz możesz tworzyć zdarzenia w {{- pluginName}} i pobierać wyniki z tych zdarzeń w Inspektorze Zmiennych.",
"debug.variableInspect.listening.tipSchedule": "Nasłuchiwanie zdarzeń z wyzwalaczy harmonogramu.\nNastępne zaplanowane uruchomienie: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Oczekiwanie na zdarzenia wywoływane przez wyzwalacze...",
"debug.variableInspect.markdownContent": "Treść Markdown",
"debug.variableInspect.reset": "Zresetuj do ostatniej wartości run",
"debug.variableInspect.resetConversationVar": "Zresetuj zmienną rozmowy do wartości domyślnej",
"debug.variableInspect.systemNode": "System",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Tył",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Dodaj opis",
"nodes.llm.jsonSchema.doc": "Dowiedz się więcej o zorganizowanym wyjściu",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Nazwa właściwości już istnieje",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nazwa pola",
"nodes.llm.jsonSchema.generate": "Generować",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Agora você pode criar eventos em {{- pluginName}} e recuperar resultados desses eventos no Inspetor de Variáveis.",
"debug.variableInspect.listening.tipSchedule": "Ouvindo eventos de gatilhos de agendamento.\nPróxima execução agendada: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Aguardando eventos dos gatilhos...",
"debug.variableInspect.markdownContent": "Conteúdo Markdown",
"debug.variableInspect.reset": "Redefinir para o último valor de execução",
"debug.variableInspect.resetConversationVar": "Redefinir a variável da conversa para o valor padrão",
"debug.variableInspect.systemNode": "Sistema",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Voltar",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Adicionar descrição",
"nodes.llm.jsonSchema.doc": "Saiba mais sobre saída estruturada",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "O nome da propriedade já existe",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Nome do Campo",
"nodes.llm.jsonSchema.generate": "Gerar",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Acum poți crea evenimente în {{- pluginName}} și poți prelua rezultatele acestor evenimente în Inspectorul de Variabile.",
"debug.variableInspect.listening.tipSchedule": "Ascultarea evenimentelor de la declanșatoarele de programare.\nUrmătoarea rulare programată: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Ascult pentru evenimente de la declanșatoare...",
"debug.variableInspect.markdownContent": "Conținut Markdown",
"debug.variableInspect.reset": "Resetează la ultima valoare rulată",
"debug.variableInspect.resetConversationVar": "Resetați variabila de conversație la valoarea implicită",
"debug.variableInspect.systemNode": "Sistem",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Înapoi",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Adăugați o descriere",
"nodes.llm.jsonSchema.doc": "Aflați mai multe despre ieșirea structurată",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Numele proprietății există deja",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Numele câmpului",
"nodes.llm.jsonSchema.generate": "Generează",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Теперь вы можете создавать события в {{- pluginName}} и получать данные этих событий в Инспекторе переменных.",
"debug.variableInspect.listening.tipSchedule": "Прослушивание событий от триггеров расписания.\nСледующий запланированный запуск: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Ожидание событий от триггеров...",
"debug.variableInspect.markdownContent": "Содержимое Markdown",
"debug.variableInspect.reset": "Сбросить до последнего значения выполнения",
"debug.variableInspect.resetConversationVar": "Сбросить переменную разговора до значения по умолчанию",
"debug.variableInspect.systemNode": "Система",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Спина",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Добавить описание",
"nodes.llm.jsonSchema.doc": "Узнайте больше о структурированном выводе",
"nodes.llm.jsonSchema.enum": "Перечисление",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Имя свойства уже существует",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Название поля",
"nodes.llm.jsonSchema.generate": "Сгенерировать",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Zdaj lahko ustvarjate dogodke v {{- pluginName}} in pridobivate izhode iz teh dogodkov v Inšpektorju spremenljivk.",
"debug.variableInspect.listening.tipSchedule": "Poslušanje dogodkov iz sprožilcev urnika.\nNaslednje načrtovano izvajanje: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Poslušanje dogodkov iz sprožilcev...",
"debug.variableInspect.markdownContent": "Vsebina Markdown",
"debug.variableInspect.reset": "Ponastavi na zadnjo vrednost izvajanja",
"debug.variableInspect.resetConversationVar": "Ponastavi spremenljivko pogovora na privzeto vrednost",
"debug.variableInspect.systemNode": "Sistem",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Nazaj",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Dodajte opis",
"nodes.llm.jsonSchema.doc": "Izvedite več o strukturiranem izhodu",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Ime lastnosti že obstaja",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Ime polja",
"nodes.llm.jsonSchema.generate": "Generirati",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "ตอนนี้คุณสามารถสร้างกิจกรรมใน {{- pluginName}} และดึงผลลัพธ์จากกิจกรรมเหล่านี้ในตัวตรวจสอบตัวแปรได้แล้ว",
"debug.variableInspect.listening.tipSchedule": "กำลังรอฟังเหตุการณ์จากตัวเรียกใช้งานตามตาราง การทำงานครั้งถัดไปตามตาราง: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "กำลังรอฟังเหตุการณ์จากทริกเกอร์...",
"debug.variableInspect.markdownContent": "เนื้อหา Markdown",
"debug.variableInspect.reset": "รีเซ็ตกลับไปยังค่าครั้งล่าสุด",
"debug.variableInspect.resetConversationVar": "รีเซ็ตตัวแปรการสนทนาไปยังค่าตั้งต้น",
"debug.variableInspect.systemNode": "ระบบ",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "กลับ",
"nodes.llm.jsonSchema.descriptionPlaceholder": "เพิ่มคำอธิบาย",
"nodes.llm.jsonSchema.doc": "เรียนรู้เพิ่มเติมเกี่ยวกับผลลัพธ์ที่มีโครงสร้าง",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "ชื่อคุณสมบัติมีอยู่แล้ว",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "ชื่อฟิลด์",
"nodes.llm.jsonSchema.generate": "สร้าง",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Artık {{- pluginName}} içinde etkinlikler oluşturabilir ve bu etkinliklerden elde edilen çıktıları Değişken Denetleyicisinde görebilirsiniz.",
"debug.variableInspect.listening.tipSchedule": "Zamanlayıcı tetikleyicilerinden etkinlikleri dinleme. Bir sonraki planlanan çalıştırma: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Tetikleyicilerden olaylar dinleniyor...",
"debug.variableInspect.markdownContent": "Markdown içeriği",
"debug.variableInspect.reset": "Son çalıştırma değerine sıfırla",
"debug.variableInspect.resetConversationVar": "Konuşma değişkenini varsayılan değere sıfırla",
"debug.variableInspect.systemNode": "Sistem",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Geri",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Açıklama ekleyin",
"nodes.llm.jsonSchema.doc": "Yapılandırılmış çıktı hakkında daha fazla bilgi edinin",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Özellik adı zaten mevcut",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Alan Adı",
"nodes.llm.jsonSchema.generate": "Oluştur",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Тепер ви можете створювати події в {{- pluginName}} та отримувати результати цих подій у Інспекторі змінних.",
"debug.variableInspect.listening.tipSchedule": "Прослуховування подій від тригерів розкладу. Наступний запланований запуск: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Очікування подій від тригерів...",
"debug.variableInspect.markdownContent": "Вміст Markdown",
"debug.variableInspect.reset": "Скинути до значення останнього запуску",
"debug.variableInspect.resetConversationVar": "Скинути змінну розмови на значення за замовчуванням",
"debug.variableInspect.systemNode": "Система",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Назад",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Додати опис",
"nodes.llm.jsonSchema.doc": "Дізнайтеся більше про структурований вихід",
"nodes.llm.jsonSchema.enum": "Перелік",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Ім'я властивості вже існує",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Назва поля",
"nodes.llm.jsonSchema.generate": "Генерувати",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "Bây giờ bạn có thể tạo các sự kiện trong {{- pluginName}} và lấy kết quả từ các sự kiện này trong Trình kiểm tra Biến.",
"debug.variableInspect.listening.tipSchedule": "Lắng nghe sự kiện từ các tác nhân kích hoạt theo lịch. Chạy theo lịch tiếp theo: {{nextTriggerTime}}",
"debug.variableInspect.listening.title": "Đang lắng nghe các sự kiện từ các kích hoạt...",
"debug.variableInspect.markdownContent": "Nội dung Markdown",
"debug.variableInspect.reset": "Đặt lại thành giá trị của lần chạy cuối cùng",
"debug.variableInspect.resetConversationVar": "Đặt lại biến cuộc trò chuyện về giá trị mặc định",
"debug.variableInspect.systemNode": "Hệ thống",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "Quay lại",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Thêm mô tả",
"nodes.llm.jsonSchema.doc": "Tìm hiểu thêm về đầu ra có cấu trúc",
"nodes.llm.jsonSchema.enum": "Enum",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Tên thuộc tính đã tồn tại",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Tên trường",
"nodes.llm.jsonSchema.generate": "Tạo ra",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "现在您可以在 {{- pluginName}} 中创建事件,并在变量检查器中查看这些事件的输出。",
"debug.variableInspect.listening.tipSchedule": "正在监听计划触发器事件。\n下一次计划运行时间{{nextTriggerTime}}",
"debug.variableInspect.listening.title": "正在监听触发器事件…",
"debug.variableInspect.markdownContent": "Markdown 内容",
"debug.variableInspect.reset": "还原至上一次运行",
"debug.variableInspect.resetConversationVar": "重置会话变量为默认值",
"debug.variableInspect.systemNode": "系统变量",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "返回",
"nodes.llm.jsonSchema.descriptionPlaceholder": "添加描述",
"nodes.llm.jsonSchema.doc": "了解有关结构化输出的更多信息",
"nodes.llm.jsonSchema.enum": "枚举",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "属性名称已存在",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "字段名",
"nodes.llm.jsonSchema.generate": "生成",

View File

@@ -302,6 +302,7 @@
"debug.variableInspect.listening.tipPlugin": "您現在可以在 {{- pluginName}} 中建立事件,並在變數檢視器中檢視這些事件的輸出。",
"debug.variableInspect.listening.tipSchedule": "正在監聽排程觸發器事件。\n下一次排程執行時間{{nextTriggerTime}}",
"debug.variableInspect.listening.title": "正在監聽觸發器事件…",
"debug.variableInspect.markdownContent": "Markdown 內容",
"debug.variableInspect.reset": "重置為上次運行值",
"debug.variableInspect.resetConversationVar": "將對話變數重置為默認值",
"debug.variableInspect.systemNode": "系統",
@@ -788,6 +789,7 @@
"nodes.llm.jsonSchema.back": "返回",
"nodes.llm.jsonSchema.descriptionPlaceholder": "新增描述",
"nodes.llm.jsonSchema.doc": "了解更多有關結構化輸出的資訊",
"nodes.llm.jsonSchema.enum": "列舉",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "屬性名稱已存在",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "欄位名稱",
"nodes.llm.jsonSchema.generate": "生成",