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:
@@ -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
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user