-
{value}
-
- {items.map(item => (
-
- ))}
-
+
+ {children}
+
+ ),
+ SelectTrigger: ({
+ children,
+ className,
+ 'data-testid': testId,
+ }: {
+ 'children': React.ReactNode
+ 'className'?: string
+ 'data-testid'?: string
+ }) => (
+
+ ),
+ SelectValue: () => {
+ const { value } = React.useContext(MockSelectContext)
+ return
{value}
+ },
+ SelectContent: ({
+ children,
+ popupClassName,
+ }: {
+ children: React.ReactNode
+ popupClassName?: string
+ }) => (
+
+ {children}
),
+ SelectItem: ({
+ children,
+ value,
+ }: {
+ children: React.ReactNode
+ value: string
+ }) => {
+ const { onValueChange } = React.useContext(MockSelectContext)
+ return (
+
+ )
+ },
}))
// ==================== Test Utilities ====================
@@ -139,7 +169,7 @@ describe('TTSParamsPanel', () => {
expect(screen.getByText('appDebug.voice.voiceSettings.voice')).toBeInTheDocument()
})
- it('should render two PortalSelect components', () => {
+ it('should render two Select components', () => {
// Arrange
const props = createDefaultProps()
@@ -147,7 +177,7 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
+ const selects = screen.getAllByTestId('select-root')
expect(selects).toHaveLength(2)
})
@@ -159,8 +189,8 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-value', 'zh-Hans')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[0]).toHaveTextContent('zh-Hans')
})
it('should render voice select with correct value', () => {
@@ -171,8 +201,8 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[1]).toHaveAttribute('data-value', 'echo')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[1]).toHaveTextContent('echo')
})
it('should only show supported languages in language select', () => {
@@ -205,7 +235,7 @@ describe('TTSParamsPanel', () => {
// ==================== Props Testing ====================
describe('Props', () => {
- it('should apply trigger className to PortalSelect', () => {
+ it('should apply trigger className to SelectTrigger', () => {
// Arrange
const props = createDefaultProps()
@@ -213,12 +243,11 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-trigger-class', 'h-8')
- expect(selects[1]).toHaveAttribute('data-trigger-class', 'h-8')
+ expect(screen.getByTestId('tts-language-select-trigger')).toHaveAttribute('data-class', 'w-full')
+ expect(screen.getByTestId('tts-voice-select-trigger')).toHaveAttribute('data-class', 'w-full')
})
- it('should apply popup className to PortalSelect', () => {
+ it('should apply popup className to SelectContent', () => {
// Arrange
const props = createDefaultProps()
@@ -226,22 +255,9 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-popup-class', 'z-[1000]')
- expect(selects[1]).toHaveAttribute('data-popup-class', 'z-[1000]')
- })
-
- it('should apply popup inner className to PortalSelect', () => {
- // Arrange
- const props = createDefaultProps()
-
- // Act
- render(
)
-
- // Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-popup-inner-class', 'w-[354px]')
- expect(selects[1]).toHaveAttribute('data-popup-inner-class', 'w-[354px]')
+ const contents = screen.getAllByTestId('select-content')
+ expect(contents[0]).toHaveAttribute('data-popup-class', 'w-[354px]')
+ expect(contents[1]).toHaveAttribute('data-popup-class', 'w-[354px]')
})
})
@@ -411,10 +427,8 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert - no voice items (except language items)
- const voiceSelects = screen.getAllByTestId('portal-select')
- // Second select is voice select, should have no voice items in items-container
- const voiceItemsContainer = voiceSelects[1].querySelector('[data-testid="items-container"]')
- expect(voiceItemsContainer?.children).toHaveLength(0)
+ expect(screen.getAllByTestId('select-content')[1].children).toHaveLength(0)
+ expect(screen.queryByTestId('select-item-alloy')).not.toBeInTheDocument()
})
it('should handle currentModel with single voice', () => {
@@ -443,8 +457,8 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-value', '')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[0]).toHaveTextContent('')
})
it('should handle empty voice value', () => {
@@ -455,8 +469,8 @@ describe('TTSParamsPanel', () => {
render(
)
// Assert
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[1]).toHaveAttribute('data-value', '')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[1]).toHaveTextContent('')
})
it('should handle many voices', () => {
@@ -514,14 +528,14 @@ describe('TTSParamsPanel', () => {
// Act
const { rerender } = render(
)
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[0]).toHaveAttribute('data-value', 'en-US')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[0]).toHaveTextContent('en-US')
rerender(
)
// Assert
- const updatedSelects = screen.getAllByTestId('portal-select')
- expect(updatedSelects[0]).toHaveAttribute('data-value', 'zh-Hans')
+ const updatedValues = screen.getAllByTestId('selected-value')
+ expect(updatedValues[0]).toHaveTextContent('zh-Hans')
})
it('should update when voice prop changes', () => {
@@ -530,14 +544,14 @@ describe('TTSParamsPanel', () => {
// Act
const { rerender } = render(
)
- const selects = screen.getAllByTestId('portal-select')
- expect(selects[1]).toHaveAttribute('data-value', 'alloy')
+ const values = screen.getAllByTestId('selected-value')
+ expect(values[1]).toHaveTextContent('alloy')
rerender(
)
// Assert
- const updatedSelects = screen.getAllByTestId('portal-select')
- expect(updatedSelects[1]).toHaveAttribute('data-value', 'echo')
+ const updatedValues = screen.getAllByTestId('selected-value')
+ expect(updatedValues[1]).toHaveTextContent('echo')
})
it('should update voice list when currentModel changes', () => {
diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx
index 97947f48c1..461b229602 100644
--- a/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx
@@ -1,9 +1,8 @@
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
-import { PortalSelect } from '@/app/components/base/select'
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/app/components/base/ui/select'
import { languages } from '@/i18n-config/language'
-import { cn } from '@/utils/classnames'
type Props = {
currentModel: any
@@ -12,6 +11,8 @@ type Props = {
onChange: (language: string, voice: string) => void
}
+const supportedLanguages = languages.filter(item => item.supported)
+
const TTSParamsPanel = ({
currentModel,
language,
@@ -19,11 +20,11 @@ const TTSParamsPanel = ({
onChange,
}: Props) => {
const { t } = useTranslation()
- const voiceList = useMemo(() => {
+ const voiceList = useMemo
>(() => {
if (!currentModel)
return []
- return currentModel.model_properties.voices.map((item: { mode: any }) => ({
- ...item,
+ return currentModel.model_properties.voices.map((item: { mode: string, name: string }) => ({
+ label: item.name,
value: item.mode,
}))
}, [currentModel])
@@ -39,27 +40,57 @@ const TTSParamsPanel = ({
{t('voice.voiceSettings.language', { ns: 'appDebug' })}
- item.supported)}
- onSelect={item => setLanguage(item.value as string)}
- />
+ onValueChange={(value) => {
+ if (value == null)
+ return
+ setLanguage(value)
+ }}
+ >
+
+
+
+
+ {supportedLanguages.map(item => (
+
+ {item.name}
+
+ ))}
+
+