dify
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { useEffect } from 'react'
|
||||
import type { ComponentProps } from 'react'
|
||||
import AudioBtn from '.'
|
||||
import { ensureMockAudioManager } from '../../../../.storybook/utils/audio-player-manager.mock'
|
||||
|
||||
ensureMockAudioManager()
|
||||
|
||||
const StoryWrapper = (props: ComponentProps<typeof AudioBtn>) => {
|
||||
useEffect(() => {
|
||||
ensureMockAudioManager()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center space-x-3">
|
||||
<AudioBtn {...props} />
|
||||
<span className="text-xs text-gray-500">Audio toggle using ActionButton styling</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/General/NewAudioButton',
|
||||
component: AudioBtn,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Updated audio playback trigger styled with `ActionButton`. Behaves like the legacy audio button but adopts the new button design system.',
|
||||
},
|
||||
},
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/apps/demo-app/text-to-audio',
|
||||
params: { appId: 'demo-app' },
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
id: {
|
||||
control: 'text',
|
||||
description: 'Message identifier used by the audio request.',
|
||||
},
|
||||
value: {
|
||||
control: 'text',
|
||||
description: 'Prompt or response text that will be converted to speech.',
|
||||
},
|
||||
voice: {
|
||||
control: 'text',
|
||||
description: 'Voice profile for the generated speech.',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof AudioBtn>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: args => <StoryWrapper {...args} />,
|
||||
args: {
|
||||
id: 'message-1',
|
||||
value: 'Listen to the latest assistant message.',
|
||||
voice: 'alloy',
|
||||
},
|
||||
}
|
||||
99
dify/web/app/components/base/new-audio-button/index.tsx
Normal file
99
dify/web/app/components/base/new-audio-button/index.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useParams, usePathname } from 'next/navigation'
|
||||
import {
|
||||
RiVolumeUpLine,
|
||||
} from '@remixicon/react'
|
||||
import { t } from 'i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
|
||||
type AudioBtnProps = {
|
||||
id?: string
|
||||
voice?: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
type AudioState = 'initial' | 'loading' | 'playing' | 'paused' | 'ended'
|
||||
|
||||
const AudioBtn = ({
|
||||
id,
|
||||
voice,
|
||||
value,
|
||||
}: AudioBtnProps) => {
|
||||
const [audioState, setAudioState] = useState<AudioState>('initial')
|
||||
|
||||
const params = useParams()
|
||||
const pathname = usePathname()
|
||||
const audio_finished_call = (event: string): any => {
|
||||
switch (event) {
|
||||
case 'ended':
|
||||
setAudioState('ended')
|
||||
break
|
||||
case 'paused':
|
||||
setAudioState('ended')
|
||||
break
|
||||
case 'loaded':
|
||||
setAudioState('loading')
|
||||
break
|
||||
case 'play':
|
||||
setAudioState('playing')
|
||||
break
|
||||
case 'error':
|
||||
setAudioState('ended')
|
||||
break
|
||||
}
|
||||
}
|
||||
let url = ''
|
||||
let isPublic = false
|
||||
|
||||
if (params.token) {
|
||||
url = '/text-to-audio'
|
||||
isPublic = true
|
||||
}
|
||||
else if (params.appId) {
|
||||
if (pathname.search('explore/installed') > -1)
|
||||
url = `/installed-apps/${params.appId}/text-to-audio`
|
||||
else
|
||||
url = `/apps/${params.appId}/text-to-audio`
|
||||
}
|
||||
const handleToggle = async () => {
|
||||
if (audioState === 'playing' || audioState === 'loading') {
|
||||
setTimeout(() => setAudioState('paused'), 1)
|
||||
AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).pauseAudio()
|
||||
}
|
||||
else {
|
||||
setTimeout(() => setAudioState('loading'), 1)
|
||||
AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).playAudio()
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipContent = {
|
||||
initial: t('appApi.play'),
|
||||
ended: t('appApi.play'),
|
||||
paused: t('appApi.pause'),
|
||||
playing: t('appApi.playing'),
|
||||
loading: t('appApi.loading'),
|
||||
}[audioState]
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={tooltipContent}
|
||||
>
|
||||
<ActionButton
|
||||
state={
|
||||
audioState === 'loading' || audioState === 'playing'
|
||||
? ActionButtonState.Active
|
||||
: ActionButtonState.Default
|
||||
}
|
||||
onClick={handleToggle}
|
||||
disabled={audioState === 'loading'}
|
||||
>
|
||||
<RiVolumeUpLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default AudioBtn
|
||||
Reference in New Issue
Block a user