396 lines
13 KiB
TypeScript
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>
|
|
)
|
|
}
|