mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
3b3f95c44c
* feat(web): clarify model availability and status display - Rename model availability field from configured to available across backend API and frontend usage - Keep status as reason classification (configured/unconfigured/unreachable) and show unreachable in UI - Preserve API key preview even when local service is unreachable - Update backend tests to assert both availability and status semantics * fix(web): clarify unreachable model status and wording - Show unreachable status in model cards instead of API key preview when service is down - Keep API key placeholder preview in model settings whenever an API key is already saved - Rename model status wording from configured to available across backend, frontend, and i18n - Update backend model status tests to match renamed status semantics * style(web): standardize formatting in handleListModels function * refactor(web): enforce status field as required to follow backend behavior
100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
|
|
import { type ModelInfo, getModels, setDefaultModel } from "@/api/models"
|
|
|
|
interface UseChatModelsOptions {
|
|
isConnected: boolean
|
|
}
|
|
|
|
function isLocalModel(model: ModelInfo): boolean {
|
|
const isLocalHostBase = Boolean(
|
|
model.api_base?.includes("localhost") ||
|
|
model.api_base?.includes("127.0.0.1"),
|
|
)
|
|
|
|
return (
|
|
model.auth_method === "local" || (!model.auth_method && isLocalHostBase)
|
|
)
|
|
}
|
|
|
|
export function useChatModels({ isConnected }: UseChatModelsOptions) {
|
|
const [modelList, setModelList] = useState<ModelInfo[]>([])
|
|
const [defaultModelName, setDefaultModelName] = useState("")
|
|
const setDefaultRequestIdRef = useRef(0)
|
|
|
|
const loadModels = useCallback(async () => {
|
|
try {
|
|
const data = await getModels()
|
|
setModelList(data.models)
|
|
if (data.models.some((m) => m.model_name === data.default_model)) {
|
|
setDefaultModelName(data.default_model)
|
|
}
|
|
} catch {
|
|
// silently fail
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const timerId = setTimeout(() => {
|
|
void loadModels()
|
|
}, 0)
|
|
|
|
return () => clearTimeout(timerId)
|
|
}, [isConnected, loadModels])
|
|
|
|
const handleSetDefault = useCallback(
|
|
async (modelName: string) => {
|
|
if (modelName === defaultModelName) return
|
|
const requestId = ++setDefaultRequestIdRef.current
|
|
|
|
try {
|
|
await setDefaultModel(modelName)
|
|
const data = await getModels()
|
|
if (requestId !== setDefaultRequestIdRef.current) {
|
|
return
|
|
}
|
|
|
|
setModelList(data.models)
|
|
if (data.models.some((m) => m.model_name === data.default_model)) {
|
|
setDefaultModelName(data.default_model)
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to set default model:", err)
|
|
}
|
|
},
|
|
[defaultModelName],
|
|
)
|
|
|
|
const hasAvailableModels = useMemo(
|
|
() => modelList.some((m) => m.available),
|
|
[modelList],
|
|
)
|
|
|
|
const oauthModels = useMemo(
|
|
() => modelList.filter((m) => m.available && m.auth_method === "oauth"),
|
|
[modelList],
|
|
)
|
|
|
|
const localModels = useMemo(
|
|
() => modelList.filter((m) => m.available && isLocalModel(m)),
|
|
[modelList],
|
|
)
|
|
|
|
const apiKeyModels = useMemo(
|
|
() =>
|
|
modelList.filter(
|
|
(m) => m.available && m.auth_method !== "oauth" && !isLocalModel(m),
|
|
),
|
|
[modelList],
|
|
)
|
|
|
|
return {
|
|
defaultModelName,
|
|
hasAvailableModels,
|
|
apiKeyModels,
|
|
oauthModels,
|
|
localModels,
|
|
handleSetDefault,
|
|
}
|
|
}
|