chatdesk-ui/chatdesk-ui/app/[locale]/setup/page.tsx

396 lines
13 KiB
TypeScript

'use client'
import { ChatbotUIContext } from "@/context/context"
import { getProfileByUserId, updateProfile } from "@/db/profile"
import {
getHomeWorkspaceByUserId,
getWorkspacesByUserId
} from "@/db/workspaces"
import {
fetchHostedModels,
fetchOpenRouterModels
} from "@/lib/models/fetch-models"
import { supabase } from "@/lib/supabase/browser-client"
import { TablesUpdate } from "@/supabase/types"
import { useRouter } from "next/navigation"
import { useContext, useEffect, useState } from "react"
import { APIStep } from "../../../components/setup/api-step"
import { FinishStep } from "../../../components/setup/finish-step"
import { ProfileStep } from "../../../components/setup/profile-step"
import {
SETUP_STEP_COUNT,
StepContainer
} from "../../../components/setup/step-container"
import { useTranslation } from 'react-i18next'
import { usePathname } from "next/navigation"
import i18nConfig from "@/i18nConfig"
import { createModel } from "@/db/models"
import { useTranslation } from "react-i18next"
export default function SetupPage() {
const {
profile,
setProfile,
setWorkspaces,
setSelectedWorkspace,
setEnvKeyMap,
setAvailableHostedModels,
setAvailableOpenRouterModels
} = useContext(ChatbotUIContext)
const router = useRouter()
const { t } = useTranslation()
const pathname = usePathname() // 获取当前路径
const pathSegments = pathname.split("/").filter(Boolean)
const locales = i18nConfig.locales
const defaultLocale = i18nConfig.defaultLocale
let locale: (typeof locales)[number] = defaultLocale
const segment = pathSegments[0] as (typeof locales)[number]
if (locales.includes(segment)) {
locale = segment
}
const getLocalizedPath = (subPath: string) =>
locale === defaultLocale ? `/${subPath}` : `/${locale}/${subPath}`
const { t } = useTranslation()
const [loading, setLoading] = useState(true)
const [currentStep, setCurrentStep] = useState(1)
// Profile Step
const [displayName, setDisplayName] = useState("")
const [username, setUsername] = useState(profile?.username || "")
const [usernameAvailable, setUsernameAvailable] = useState(true)
// API Step
const [useAzureOpenai, setUseAzureOpenai] = useState(false)
const [openaiAPIKey, setOpenaiAPIKey] = useState("")
const [openaiOrgID, setOpenaiOrgID] = useState("")
const [azureOpenaiAPIKey, setAzureOpenaiAPIKey] = useState("")
const [azureOpenaiEndpoint, setAzureOpenaiEndpoint] = useState("")
const [azureOpenai35TurboID, setAzureOpenai35TurboID] = useState("")
const [azureOpenai45TurboID, setAzureOpenai45TurboID] = useState("")
const [azureOpenai45VisionID, setAzureOpenai45VisionID] = useState("")
const [azureOpenaiEmbeddingsID, setAzureOpenaiEmbeddingsID] = useState("")
const [anthropicAPIKey, setAnthropicAPIKey] = useState("")
const [googleGeminiAPIKey, setGoogleGeminiAPIKey] = useState("")
const [mistralAPIKey, setMistralAPIKey] = useState("")
const [groqAPIKey, setGroqAPIKey] = useState("")
const [perplexityAPIKey, setPerplexityAPIKey] = useState("")
const [openrouterAPIKey, setOpenrouterAPIKey] = useState("")
// 只允许点击一次
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
;(async () => {
//const supabaseClient = await supabase(); // Await the client creation
const session = (await supabase.auth.getSession()).data.session
if (!session) {
// 强制跳转到带有 locale 的 login 页面
//console.log("...........[setup/page.tsx]")
return router.push(getLocalizedPath("login"))
} else {
const user = session.user
const profile = await getProfileByUserId(user.id)
setProfile(profile)
setUsername(profile.username)
if (!profile.has_onboarded) {
setLoading(false)
} else {
const data = await fetchHostedModels(profile)
if (!data) return
setEnvKeyMap(data.envKeyMap)
setAvailableHostedModels(data.hostedModels)
if (profile["openrouter_api_key"] || data.envKeyMap["openrouter"]) {
const openRouterModels = await fetchOpenRouterModels()
if (!openRouterModels) return
setAvailableOpenRouterModels(openRouterModels)
}
const homeWorkspaceId = await getHomeWorkspaceByUserId(
session.user.id
)
return router.push(getLocalizedPath(`${homeWorkspaceId}/chat`))
}
}
})()
}, [])
const handleShouldProceed = (proceed: boolean) => {
if (proceed) {
if (currentStep === SETUP_STEP_COUNT) {
//handleSaveSetupSetting()
if (!isSubmitting) {
handleSaveSetupSetting()
}
} else {
setCurrentStep(currentStep + 1)
}
} else {
setCurrentStep(currentStep - 1)
}
}
// const handleSaveSetupSetting = async () => {
// const session = (await supabase.auth.getSession()).data.session
// if (!session) {
// return router.push(getLocalizedPath("login"))
// }
// const user = session.user
// const profile = await getProfileByUserId(user.id)
// const updateProfilePayload: TablesUpdate<"profiles"> = {
// ...profile,
// has_onboarded: true,
// display_name: displayName,
// username,
// openai_api_key: openaiAPIKey,
// openai_organization_id: openaiOrgID,
// anthropic_api_key: anthropicAPIKey,
// google_gemini_api_key: googleGeminiAPIKey,
// mistral_api_key: mistralAPIKey,
// groq_api_key: groqAPIKey,
// perplexity_api_key: perplexityAPIKey,
// openrouter_api_key: openrouterAPIKey,
// use_azure_openai: useAzureOpenai,
// azure_openai_api_key: azureOpenaiAPIKey,
// azure_openai_endpoint: azureOpenaiEndpoint,
// azure_openai_35_turbo_id: azureOpenai35TurboID,
// azure_openai_45_turbo_id: azureOpenai45TurboID,
// azure_openai_45_vision_id: azureOpenai45VisionID,
// azure_openai_embeddings_id: azureOpenaiEmbeddingsID
// }
// const updatedProfile = await updateProfile(profile.id, updateProfilePayload)
// setProfile(updatedProfile)
// const workspaces = await getWorkspacesByUserId(profile.user_id)
// const homeWorkspace = workspaces.find(w => w.is_home)
// if (!homeWorkspace) {
// throw new Error("Home workspace not found for user during setup. This should not happen.")
// }
// const baseUrl = typeof window !== "undefined"
// ? `http://${window.location.hostname}:30000/v1`
// : "http://localhost:30000/v1"
// if (typeof window !== "undefined") {
// await createModel(
// {
// user_id: profile.user_id,
// name: "Default",
// model_id: "GPT",
// base_url: baseUrl,
// api_key: "token-abc123",
// description: "Default LLM model created during onboarding",
// context_length: 131072
// },
// homeWorkspace.id
// )
// }
// // There will always be a home workspace
// setSelectedWorkspace(homeWorkspace!)
// setWorkspaces(workspaces)
// return router.push(getLocalizedPath(`${homeWorkspace?.id}/chat`))
// }
const handleSaveSetupSetting = async () => {
if (isSubmitting) return
setIsSubmitting(true)
try {
const session = (await supabase.auth.getSession()).data.session
if (!session) {
return router.push(getLocalizedPath("login"))
}
const user = session.user
const profile = await getProfileByUserId(user.id)
const updateProfilePayload: TablesUpdate<"profiles"> = {
...profile,
has_onboarded: true,
display_name: displayName,
username,
openai_api_key: openaiAPIKey,
openai_organization_id: openaiOrgID,
anthropic_api_key: anthropicAPIKey,
google_gemini_api_key: googleGeminiAPIKey,
mistral_api_key: mistralAPIKey,
groq_api_key: groqAPIKey,
perplexity_api_key: perplexityAPIKey,
openrouter_api_key: openrouterAPIKey,
use_azure_openai: useAzureOpenai,
azure_openai_api_key: azureOpenaiAPIKey,
azure_openai_endpoint: azureOpenaiEndpoint,
azure_openai_35_turbo_id: azureOpenai35TurboID,
azure_openai_45_turbo_id: azureOpenai45TurboID,
azure_openai_45_vision_id: azureOpenai45VisionID,
azure_openai_embeddings_id: azureOpenaiEmbeddingsID
}
const updatedProfile = await updateProfile(profile.id, updateProfilePayload)
setProfile(updatedProfile)
const workspaces = await getWorkspacesByUserId(profile.user_id)
const homeWorkspace = workspaces.find(w => w.is_home)
if (!homeWorkspace) {
throw new Error("Home workspace not found.")
}
const baseUrl = typeof window !== "undefined"
? `http://${window.location.hostname}:30000/v1`
: "http://localhost:30000/v1"
if (typeof window !== "undefined") {
await createModel(
{
user_id: profile.user_id,
name: t("setup.Default"),
model_id: "GPT",
base_url: baseUrl,
api_key: "token-abc123",
description: t("setup.DefaultLLMModelCreatedDuringOnboarding"),
context_length: 131072
},
homeWorkspace.id
)
}
setSelectedWorkspace(homeWorkspace)
setWorkspaces(workspaces)
router.push(getLocalizedPath(`${homeWorkspace.id}/chat`))
} catch (err) {
console.error("Setup failed:", err)
setIsSubmitting(false) // 失败后允许重试
}
}
const renderStep = (stepNum: number) => {
switch (stepNum) {
// Profile Step
case 1:
return (
<StepContainer
stepDescription={t("setup.LetsCreateYourProfile")}
stepNum={currentStep}
stepTitle={t("setup.WelcomeToChatbotUI")}
onShouldProceed={handleShouldProceed}
showNextButton={!!(username && usernameAvailable)}
showBackButton={false}
>
<ProfileStep
username={username}
usernameAvailable={usernameAvailable}
displayName={displayName}
onUsernameAvailableChange={setUsernameAvailable}
onUsernameChange={setUsername}
onDisplayNameChange={setDisplayName}
/>
</StepContainer>
)
// API Step
case 2:
return (
<StepContainer
stepDescription={t("setup.EnterAPIKeysForEachServiceYoudLikeToUse")}
stepNum={currentStep}
stepTitle={t("setup.SetAPIKeysOptional")}
onShouldProceed={handleShouldProceed}
showNextButton={true}
showBackButton={true}
>
<APIStep
openaiAPIKey={openaiAPIKey}
openaiOrgID={openaiOrgID}
azureOpenaiAPIKey={azureOpenaiAPIKey}
azureOpenaiEndpoint={azureOpenaiEndpoint}
azureOpenai35TurboID={azureOpenai35TurboID}
azureOpenai45TurboID={azureOpenai45TurboID}
azureOpenai45VisionID={azureOpenai45VisionID}
azureOpenaiEmbeddingsID={azureOpenaiEmbeddingsID}
anthropicAPIKey={anthropicAPIKey}
googleGeminiAPIKey={googleGeminiAPIKey}
mistralAPIKey={mistralAPIKey}
groqAPIKey={groqAPIKey}
perplexityAPIKey={perplexityAPIKey}
useAzureOpenai={useAzureOpenai}
onOpenaiAPIKeyChange={setOpenaiAPIKey}
onOpenaiOrgIDChange={setOpenaiOrgID}
onAzureOpenaiAPIKeyChange={setAzureOpenaiAPIKey}
onAzureOpenaiEndpointChange={setAzureOpenaiEndpoint}
onAzureOpenai35TurboIDChange={setAzureOpenai35TurboID}
onAzureOpenai45TurboIDChange={setAzureOpenai45TurboID}
onAzureOpenai45VisionIDChange={setAzureOpenai45VisionID}
onAzureOpenaiEmbeddingsIDChange={setAzureOpenaiEmbeddingsID}
onAnthropicAPIKeyChange={setAnthropicAPIKey}
onGoogleGeminiAPIKeyChange={setGoogleGeminiAPIKey}
onMistralAPIKeyChange={setMistralAPIKey}
onGroqAPIKeyChange={setGroqAPIKey}
onPerplexityAPIKeyChange={setPerplexityAPIKey}
onUseAzureOpenaiChange={setUseAzureOpenai}
openrouterAPIKey={openrouterAPIKey}
onOpenrouterAPIKeyChange={setOpenrouterAPIKey}
/>
</StepContainer>
)
// Finish Step
case 3:
return (
<StepContainer
stepDescription={t("setup.YouAreAllSetUp")}
stepNum={currentStep}
stepTitle={t("setup.SetupComplete")}
onShouldProceed={handleShouldProceed}
//showNextButton={true}
showNextButton={true} // 一直显示按钮
isSubmitting={isSubmitting} // 控制是否禁用
showBackButton={true}
>
<FinishStep displayName={displayName} />
</StepContainer>
)
default:
return null
}
}
if (loading) {
return null
}
return (
<div className="flex h-full items-center justify-center">
{renderStep(currentStep)}
</div>
)
}