refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.
This commit is contained in:
QuentinHsu
2026-05-29 10:04:50 +08:00
parent 5489c68eec
commit a297c00cc3
3 changed files with 128 additions and 73 deletions
+1
View File
@@ -21,3 +21,4 @@ export * from './use-stream-request'
export * from './use-chat-handler'
export * from './use-message-action-guard'
export * from './use-playground-conversation'
export * from './use-playground-options'
@@ -0,0 +1,120 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { getUserGroups, getUserModels } from '../api'
import type { GroupOption, ModelOption, PlaygroundConfig } from '../types'
type UsePlaygroundOptionsParams = {
currentGroup: string
currentModel: string
setGroups: (groups: GroupOption[]) => void
setModels: (models: ModelOption[]) => void
updateConfig: <K extends keyof PlaygroundConfig>(
key: K,
value: PlaygroundConfig[K]
) => void
}
export function usePlaygroundOptions({
currentGroup,
currentModel,
setGroups,
setModels,
updateConfig,
}: UsePlaygroundOptionsParams) {
const { t } = useTranslation()
const {
data: modelsData,
error: modelsError,
isError: isModelsError,
isLoading: isLoadingModels,
} = useQuery({
queryKey: ['playground-models'],
queryFn: getUserModels,
})
const {
data: groupsData,
error: groupsError,
isError: isGroupsError,
} = useQuery({
queryKey: ['playground-groups'],
queryFn: getUserGroups,
})
useEffect(() => {
if (!isModelsError) return
toast.error(
modelsError instanceof Error
? modelsError.message
: t('Failed to load playground models')
)
}, [isModelsError, modelsError, t])
useEffect(() => {
if (!isGroupsError) return
toast.error(
groupsError instanceof Error
? groupsError.message
: t('Failed to load playground groups')
)
}, [isGroupsError, groupsError, t])
useEffect(() => {
if (!modelsData) return
setModels(modelsData)
const hasCurrentModel = modelsData.some(
(model) => model.value === currentModel
)
if (modelsData.length > 0 && !hasCurrentModel) {
updateConfig('model', modelsData[0].value)
}
}, [modelsData, currentModel, setModels, updateConfig])
useEffect(() => {
if (!groupsData) return
setGroups(groupsData)
const hasCurrentGroup = groupsData.some(
(group) => group.value === currentGroup
)
if (!hasCurrentGroup && groupsData.length > 0) {
const fallback =
groupsData.find((group) => group.value === 'default')?.value ??
groupsData[0].value
updateConfig('group', fallback)
}
}, [groupsData, currentGroup, setGroups, updateConfig])
return {
isLoadingModels,
}
}
+7 -73
View File
@@ -16,21 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { getUserModels, getUserGroups } from './api'
import { PlaygroundChat } from './components/playground-chat'
import { PlaygroundInput } from './components/playground-input'
import {
useChatHandler,
usePlaygroundConversation,
usePlaygroundOptions,
usePlaygroundState,
} from './hooks'
export function Playground() {
const { t } = useTranslation()
const {
config,
parameterEnabled,
@@ -63,75 +58,14 @@ export function Playground() {
sendChat,
})
// Load models
const {
data: modelsData,
error: modelsError,
isError: isModelsError,
isLoading: isLoadingModels,
} = useQuery({
queryKey: ['playground-models'],
queryFn: getUserModels,
const { isLoadingModels } = usePlaygroundOptions({
currentGroup: config.group,
currentModel: config.model,
setGroups,
setModels,
updateConfig,
})
// Load groups
const {
data: groupsData,
error: groupsError,
isError: isGroupsError,
} = useQuery({
queryKey: ['playground-groups'],
queryFn: getUserGroups,
})
useEffect(() => {
if (!isModelsError) return
toast.error(
modelsError instanceof Error
? modelsError.message
: t('Failed to load playground models')
)
}, [isModelsError, modelsError, t])
useEffect(() => {
if (!isGroupsError) return
toast.error(
groupsError instanceof Error
? groupsError.message
: t('Failed to load playground groups')
)
}, [isGroupsError, groupsError, t])
// Update models when data changes
useEffect(() => {
if (!modelsData) return
setModels(modelsData)
// Set default model if current model is not available
const isCurrentModelValid = modelsData.some((m) => m.value === config.model)
if (modelsData.length > 0 && !isCurrentModelValid) {
updateConfig('model', modelsData[0].value)
}
}, [modelsData, config.model, setModels, updateConfig])
// Update groups when data changes
useEffect(() => {
if (!groupsData) return
setGroups(groupsData)
const hasCurrentGroup = groupsData.some((g) => g.value === config.group)
if (!hasCurrentGroup && groupsData.length > 0) {
const fallback =
groupsData.find((g) => g.value === 'default')?.value ??
groupsData[0].value
updateConfig('group', fallback)
}
}, [groupsData, setGroups, config.group, updateConfig])
return (
<div className='relative flex size-full flex-col overflow-hidden'>
{/* Full-width scroll container: scrolling works even over side whitespace */}