Compare commits

...

105 Commits
v1.0.0 ... main

Author SHA1 Message Date
hailin 2c1daebf74 . 2025-06-25 13:54:21 +08:00
hailin cd2c561547 . 2025-06-16 14:28:46 +08:00
hailin 8d8b704a73 . 2025-05-29 23:54:27 +08:00
hailin 501b249a7c . 2025-05-29 23:51:45 +08:00
hailin a522fe3498 . 2025-05-29 23:47:33 +08:00
hailin 798faf00d4 . 2025-05-29 23:22:55 +08:00
hailin e86859fc6e . 2025-05-29 23:19:50 +08:00
hailin 74fc21608d . 2025-05-29 22:57:00 +08:00
hailin 7918c2b7b8 . 2025-05-29 22:45:45 +08:00
hailin 191af7e22d . 2025-05-29 22:32:26 +08:00
hailin 5f65a8dda3 . 2025-05-29 22:27:46 +08:00
hailin 9063270608 . 2025-05-29 22:26:28 +08:00
hailin 907527ecf7 . 2025-05-29 22:21:20 +08:00
hailin bd58362612 . 2025-05-29 21:36:44 +08:00
hailin 2c96c983b5 . 2025-05-29 21:22:07 +08:00
hailin 6ee23bebb5 . 2025-05-29 21:08:42 +08:00
hailin f17d6aca22 . 2025-05-29 21:01:32 +08:00
hailin a6e77c12fe . 2025-05-29 20:50:07 +08:00
hailin e6164160ef . 2025-05-29 20:40:22 +08:00
hailin b7941a45b9 . 2025-05-29 20:34:59 +08:00
hailin fa170db990 . 2025-05-29 19:55:49 +08:00
hailin e1d22386a1 . 2025-05-29 18:38:36 +08:00
hailin 8c9b6ec07e . 2025-05-29 18:31:15 +08:00
hailin 0f5d9b9b68 . 2025-05-29 18:22:30 +08:00
hailin 43b4279e2d . 2025-05-29 18:06:04 +08:00
hailin 6ee913f067 . 2025-05-29 17:46:07 +08:00
hailin b0bdbf633e . 2025-05-29 17:43:10 +08:00
hailin 3f2f319e3b . 2025-05-29 17:38:46 +08:00
hailin 7237a55c37 . 2025-05-29 17:37:51 +08:00
hailin 9eaf30c617 . 2025-05-29 17:14:24 +08:00
hailin 665ad78b20 . 2025-05-29 17:12:58 +08:00
hailin 36c57c6a41 . 2025-05-29 16:56:59 +08:00
hailin 1730caf144 . 2025-05-29 13:55:24 +08:00
hailin 5ead504e66 . 2025-05-29 13:42:54 +08:00
hailin 673308a513 . 2025-05-29 02:04:47 +08:00
hailin 03daddd2fa . 2025-05-29 01:57:44 +08:00
hailin 0280845a3a . 2025-05-29 01:14:19 +08:00
hailin 9d7b8c7c1d . 2025-05-29 01:12:07 +08:00
hailin 4e169bf3d6 . 2025-05-29 01:08:19 +08:00
hailin e04a84e08a . 2025-05-29 00:58:34 +08:00
hailin ec484afcaa . 2025-05-29 00:14:27 +08:00
hailin 035a93a439 . 2025-05-28 23:48:20 +08:00
hailin 936d7c298d . 2025-05-28 23:36:11 +08:00
hailin f0e6baa352 . 2025-05-28 22:55:20 +08:00
hailin b7498e3bb2 . 2025-05-28 22:39:32 +08:00
hailin 467884c0df . 2025-05-28 22:22:18 +08:00
hailin 285ac31ceb . 2025-05-28 22:04:02 +08:00
hailin 5e475801a4 . 2025-05-28 21:53:24 +08:00
hailin 2d757057c1 . 2025-05-28 21:42:09 +08:00
hailin b9a40d26ce . 2025-05-28 21:31:12 +08:00
hailin b1ce04b5d6 . 2025-05-28 21:18:17 +08:00
hailin 10fa1637ea . 2025-05-28 20:44:36 +08:00
hailin de91c632e0 . 2025-05-28 20:39:15 +08:00
hailin 6794ebf5c1 . 2025-05-28 20:34:22 +08:00
hailin be0662d918 . 2025-05-28 11:12:27 +08:00
hailin 41dc3ca5fa . 2025-05-28 10:51:54 +08:00
hailin 4e9122a657 . 2025-05-28 10:49:37 +08:00
hailin 765f61d444 . 2025-05-28 10:47:10 +08:00
hailin b3c0b0d38f . 2025-05-28 10:44:32 +08:00
hailin b7e53489d2 . 2025-05-28 10:42:01 +08:00
hailin adceadc16c . 2025-05-27 23:00:01 +08:00
hailin 56b919390d . 2025-05-27 22:25:23 +08:00
hailin e02b4954ee . 2025-05-27 19:24:01 +08:00
hailin 7a841d8171 . 2025-05-27 19:22:30 +08:00
hailin 66a9865c52 . 2025-05-27 19:07:11 +08:00
hailin c174f7a262 . 2025-05-27 18:57:39 +08:00
hailin b82018e971 . 2025-05-27 18:26:55 +08:00
hailin 57382b2ab7 . 2025-05-27 18:25:31 +08:00
hailin 5b3dabacd0 . 2025-05-27 18:23:47 +08:00
hailin b81cd6e3d0 . 2025-05-27 18:14:27 +08:00
hailin e8f542ef63 . 2025-05-27 17:57:04 +08:00
hailin 6f09892b13 . 2025-05-27 17:52:30 +08:00
hailin c54328b2c8 . 2025-05-27 17:51:32 +08:00
hailin 40a458989b . 2025-05-27 17:42:39 +08:00
hailin 72abcbeb26 . 2025-05-27 17:29:13 +08:00
hailin 9b8d9c0db4 . 2025-05-27 17:08:01 +08:00
hailin df7af82d85 . 2025-05-27 16:25:31 +08:00
hailin ed7afaafb6 . 2025-05-27 16:20:50 +08:00
hailin c62109c022 . 2025-05-27 14:42:52 +08:00
hailin ad5fb2fa7b . 2025-05-27 14:26:58 +08:00
hailin a1a251f2fe . 2025-05-27 14:22:28 +08:00
hailin fc910862d0 . 2025-05-27 14:15:29 +08:00
hailin fbaa2800c6 . 2025-05-27 13:58:46 +08:00
hailin ac99f5670c . 2025-05-27 13:55:06 +08:00
hailin 6a47033ab2 . 2025-05-27 12:28:06 +08:00
hailin 7fcd5a3f41 . 2025-05-26 20:37:29 +08:00
hailin 488a3dfe05 . 2025-05-26 19:45:44 +08:00
hailin 343350e302 . 2025-05-26 19:34:59 +08:00
hailin 78aa17b367 . 2025-05-26 19:24:39 +08:00
hailin c4b59f3292 . 2025-05-26 19:06:47 +08:00
hailin 8b77ede8d5 . 2025-05-26 18:08:58 +08:00
hailin f95bf09652 . 2025-05-26 17:42:11 +08:00
hailin bea9a580c5 . 2025-05-26 17:30:29 +08:00
hailin ccac741f70 . 2025-05-26 17:13:17 +08:00
hailin 30e1cde3b2 . 2025-05-26 16:50:39 +08:00
hailin f055fe609c . 2025-05-26 16:16:07 +08:00
hailin 1600c29f44 . 2025-05-26 16:15:41 +08:00
hailin 7cb1865183 . 2025-05-26 13:48:32 +08:00
hailin 030990bb08 . 2025-05-26 13:39:24 +08:00
hailin 7973bc9d40 . 2025-05-26 13:25:48 +08:00
hailin 8c597005d6 . 2025-05-26 12:35:13 +08:00
hailin d64d64ad4f . 2025-05-26 12:28:23 +08:00
hailin b8bd679fe4 . 2025-05-26 11:31:11 +08:00
hailin 71f47a7539 . 2025-05-23 10:57:07 +08:00
hailin edfb1fec43 . 2025-05-23 10:52:57 +08:00
60 changed files with 1486 additions and 424 deletions

View File

@ -355,7 +355,7 @@ RUN chmod +x /supabase/postgres/wrapper.sh /supabase/postgrest/wrapper.sh /supab
ENTRYPOINT ["supervisord"] ENTRYPOINT ["supervisord"]
#HEALTHCHECK --interval=2s --timeout=2s --retries=10 CMD pg_isready -U postgres -h localhost #HEALTHCHECK --interval=2s --timeout=2s --retries=10 CMD pg_isready -U postgres -h localhost
HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=10 CMD pg_isready -U postgres -h 127.0.0.1 -p 5432 || exit 1 HEALTHCHECK --interval=10s --timeout=6s --start-period=60s --retries=5 CMD pg_isready -U postgres -h 127.0.0.1 -p 5432 || exit 1
STOPSIGNAL SIGINT STOPSIGNAL SIGINT
#EXPOSE 5432 #EXPOSE 5432

View File

@ -155,7 +155,7 @@ If the environment variable is set, it will disable the input in the user settin
In the 1st migration file `supabase/migrations/20240108234540_setup.sql` you will need to replace 2 values with the values you got above: In the 1st migration file `supabase/migrations/20240108234540_setup.sql` you will need to replace 2 values with the values you got above:
- `project_url` (line 53): `http://supabase_kong_chatbotui:8000` (default) can remain unchanged if you don't change your `project_id` in the `config.toml` file - `project_url` (line 53): `http://localhost:8000` (default) can remain unchanged if you don't change your `project_id` in the `config.toml` file
- `service_role_key` (line 54): You got this value from running `supabase status` - `service_role_key` (line 54): You got this value from running `supabase status`
This prevents issues with storage files not being deleted properly. This prevents issues with storage files not being deleted properly.

View File

@ -169,12 +169,19 @@ export default function WorkspaceLayout({ children }: WorkspaceLayoutProps) {
setTools(toolData.tools) setTools(toolData.tools)
const modelData = await getModelWorkspacesByWorkspaceId(workspaceId) const modelData = await getModelWorkspacesByWorkspaceId(workspaceId)
//console.log("......Model data from DB:", modelData.models)
setModels(modelData.models) setModels(modelData.models)
// setChatSettings({
// model: (searchParams.get("model") ||
// workspace?.default_model ||
// "gpt-4-1106-preview") as LLMID,
const firstModel = modelData.models[0]?.model_id || "GPT"; // 默认兜底
setChatSettings({ setChatSettings({
model: (searchParams.get("model") || model: (searchParams.get("model") ||
workspace?.default_model || workspace?.default_model ||
"gpt-4-1106-preview") as LLMID, firstModel) as LLMID,
prompt: prompt:
workspace?.default_prompt || workspace?.default_prompt ||
t("chat.promptPlaceholder"), t("chat.promptPlaceholder"),
@ -184,7 +191,7 @@ export default function WorkspaceLayout({ children }: WorkspaceLayoutProps) {
includeWorkspaceInstructions: includeWorkspaceInstructions:
workspace?.include_workspace_instructions || true, workspace?.include_workspace_instructions || true,
embeddingsProvider: embeddingsProvider:
(workspace?.embeddings_provider as "openai" | "local") || "openai" (workspace?.embeddings_provider as "openai" | "local" | "bge-m3") || "bge-m3"
}) })
setLoading(false) setLoading(false)

View File

@ -4,7 +4,6 @@ import { Providers } from "@/components/utility/providers"
import TranslationsProvider from "@/components/utility/translations-provider" import TranslationsProvider from "@/components/utility/translations-provider"
import initTranslations from "@/lib/i18n" import initTranslations from "@/lib/i18n"
import { Database } from "@/supabase/types" import { Database } from "@/supabase/types"
//import { createServerClient } from "@supabase/ssr"
import { getSupabaseServerClient } from "@/lib/supabase/server" import { getSupabaseServerClient } from "@/lib/supabase/server"
import { Metadata, Viewport } from "next" import { Metadata, Viewport } from "next"
import { Inter } from "next/font/google" import { Inter } from "next/font/google"
@ -114,39 +113,22 @@ export default async function RootLayout({
console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`); console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`);
} }
// const supabaseUrl = getRuntimeEnv("SUPABASE_URL");
// console.log("==========>Supabase URL: ", supabaseUrl);
let supabaseUrl = ""; let supabaseUrl = "";
try { try {
// 等待直到获取到有效的 Supabase URL // 等待直到获取到有效的 Supabase URL
supabaseUrl = await getValidSupabaseUrl(); supabaseUrl = await getValidSupabaseUrl();
console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl); //console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
} catch (error) { } catch (error) {
console.error("Supabase URL 获取失败:", error); console.error("Supabase URL 获取失败:", error);
return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示 return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示
} }
// const supabase = createServerClient<Database>(
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
// process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
// {
// cookies: {
// get(name: string) {
// return cookieStore.get(name)?.value
// }
// }
// }
// )
const supabase = getSupabaseServerClient() const supabase = getSupabaseServerClient()
// const session = (await supabase.auth.getSession()).data.session
const { data, error } = await supabase.auth.getSession(); const { data, error } = await supabase.auth.getSession();
if (error) { if (error) {
console.log("[layout.tsx]............Session Error: ", error); console.log("[layout.tsx]............Session Error: ", error);
} else { } else {
console.log("[layout.tsx]............Session Data: ", data.session); //console.log("[layout.tsx]............Session Data: ", data.session);
} }
const { t, resources } = await initTranslations(locale, i18nNamespaces) const { t, resources } = await initTranslations(locale, i18nNamespaces)
@ -179,20 +161,4 @@ export default async function RootLayout({
</body> </body>
</html> </html>
) )
// return (
// <Providers attribute="class" defaultTheme="dark">
// <TranslationsProvider
// namespaces={i18nNamespaces}
// locale={locale}
// resources={resources}
// >
// <Toaster richColors position="top-center" duration={3000} />
// <div className={`${inter.className} bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto`}>
// {data.session ? <GlobalState>{children}</GlobalState> : children}
// </div>
// </TranslationsProvider>
// </Providers>
// )
} }

View File

@ -70,7 +70,11 @@ export default async function Login({
console.error("Host URL is not available"); console.error("Host URL is not available");
} }
return redirect(`/${locale}/system/clearcookies-then-redirect`) //return redirect(`/${locale}/system/clearcookies-then-redirect`)
const cookieStore = cookies()
const cookieKeys = cookieStore.getAll().map(c => c.name)
const cookieString = encodeURIComponent(cookieKeys.join(","))
return redirect(`/${locale}/system/clearcookies-then-redirect?keys=${cookieString}`)
} }
else{ else{

View File

@ -15,15 +15,13 @@ export default function HomePage() {
const { theme } = useTheme() const { theme } = useTheme()
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
const [preferredLanguage, setPreferredLanguage] = useState<string>('en') // 默认语言为 'en' const [preferredLanguage, setPreferredLanguage] = useState<string>('en')
// 根据 localStorage 或 cookie 设置 preferredLanguage // 根据 localStorage 或 cookie 设置 preferredLanguage
useEffect(() => { useEffect(() => {
const languageFromStorage = localStorage.getItem('preferred-language') || document.cookie.split('; ').find(row => row.startsWith('preferred-language='))?.split('=')[1]; const languageFromStorage = localStorage.getItem('preferred-language') || document.cookie.split('; ').find(row => row.startsWith('preferred-language='))?.split('=')[1];
if (languageFromStorage) { if (languageFromStorage) {
setPreferredLanguage(languageFromStorage); setPreferredLanguage(languageFromStorage);
// 更新 i18n 的语言设置
//i18n.changeLanguage(languageFromStorage); // 通过 i18n 更新默认语言
} }
}, []); }, []);

View File

@ -21,11 +21,11 @@ import {
SETUP_STEP_COUNT, SETUP_STEP_COUNT,
StepContainer StepContainer
} from "../../../components/setup/step-container" } from "../../../components/setup/step-container"
import { useTranslation } from 'react-i18next'
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import i18nConfig from "@/i18nConfig" import i18nConfig from "@/i18nConfig"
import { createModel } from "@/db/models"
import { useTranslation } from "react-i18next"
export default function SetupPage() { export default function SetupPage() {
const { const {
@ -39,6 +39,7 @@ export default function SetupPage() {
} = useContext(ChatbotUIContext) } = useContext(ChatbotUIContext)
const router = useRouter() const router = useRouter()
const { t } = useTranslation()
@ -58,8 +59,6 @@ export default function SetupPage() {
const getLocalizedPath = (subPath: string) => const getLocalizedPath = (subPath: string) =>
locale === defaultLocale ? `/${subPath}` : `/${locale}/${subPath}` locale === defaultLocale ? `/${subPath}` : `/${locale}/${subPath}`
const { t } = useTranslation()
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [currentStep, setCurrentStep] = useState(1) const [currentStep, setCurrentStep] = useState(1)
@ -86,6 +85,10 @@ export default function SetupPage() {
const [perplexityAPIKey, setPerplexityAPIKey] = useState("") const [perplexityAPIKey, setPerplexityAPIKey] = useState("")
const [openrouterAPIKey, setOpenrouterAPIKey] = useState("") const [openrouterAPIKey, setOpenrouterAPIKey] = useState("")
// 只允许点击一次
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
//const supabaseClient = await supabase(); // Await the client creation //const supabaseClient = await supabase(); // Await the client creation
@ -93,10 +96,8 @@ export default function SetupPage() {
if (!session) { if (!session) {
// 强制跳转到带有 locale 的 login 页面 // 强制跳转到带有 locale 的 login 页面
console.log("...........[setup/page.tsx]") //console.log("...........[setup/page.tsx]")
//return router.push(`${homePath}/login`)
return router.push(getLocalizedPath("login")) return router.push(getLocalizedPath("login"))
// return router.push(`/${locale}/login`)
} else { } else {
const user = session.user const user = session.user
@ -123,8 +124,6 @@ export default function SetupPage() {
const homeWorkspaceId = await getHomeWorkspaceByUserId( const homeWorkspaceId = await getHomeWorkspaceByUserId(
session.user.id session.user.id
) )
//return router.push(`${homePath}/${homeWorkspaceId}/chat`)
// return router.push(`/${locale}/${homeWorkspaceId}/chat`)
return router.push(getLocalizedPath(`${homeWorkspaceId}/chat`)) return router.push(getLocalizedPath(`${homeWorkspaceId}/chat`))
} }
} }
@ -134,7 +133,10 @@ export default function SetupPage() {
const handleShouldProceed = (proceed: boolean) => { const handleShouldProceed = (proceed: boolean) => {
if (proceed) { if (proceed) {
if (currentStep === SETUP_STEP_COUNT) { if (currentStep === SETUP_STEP_COUNT) {
handleSaveSetupSetting() //handleSaveSetupSetting()
if (!isSubmitting) {
handleSaveSetupSetting()
}
} else { } else {
setCurrentStep(currentStep + 1) setCurrentStep(currentStep + 1)
} }
@ -143,56 +145,149 @@ export default function SetupPage() {
} }
} }
// 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 () => { const handleSaveSetupSetting = async () => {
//const supabaseClient = await supabase(); // Await the client creation if (isSubmitting) return
const session = (await supabase.auth.getSession()).data.session setIsSubmitting(true)
if (!session) {
// return router.push(`/${locale}/login`) try {
//return (`${homePath}/login`) const session = (await supabase.auth.getSession()).data.session
return router.push(getLocalizedPath("login")) 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 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)
// There will always be a home workspace
setSelectedWorkspace(homeWorkspace!)
setWorkspaces(workspaces)
// return router.push(`${homePath}/${homeWorkspace?.id}/chat`)
// return router.push(`/${locale}/${homeWorkspace?.id}/chat`)
return router.push(getLocalizedPath(`${homeWorkspace?.id}/chat`))
} }
const renderStep = (stepNum: number) => { const renderStep = (stepNum: number) => {
switch (stepNum) { switch (stepNum) {
// Profile Step // Profile Step
@ -271,7 +366,9 @@ export default function SetupPage() {
stepNum={currentStep} stepNum={currentStep}
stepTitle={t("setup.SetupComplete")} stepTitle={t("setup.SetupComplete")}
onShouldProceed={handleShouldProceed} onShouldProceed={handleShouldProceed}
showNextButton={true} //showNextButton={true}
showNextButton={true} // 一直显示按钮
isSubmitting={isSubmitting} // 控制是否禁用
showBackButton={true} showBackButton={true}
> >
<FinishStep displayName={displayName} /> <FinishStep displayName={displayName} />

View File

@ -2,18 +2,27 @@
"use client" "use client"
import { useEffect } from "react" import { useEffect } from "react"
import { useRouter } from "next/navigation" import { useRouter, useSearchParams } from "next/navigation"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
export default function ClearCookiesThenRedirect({ params }: { params: { locale: string } }) { export default function ClearCookiesThenRedirect({ params }: { params: { locale: string } }) {
const router = useRouter() const router = useRouter()
const { t } = useTranslation() const { t } = useTranslation()
const searchParams = useSearchParams()
useEffect(() => { useEffect(() => {
// 🔥 手动清除浏览器端 cookie服务端清除不够 // 🔥 手动清除浏览器端 cookie服务端清除不够
document.cookie = "sb-67-auth-token=; path=/; max-age=0" // document.cookie = "sb-67-auth-token=; path=/; max-age=0"
document.cookie = "sb-67-auth-token-code-verifier=; path=/; max-age=0" // document.cookie = "sb-67-auth-token-code-verifier=; path=/; max-age=0"
document.cookie = "_ga=; path=/; max-age=0" // document.cookie = "_ga=; path=/; max-age=0"
const keysParam = searchParams.get("keys")
const keys = keysParam ? keysParam.split(",") : []
keys.forEach((key) => {
document.cookie = `${key}=; path=/; max-age=0`
})
// ✅ 仍然调用服务端 API 处理 HttpOnly cookie比如 Supabase 设置的) // ✅ 仍然调用服务端 API 处理 HttpOnly cookie比如 Supabase 设置的)
fetch("/api/clearcookies", { method: "POST" }).finally(() => { fetch("/api/clearcookies", { method: "POST" }).finally(() => {

View File

@ -7,13 +7,14 @@ import { createClient } from "@supabase/supabase-js"
import { NextResponse } from "next/server" import { NextResponse } from "next/server"
import OpenAI from "openai" import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入 import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding"
export async function POST(req: Request) { export async function POST(req: Request) {
const json = await req.json() const json = await req.json()
const { text, fileId, embeddingsProvider, fileExtension } = json as { const { text, fileId, embeddingsProvider, fileExtension } = json as {
text: string text: string
fileId: string fileId: string
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
fileExtension: string fileExtension: string
} }
@ -82,6 +83,40 @@ export async function POST(req: Request) {
}) })
embeddings = await Promise.all(embeddingPromises) embeddings = await Promise.all(embeddingPromises)
} else if (embeddingsProvider === "bge-m3"){
// 示例:调用你自己的 BGE-M3 API 或本地函数
const embeddingPromises = chunks.map(async (chunk, index) => {
try {
// return await generateBgeM3Embedding(chunk.content)
const result = await generateBgeM3Embedding(chunk.content)
if (!Array.isArray(result)) {
console.error(`......❌ Chunk ${index}: result is not an array`, result)
return null
}
if (result.length !== 1024) {
console.error(`......❌ Chunk ${index}: incorrect length: ${result.length}`)
return null
}
if (!result.every(x => typeof x === "number")) {
console.error(`......❌ Chunk ${index}: contains non-numbers`, result)
return null
}
return result
} catch (error) {
console.error(`Error generating BGE-M3 embedding for chunk: ${chunk}`, error)
return null
}
})
embeddings = await Promise.all(embeddingPromises)
console.log(`......[embedding] 维度: ${embeddings.length}`);
} }
const file_items = chunks.map((chunk, index) => ({ const file_items = chunks.map((chunk, index) => ({
@ -96,6 +131,11 @@ export async function POST(req: Request) {
local_embedding: local_embedding:
embeddingsProvider === "local" embeddingsProvider === "local"
? ((embeddings[index] || null) as any) ? ((embeddings[index] || null) as any)
: null,
bge_m3_embedding:
embeddingsProvider === "bge-m3" && embeddings[index] && embeddings[index].length === 1024
// ? (embeddings[index] || null) as any
? embeddings[index] as any
: null : null
})) }))

View File

@ -1,4 +1,5 @@
import { generateLocalEmbedding } from "@/lib/generate-local-embedding" import { generateLocalEmbedding } from "@/lib/generate-local-embedding"
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding"
import { import {
processCSV, processCSV,
processJSON, processJSON,
@ -15,6 +16,7 @@ import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入 import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
export async function POST(req: Request) { export async function POST(req: Request) {
try { try {
const supabaseAdmin = createClient<Database>( const supabaseAdmin = createClient<Database>(
getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000", getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
@ -28,6 +30,8 @@ export async function POST(req: Request) {
const file_id = formData.get("file_id") as string const file_id = formData.get("file_id") as string
const embeddingsProvider = formData.get("embeddingsProvider") as string const embeddingsProvider = formData.get("embeddingsProvider") as string
console.log("......[process] Starting file processing request,embeddingsProvider=", embeddingsProvider)
const { data: fileMetadata, error: metadataError } = await supabaseAdmin const { data: fileMetadata, error: metadataError } = await supabaseAdmin
.from("files") .from("files")
.select("*") .select("*")
@ -87,7 +91,9 @@ export async function POST(req: Request) {
chunks = await processMarkdown(blob) chunks = await processMarkdown(blob)
break break
case "pdf": case "pdf":
console.log("......[process] Processing PDF...")
chunks = await processPdf(blob) chunks = await processPdf(blob)
console.log("......[process] PDF Processed chunks=", chunks)
break break
case "txt": case "txt":
chunks = await processTxt(blob) chunks = await processTxt(blob)
@ -136,6 +142,42 @@ export async function POST(req: Request) {
}) })
embeddings = await Promise.all(embeddingPromises) embeddings = await Promise.all(embeddingPromises)
} else if (embeddingsProvider === "bge-m3"){
// 示例:调用你自己的 BGE-M3 API 或本地函数
// const embeddingPromises = chunks.map(async chunk => {
const embeddingPromises = chunks.map(async (chunk, index) => {
try {
// return await generateBgeM3Embedding(chunk.content)
const result = await generateBgeM3Embedding(chunk.content)
if (!Array.isArray(result)) {
console.error(`......❌ Chunk ${index}: result is not an array`, result)
return null
}
if (result.length !== 1024) {
console.error(`......❌ Chunk ${index}: incorrect length: ${result.length}`)
return null
}
if (!result.every(x => typeof x === "number")) {
console.error(`......❌ Chunk ${index}: contains non-numbers`, result)
return null
}
return result
} catch (error) {
console.error(`Error generating BGE-M3 embedding for chunk: ${chunk}`, error)
return null
}
})
embeddings = await Promise.all(embeddingPromises)
console.log(`......[embedding] 维度: ${embeddings.length}`);
} }
const file_items = chunks.map((chunk, index) => ({ const file_items = chunks.map((chunk, index) => ({
@ -150,6 +192,10 @@ export async function POST(req: Request) {
local_embedding: local_embedding:
embeddingsProvider === "local" embeddingsProvider === "local"
? ((embeddings[index] || null) as any) ? ((embeddings[index] || null) as any)
: null,
bge_m3_embedding:
embeddingsProvider === "bge-m3" && embeddings[index] && embeddings[index].length === 1024
? (embeddings[index] || null) as any
: null : null
})) }))
@ -162,6 +208,7 @@ export async function POST(req: Request) {
.update({ tokens: totalTokens }) .update({ tokens: totalTokens })
.eq("id", file_id) .eq("id", file_id)
console.log("......[embedding]Embed Successful.")
return new NextResponse("Embed Successful", { return new NextResponse("Embed Successful", {
status: 200 status: 200
}) })

View File

@ -1,4 +1,5 @@
import { generateLocalEmbedding } from "@/lib/generate-local-embedding" import { generateLocalEmbedding } from "@/lib/generate-local-embedding"
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding" // 新增
import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers"
import { Database } from "@/supabase/types" import { Database } from "@/supabase/types"
import { createClient } from "@supabase/supabase-js" import { createClient } from "@supabase/supabase-js"
@ -6,19 +7,28 @@ import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入 import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
export async function POST(request: Request) { export async function POST(request: Request) {
console.log("......[retrieve] request=", request)
const json = await request.json() const json = await request.json()
const { userInput, fileIds, embeddingsProvider, sourceCount } = json as { const { userInput, fileIds, embeddingsProvider, sourceCount } = json as {
userInput: string userInput: string
fileIds: string[] fileIds: string[]
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
sourceCount: number sourceCount: number
} }
const uniqueFileIds = [...new Set(fileIds)] const uniqueFileIds = [...new Set(fileIds)]
try { try {
const rawSupaUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
const supaUrlObj = new URL(rawSupaUrl)
supaUrlObj.port = "8000"
const supabaseAdmin = createClient<Database>( const supabaseAdmin = createClient<Database>(
getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000", supaUrlObj.origin,
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
process.env.SUPABASE_SERVICE_ROLE_KEY! process.env.SUPABASE_SERVICE_ROLE_KEY!
) )
@ -84,6 +94,37 @@ export async function POST(request: Request) {
} }
chunks = localFileItems chunks = localFileItems
} else if (embeddingsProvider === "bge-m3"){
// 示例:调用你自己的 BGE-M3 API 或本地函数
// 新增:使用 BGE-M3 嵌入
console.log("......[retrieve] userInput=",userInput)
const bgeEmbedding = await generateBgeM3Embedding(userInput)
//console.log("......[retrieve] [bge-m3] got embedding:", bgeEmbedding)
// 3. 调用 RPC 之前打印参数
console.log(
"......[retrieve] [bge-m3] calling RPC match_file_items_bge_m3 with:",
{
match_count: sourceCount,
file_ids: uniqueFileIds,
}
)
// 调用对应的 RPC需要在数据库侧提前定义 match_file_items_bge_m3
const { data: bgeFileItems, error: bgeError } =
await supabaseAdmin.rpc("match_file_items_bge_m3", {
query_embedding: bgeEmbedding as any,
match_count: sourceCount,
file_ids: uniqueFileIds
})
if (bgeError) {
console.log("......[retrieve] [bge-m3] RPC error full:", bgeError)
console.log("......[retrieve] [bge-m3] error.message:", bgeError.message)
throw bgeError
}
console.log("......[retrieve] [bge-m3] RPC result count =", bgeFileItems?.length)
chunks = bgeFileItems
} }
const mostSimilarChunks = chunks?.sort( const mostSimilarChunks = chunks?.sort(
@ -94,6 +135,12 @@ export async function POST(request: Request) {
status: 200 status: 200
}) })
} catch (error: any) { } catch (error: any) {
// 把完整错误对象都打印出来
console.log("......[retrieve] Caught error:", error)
console.log("......[retrieve] error.message:", error.message)
console.log("......[retrieve] error.error:", error.error)
const errorMessage = error.error?.message || "An unexpected error occurred" const errorMessage = error.error?.message || "An unexpected error occurred"
const errorCode = error.status || 500 const errorCode = error.status || 500
return new Response(JSON.stringify({ message: errorMessage }), { return new Response(JSON.stringify({ message: errorMessage }), {

View File

@ -21,10 +21,12 @@ import { Button } from "../ui/button"
import { FilePreview } from "../ui/file-preview" import { FilePreview } from "../ui/file-preview"
import { WithTooltip } from "../ui/with-tooltip" import { WithTooltip } from "../ui/with-tooltip"
import { ChatRetrievalSettings } from "./chat-retrieval-settings" import { ChatRetrievalSettings } from "./chat-retrieval-settings"
import { useTranslation } from "react-i18next"
interface ChatFilesDisplayProps {} interface ChatFilesDisplayProps {}
export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => { export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
const { t } = useTranslation()
useHotkey("f", () => setShowFilesDisplay(prev => !prev)) useHotkey("f", () => setShowFilesDisplay(prev => !prev))
useHotkey("e", () => setUseRetrieval(prev => !prev)) useHotkey("e", () => setUseRetrieval(prev => !prev))
@ -106,7 +108,7 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
> >
<RetrievalToggle /> <RetrievalToggle />
<div>Hide files</div> <div>{t("RAG.hideFiles")}</div>
<div onClick={e => e.stopPropagation()}> <div onClick={e => e.stopPropagation()}>
<ChatRetrievalSettings /> <ChatRetrievalSettings />
@ -233,10 +235,14 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
> >
<RetrievalToggle /> <RetrievalToggle />
<div> {/* <div>
{" "} {" "}
View {combinedMessageFiles.length} file View {combinedMessageFiles.length} file
{combinedMessageFiles.length > 1 ? "s" : ""} {combinedMessageFiles.length > 1 ? "s" : ""}
</div> */}
<div>
{t("RAG.viewFiles", { count: combinedMessageFiles.length })}
</div> </div>
<div onClick={e => e.stopPropagation()}> <div onClick={e => e.stopPropagation()}>
@ -249,6 +255,7 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
} }
const RetrievalToggle = ({}) => { const RetrievalToggle = ({}) => {
const { t } = useTranslation()
const { useRetrieval, setUseRetrieval } = useContext(ChatbotUIContext) const { useRetrieval, setUseRetrieval } = useContext(ChatbotUIContext)
return ( return (
@ -259,8 +266,8 @@ const RetrievalToggle = ({}) => {
display={ display={
<div> <div>
{useRetrieval {useRetrieval
? "File retrieval is enabled on the selected files for this message. Click the indicator to disable." ? t("RAG.retrievalEnabled")
: "Click the indicator to enable file retrieval for this message."} : t("RAG.retrievalDisabled")}
</div> </div>
} }
trigger={ trigger={

View File

@ -22,6 +22,13 @@ import {
import React from "react" import React from "react"
import { toast } from "sonner" import { toast } from "sonner"
import { v4 as uuidv4 } from "uuid" import { v4 as uuidv4 } from "uuid"
import { getRuntimeEnv } from "@/lib/ipconfig"
type RetrievedFileItem = Tables<"file_items"> & {
similarity: number
}
export const validateChatSettings = ( export const validateChatSettings = (
chatSettings: ChatSettings | null, chatSettings: ChatSettings | null,
@ -55,7 +62,7 @@ export const handleRetrieval = async (
userInput: string, userInput: string,
newMessageFiles: ChatFile[], newMessageFiles: ChatFile[],
chatFiles: ChatFile[], chatFiles: ChatFile[],
embeddingsProvider: "openai" | "local", embeddingsProvider: "openai" | "local" | "bge-m3",
sourceCount: number sourceCount: number
) => { ) => {
const response = await fetch("/api/retrieval/retrieve", { const response = await fetch("/api/retrieval/retrieve", {
@ -73,10 +80,86 @@ export const handleRetrieval = async (
} }
const { results } = (await response.json()) as { const { results } = (await response.json()) as {
results: Tables<"file_items">[] //results: Tables<"file_items">[]
results: RetrievedFileItem[]
} }
return results // return results
// ✅ 打印全部相似度得分和对应内容
results.forEach((item, index) => {
console.log(`Result ${index + 1}: similarity = ${(item.similarity * 100).toFixed(2)}%, content = ${item.content.slice(0, 100)}...`)
})
// ✅ 只保留 similarity >= 0.8 的记录(大于等于 80%
// const filteredResults = results.filter(item => item.similarity >= 0.8)
// return filteredResults
// // 阈值设定
// const HIGH_THRESHOLD = 0.8
// const LOW_THRESHOLD = 0.5
// let filteredResults: RetrievedFileItem[] = []
// // 找出所有 ≥ 0.8 的结果
// const highSimilarity = results.filter(item => item.similarity >= HIGH_THRESHOLD)
// if (highSimilarity.length > 0) {
// // ✅ 高相似度:返回全部
// filteredResults = highSimilarity
// } else {
// // ❌ 无高相似度
// const acceptable = results.filter(item => item.similarity >= LOW_THRESHOLD)
// if (acceptable.length === 0) {
// // 🚫 无结果满足最低要求
// filteredResults = []
// } else {
// // 🔍 返回最佳项
// const best = acceptable.reduce((a, b) => (a.similarity > b.similarity ? a : b))
// filteredResults = [best]
// }
// }
// console.log("🔍 筛选后结果:", {
// total: results.length,
// ">=80": highSimilarity.length,
// ">=50": results.filter(r => r.similarity >= LOW_THRESHOLD).length,
// finalReturned: filteredResults.length,
// topMatchScore: filteredResults[0]?.similarity
// })
// return filteredResults
// 阈值设定
const HIGH_THRESHOLD = 0.8
const LOW_THRESHOLD = 0.51
let filteredResults: RetrievedFileItem[] = []
// 收集所有 ≥ 0.51 的结果(中等和高)
const acceptable = results.filter(item => item.similarity >= LOW_THRESHOLD)
if (acceptable.length === 0) {
// 🚫 全部太低,直接返回空
filteredResults = []
} else {
// ✅ 返回所有 ≥ 0.51 的
filteredResults = acceptable
}
console.log("🔍 筛选后结果:", {
total: results.length,
">=80": results.filter(r => r.similarity >= HIGH_THRESHOLD).length,
">=50": acceptable.length,
finalReturned: filteredResults.length,
topMatchScore: filteredResults[0]?.similarity
})
return filteredResults
} }
export const createTempMessages = ( export const createTempMessages = (

View File

@ -120,6 +120,7 @@ export const useChatHandler = () => {
embeddingsProvider: selectedAssistant.embeddings_provider as embeddingsProvider: selectedAssistant.embeddings_provider as
| "openai" | "openai"
| "local" | "local"
| "bge-m3"
}) })
let allFiles = [] let allFiles = []
@ -261,6 +262,17 @@ export const useChatHandler = () => {
let retrievedFileItems: Tables<"file_items">[] = [] let retrievedFileItems: Tables<"file_items">[] = []
console.log(
'......[handleSendMessage] newMessageFiles.length =',
newMessageFiles.length,
', chatFiles.length =',
chatFiles.length,
', useRetrieval =',
useRetrieval
)
if ( if (
(newMessageFiles.length > 0 || chatFiles.length > 0) && (newMessageFiles.length > 0 || chatFiles.length > 0) &&
useRetrieval useRetrieval

View File

@ -1,38 +1,103 @@
// import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler"
// import { ChatbotUIContext } from "@/context/context"
// import { Tables } from "@/supabase/types"
// import { FC, useContext, useState } from "react"
// import { Message } from "../messages/message"
// interface ChatMessagesProps {}
// export const ChatMessages: FC<ChatMessagesProps> = ({}) => {
// const { chatMessages, chatFileItems } = useContext(ChatbotUIContext)
// const { handleSendEdit } = useChatHandler()
// const [editingMessage, setEditingMessage] = useState<Tables<"messages">>()
// return chatMessages
// .sort((a, b) => a.message.sequence_number - b.message.sequence_number)
// .map((chatMessage, index, array) => {
// const messageFileItems = chatFileItems.filter(
// (chatFileItem, _, self) =>
// chatMessage.fileItems.includes(chatFileItem.id) &&
// self.findIndex(item => item.id === chatFileItem.id) === _
// )
// return (
// <Message
// key={chatMessage.message.sequence_number}
// message={chatMessage.message}
// fileItems={messageFileItems}
// isEditing={editingMessage?.id === chatMessage.message.id}
// isLast={index === array.length - 1}
// onStartEdit={setEditingMessage}
// onCancelEdit={() => setEditingMessage(undefined)}
// onSubmitEdit={handleSendEdit}
// />
// )
// })
// }
import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler"
import { ChatbotUIContext } from "@/context/context" import { ChatbotUIContext } from "@/context/context"
import { Tables } from "@/supabase/types" import { Tables } from "@/supabase/types"
import { FC, useContext, useState } from "react" import { FC, useContext, useState, useRef, useEffect } from "react"
import { Message } from "../messages/message" import { Message } from "../messages/message"
interface ChatMessagesProps {} interface ChatMessagesProps {}
export const ChatMessages: FC<ChatMessagesProps> = ({}) => { export const ChatMessages: FC<ChatMessagesProps> = ({}) => {
const { chatMessages, chatFileItems } = useContext(ChatbotUIContext) const { chatMessages, chatFileItems } = useContext(ChatbotUIContext)
const { handleSendEdit } = useChatHandler() const { handleSendEdit } = useChatHandler()
const [editingMessage, setEditingMessage] = useState<Tables<"messages">>() const [editingMessage, setEditingMessage] = useState<Tables<"messages">>()
return chatMessages // 1. 创建一个 ref 指向滚动容器
.sort((a, b) => a.message.sequence_number - b.message.sequence_number) const containerRef = useRef<HTMLDivElement>(null)
.map((chatMessage, index, array) => {
const messageFileItems = chatFileItems.filter(
(chatFileItem, _, self) =>
chatMessage.fileItems.includes(chatFileItem.id) &&
self.findIndex(item => item.id === chatFileItem.id) === _
)
return ( // 2. 每次 chatMessages 变化时,滚到底部
<Message useEffect(() => {
key={chatMessage.message.sequence_number} const dom = containerRef.current
message={chatMessage.message} if (dom) {
fileItems={messageFileItems} dom.scrollTo({ top: dom.scrollHeight, behavior: "smooth" })
isEditing={editingMessage?.id === chatMessage.message.id} }
isLast={index === array.length - 1} }, [chatMessages])
onStartEdit={setEditingMessage}
onCancelEdit={() => setEditingMessage(undefined)} // 先排序
onSubmitEdit={handleSendEdit} const sorted = [...chatMessages].sort(
/> (a, b) => a.message.sequence_number - b.message.sequence_number
) )
})
return (
// 3. 给外层 div 加上 overflow-auto并且要有高度限制
<div
ref={containerRef}
className="w-full h-screen overflow-auto"
style={{ maxHeight: "100vh" }}
>
{sorted.map((chatMessage, index, array) => {
const messageFileItems = chatFileItems.filter(
(chatFileItem, _, self) =>
chatMessage.fileItems.includes(chatFileItem.id) &&
self.findIndex(item => item.id === chatFileItem.id) === _
)
return (
<Message
key={chatMessage.message.sequence_number}
message={chatMessage.message}
fileItems={messageFileItems}
isEditing={editingMessage?.id === chatMessage.message.id}
isLast={index === array.length - 1}
onStartEdit={setEditingMessage}
onCancelEdit={() => setEditingMessage(undefined)}
onSubmitEdit={handleSendEdit}
/>
)
})}
</div>
)
} }

View File

@ -11,10 +11,14 @@ import {
import { Label } from "../ui/label" import { Label } from "../ui/label"
import { Slider } from "../ui/slider" import { Slider } from "../ui/slider"
import { WithTooltip } from "../ui/with-tooltip" import { WithTooltip } from "../ui/with-tooltip"
import { useTranslation } from "react-i18next"
interface ChatRetrievalSettingsProps {} interface ChatRetrievalSettingsProps {}
export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => { export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
const { t } = useTranslation()
const { sourceCount, setSourceCount } = useContext(ChatbotUIContext) const { sourceCount, setSourceCount } = useContext(ChatbotUIContext)
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
@ -25,7 +29,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<WithTooltip <WithTooltip
delayDuration={0} delayDuration={0}
side="top" side="top"
display={<div>Adjust retrieval settings.</div>} display={<div>{t("RAG.adjustRetrievalSettings")}.</div>}
trigger={ trigger={
<IconAdjustmentsHorizontal <IconAdjustmentsHorizontal
className="cursor-pointer pt-[4px] hover:opacity-50" className="cursor-pointer pt-[4px] hover:opacity-50"
@ -38,7 +42,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogContent> <DialogContent>
<div className="space-y-3"> <div className="space-y-3">
<Label className="flex items-center space-x-1"> <Label className="flex items-center space-x-1">
<div>Source Count:</div> <div>{t("RAG.sourceCount")}:</div>
<div>{sourceCount}</div> <div>{sourceCount}</div>
</Label> </Label>
@ -56,7 +60,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogFooter> <DialogFooter>
<Button size="sm" onClick={() => setIsOpen(false)}> <Button size="sm" onClick={() => setIsOpen(false)}>
Save & Close {t("RAG.saveAndClose")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -23,6 +23,8 @@ import { TextareaAutosize } from "../ui/textarea-autosize"
import { WithTooltip } from "../ui/with-tooltip" import { WithTooltip } from "../ui/with-tooltip"
import { MessageActions } from "./message-actions" import { MessageActions } from "./message-actions"
import { MessageMarkdown } from "./message-markdown" import { MessageMarkdown } from "./message-markdown"
import { useTranslation } from "react-i18next"
const ICON_SIZE = 32 const ICON_SIZE = 32
@ -36,6 +38,100 @@ interface MessageProps {
onSubmitEdit: (value: string, sequenceNumber: number) => void onSubmitEdit: (value: string, sequenceNumber: number) => void
} }
// ✅ 提取为组件,放在同文件中也没问题
// const MessageContent: React.FC<{ content: string }> = ({ content }) => {
// const [showThink, setShowThink] = useState(false)
// let thinkContent = ""
// let visibleContent = content.trim()
// const fullMatch = content.match(/<think>([\s\S]*?)<\/think>/i)
// if (fullMatch) {
// thinkContent = fullMatch[1].trim()
// visibleContent = content.replace(fullMatch[0], "").trim()
// } else {
// const endOnlyMatch = content.match(/([\s\S]*?)<\/think>/i)
// if (endOnlyMatch) {
// thinkContent = endOnlyMatch[1].trim()
// visibleContent = content.replace(endOnlyMatch[0], "").trim()
// }
// }
// return (
// <div className="space-y-2">
// <MessageMarkdown content={visibleContent} />
// {thinkContent && (
// <div className="mt-2 text-sm text-muted-foreground">
// <button
// className="text-blue-500 hover:underline"
// onClick={() => setShowThink(prev => !prev)}
// >
// {showThink ? "隐藏思考过程 ▲" : "显示思考过程 ▼"}
// </button>
// {showThink && (
// <div className="mt-2 rounded bg-gray-100 p-3 text-sm dark:bg-gray-800">
// <MessageMarkdown content={thinkContent} />
// </div>
// )}
// </div>
// )}
// </div>
// )
// }
const MessageContent: React.FC<{ content: string }> = ({ content }) => {
const { t } = useTranslation()
const [showThink, setShowThink] = useState(false)
let thinkContent = ""
let visibleContent = content.trim()
const fullMatch = content.match(/<think>([\s\S]*?)<\/think>/i)
if (fullMatch) {
thinkContent = fullMatch[1].trim()
visibleContent = content.replace(fullMatch[0], "").trim()
} else {
const endOnlyMatch = content.match(/([\s\S]*?)<\/think>/i)
if (endOnlyMatch) {
thinkContent = endOnlyMatch[1].trim()
visibleContent = content.replace(endOnlyMatch[0], "").trim()
}
}
return (
<div className="space-y-2">
{/* 折叠内容提前显示 */}
{thinkContent && (
<div className="text-sm text-muted-foreground">
<button
className="text-sm font-medium text-foreground/70 hover:text-foreground underline underline-offset-2"
onClick={() => setShowThink(prev => !prev)}
>
{showThink
? t("chat.hideThinking") + " ▲"
: t("chat.showThinking") + " ▼"}
</button>
{showThink && (
<div className="mt-2 rounded bg-gray-100 p-3 text-sm dark:bg-gray-800">
<MessageMarkdown content={thinkContent} />
</div>
)}
</div>
)}
{/* 主消息显示在折叠内容之后 */}
<MessageMarkdown content={visibleContent} />
</div>
)
}
export const Message: FC<MessageProps> = ({ export const Message: FC<MessageProps> = ({
message, message,
fileItems, fileItems,
@ -62,7 +158,11 @@ export const Message: FC<MessageProps> = ({
models models
} = useContext(ChatbotUIContext) } = useContext(ChatbotUIContext)
const { handleSendMessage } = useChatHandler() const { t } = useTranslation()
const [showThink, setShowThink] = useState(false) //添加Think的状态
const { handleSendMessage } = useChatHandler()
const editInputRef = useRef<HTMLTextAreaElement>(null) const editInputRef = useRef<HTMLTextAreaElement>(null)
@ -209,7 +309,7 @@ export const Message: FC<MessageProps> = ({
size={ICON_SIZE} size={ICON_SIZE}
/> />
<div className="text-lg font-semibold">Prompt</div> <div className="text-lg font-semibold">{t("RAG.prompt")}</div>
</div> </div>
) : ( ) : (
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
@ -282,7 +382,7 @@ export const Message: FC<MessageProps> = ({
<div className="flex animate-pulse items-center space-x-2"> <div className="flex animate-pulse items-center space-x-2">
<IconFileText size={20} /> <IconFileText size={20} />
<div>Searching files...</div> <div>{t("RAG.searchingFiles")}...</div>
</div> </div>
) )
default: default:
@ -290,7 +390,8 @@ export const Message: FC<MessageProps> = ({
<div className="flex animate-pulse items-center space-x-2"> <div className="flex animate-pulse items-center space-x-2">
<IconBolt size={20} /> <IconBolt size={20} />
<div>Using {toolInUse}...</div> {/* <div>Using {toolInUse}...</div> */}
<div>{t("RAG.usingTool", { tool: toolInUse })}</div>
</div> </div>
) )
} }
@ -305,7 +406,19 @@ export const Message: FC<MessageProps> = ({
maxRows={20} maxRows={20}
/> />
) : ( ) : (
<MessageMarkdown content={message.content} />
// <MessageMarkdown content={message.content} />
<MessageContent content={message.content} />
)} )}
</div> </div>
@ -407,11 +520,11 @@ export const Message: FC<MessageProps> = ({
{isEditing && ( {isEditing && (
<div className="mt-4 flex justify-center space-x-2"> <div className="mt-4 flex justify-center space-x-2">
<Button size="sm" onClick={handleSendEdit}> <Button size="sm" onClick={handleSendEdit}>
Save & Send {t("RAG.saveAndSend")}
</Button> </Button>
<Button size="sm" variant="outline" onClick={onCancelEdit}> <Button size="sm" variant="outline" onClick={onCancelEdit}>
Cancel {t("RAG.cancel")}
</Button> </Button>
</div> </div>
)} )}

View File

@ -69,6 +69,9 @@ export const ModelSelect: FC<ModelSelectProps> = ({
...availableOpenRouterModels ...availableOpenRouterModels
] ]
// console.log("......ModelSelect - selectedModelId:", selectedModelId)
// console.log("......ModelSelect - allModels IDs:", allModels.map(m => m.modelId))
const groupedModels = allModels.reduce<Record<string, LLM[]>>( const groupedModels = allModels.reduce<Record<string, LLM[]>>(
(groups, model) => { (groups, model) => {
const key = model.provider const key = model.provider

View File

@ -21,6 +21,7 @@ interface StepContainerProps {
children?: React.ReactNode children?: React.ReactNode
showBackButton?: boolean showBackButton?: boolean
showNextButton?: boolean showNextButton?: boolean
isSubmitting?: boolean // ✅ 新增这个
} }
export const StepContainer: FC<StepContainerProps> = ({ export const StepContainer: FC<StepContainerProps> = ({
@ -30,7 +31,8 @@ export const StepContainer: FC<StepContainerProps> = ({
onShouldProceed, onShouldProceed,
children, children,
showBackButton = false, showBackButton = false,
showNextButton = true showNextButton = true,
isSubmitting = false // ✅ 加上这行
}) => { }) => {
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@ -80,6 +82,7 @@ export const StepContainer: FC<StepContainerProps> = ({
ref={buttonRef} ref={buttonRef}
size="sm" size="sm"
onClick={() => onShouldProceed(true)} onClick={() => onShouldProceed(true)}
disabled={isSubmitting}
> >
{t("setup.next")} {t("setup.next")}
</Button> </Button>

View File

@ -84,7 +84,7 @@ export const SidebarCreateItem: FC<SidebarCreateItemProps> = ({
file, file,
rest, rest,
workspaceId, workspaceId,
selectedWorkspace.embeddings_provider as "openai" | "local" selectedWorkspace.embeddings_provider as "openai" | "local" | "bge-m3"
) )
return createdFile return createdFile

View File

@ -85,7 +85,7 @@ export const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {
<Input <Input
placeholder={t("side.fileDescriptionPlaceholder")} placeholder={t("side.fileDescriptionPlaceholder")}
value={name} value={description} // ✅ 正确
onChange={e => setDescription(e.target.value)} onChange={e => setDescription(e.target.value)}
maxLength={FILE_DESCRIPTION_MAX} maxLength={FILE_DESCRIPTION_MAX}
/> />

View File

@ -234,7 +234,7 @@ const AdvancedContent: FC<AdvancedContentProps> = ({
<Select <Select
value={chatSettings.embeddingsProvider} value={chatSettings.embeddingsProvider}
onValueChange={(embeddingsProvider: "openai" | "local") => { onValueChange={(embeddingsProvider: "openai" | "local" | "bge-m3") => {
onChangeChatSettings({ onChangeChatSettings({
...chatSettings, ...chatSettings,
embeddingsProvider embeddingsProvider
@ -253,6 +253,9 @@ const AdvancedContent: FC<AdvancedContentProps> = ({
{window.location.hostname === "localhost" && ( {window.location.hostname === "localhost" && (
<SelectItem value="local">{t("chat.local")}</SelectItem> <SelectItem value="local">{t("chat.local")}</SelectItem>
)} )}
<SelectItem value="bge-m3">BGE-M3</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@ -96,13 +96,13 @@ export const GlobalState: FC<GlobalStateProps> = ({ children }) => {
const [userInput, setUserInput] = useState<string>("") const [userInput, setUserInput] = useState<string>("")
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]) const [chatMessages, setChatMessages] = useState<ChatMessage[]>([])
const [chatSettings, setChatSettings] = useState<ChatSettings>({ const [chatSettings, setChatSettings] = useState<ChatSettings>({
model: "gpt-4-turbo-preview", model: "GPT",
prompt: "You are a helpful AI assistant.", prompt: "You are a helpful AI assistant.",
temperature: 0.5, temperature: 0.5,
contextLength: 4000, contextLength: 4000,
includeProfileContext: true, includeProfileContext: true,
includeWorkspaceInstructions: true, includeWorkspaceInstructions: true,
embeddingsProvider: "openai" embeddingsProvider: "bge-m3"
}) })
const [selectedChat, setSelectedChat] = useState<Tables<"chats"> | null>(null) const [selectedChat, setSelectedChat] = useState<Tables<"chats"> | null>(null)
const [chatFileItems, setChatFileItems] = useState<Tables<"file_items">[]>([]) const [chatFileItems, setChatFileItems] = useState<Tables<"file_items">[]>([])
@ -181,12 +181,8 @@ export const GlobalState: FC<GlobalStateProps> = ({ children }) => {
setProfile(profile) setProfile(profile)
if (profile !== null && profile !== undefined && !profile.has_onboarded) { if (profile !== null && profile !== undefined && !profile.has_onboarded) {
// 修正 homePath 拼接逻辑,避免 //setup 问题
const homePath = locale === defaultLocale ? "" : `/${locale}` const homePath = locale === defaultLocale ? "" : `/${locale}`
//const targetPath = `${homePath}/setup`
const targetPath = `${homePath}/setup`.replace(/\/\//g, '/') const targetPath = `${homePath}/setup`.replace(/\/\//g, '/')
router.push(targetPath) router.push(targetPath)
} }

View File

@ -143,6 +143,7 @@ export const WorkspaceSettings: FC<WorkspaceSettingsProps> = ({}) => {
embeddingsProvider: defaultChatSettings.embeddingsProvider as embeddingsProvider: defaultChatSettings.embeddingsProvider as
| "openai" | "openai"
| "local" | "local"
| "bge-m3"
}) })
} }

View File

@ -62,7 +62,7 @@ export const createFileBasedOnExtension = async (
file: File, file: File,
fileRecord: TablesInsert<"files">, fileRecord: TablesInsert<"files">,
workspace_id: string, workspace_id: string,
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
) => { ) => {
const fileExtension = file.name.split(".").pop() const fileExtension = file.name.split(".").pop()
@ -89,9 +89,11 @@ export const createFile = async (
file: File, file: File,
fileRecord: TablesInsert<"files">, fileRecord: TablesInsert<"files">,
workspace_id: string, workspace_id: string,
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
) => { ) => {
let validFilename = fileRecord.name.replace(/[^a-z0-9.]/gi, "_").toLowerCase()
//let validFilename = fileRecord.name.replace(/[^a-z0-9.]/gi, "_").toLowerCase()
let validFilename = fileRecord.name.replace(/[<>:"/\\|?*%#\s]/g, "_")
const extension = file.name.split(".").pop() const extension = file.name.split(".").pop()
const extensionIndex = validFilename.lastIndexOf(".") const extensionIndex = validFilename.lastIndexOf(".")
const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex) const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex)
@ -101,6 +103,7 @@ export const createFile = async (
} else { } else {
fileRecord.name = baseName + "." + extension fileRecord.name = baseName + "." + extension
} }
console.log("......[createFile] Inserting file record:", fileRecord);
const { data: createdFile, error } = await supabase const { data: createdFile, error } = await supabase
.from("files") .from("files")
.insert([fileRecord]) .insert([fileRecord])
@ -116,7 +119,7 @@ export const createFile = async (
file_id: createdFile.id, file_id: createdFile.id,
workspace_id workspace_id
}) })
console.log("......[createFile] Uploading file to storage:", createdFile.name);
const filePath = await uploadFile(file, { const filePath = await uploadFile(file, {
name: createdFile.name, name: createdFile.name,
user_id: createdFile.user_id, user_id: createdFile.user_id,
@ -131,11 +134,20 @@ export const createFile = async (
formData.append("file_id", createdFile.id) formData.append("file_id", createdFile.id)
formData.append("embeddingsProvider", embeddingsProvider) formData.append("embeddingsProvider", embeddingsProvider)
console.log(
`......[createFile] Sending process request for file ${createdFile.id}, provider: ${embeddingsProvider}`
);
const response = await fetch("/api/retrieval/process", { const response = await fetch("/api/retrieval/process", {
method: "POST", method: "POST",
body: formData body: formData
}) })
console.log(
`......[createFile] /api/retrieval/process responded status=${response.status}`
);
if (!response.ok) { if (!response.ok) {
const jsonText = await response.text() const jsonText = await response.text()
const json = JSON.parse(jsonText) const json = JSON.parse(jsonText)
@ -159,8 +171,21 @@ export const createDocXFile = async (
file: File, file: File,
fileRecord: TablesInsert<"files">, fileRecord: TablesInsert<"files">,
workspace_id: string, workspace_id: string,
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
) => { ) => {
let validFilename = fileRecord.name.replace(/[<>:"/\\|?*%#\s]/g, "_")
const extension = file.name.split(".").pop()
const extensionIndex = validFilename.lastIndexOf(".")
const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex)
const maxBaseNameLength = 100 - (extension?.length || 0) - 1
if (baseName.length > maxBaseNameLength) {
fileRecord.name = baseName.substring(0, maxBaseNameLength) + "." + extension
} else {
fileRecord.name = baseName + "." + extension
}
const { data: createdFile, error } = await supabase const { data: createdFile, error } = await supabase
.from("files") .from("files")
.insert([fileRecord]) .insert([fileRecord])

View File

@ -157,6 +157,12 @@ export const CHAT_SETTING_LIMITS: Record<LLMID, ChatSettingLimits> = {
MAX_TOKEN_OUTPUT_LENGTH: 4096, MAX_TOKEN_OUTPUT_LENGTH: 4096,
MAX_CONTEXT_LENGTH: 128000 MAX_CONTEXT_LENGTH: 128000
}, },
"GPT": {
MIN_TEMPERATURE: 0.0,
MAX_TEMPERATURE: 2.0,
MAX_TOKEN_OUTPUT_LENGTH: 4096,
MAX_CONTEXT_LENGTH: 8192
},
// PERPLEXITY MODELS // PERPLEXITY MODELS
"pplx-7b-online": { "pplx-7b-online": {

View File

@ -0,0 +1,41 @@
import { getRuntimeEnv } from "@/lib/ipconfig"
export async function generateBgeM3Embedding(text: string): Promise<number[] | null> {
try {
// 取 Supabase URL 或本地默认
const supaUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
// 构造 Embedding 服务地址:同 host + 8001 端口
const urlObj = new URL(supaUrl)
urlObj.port = "8001" // 强制改成 8001
const apiUrl = `${urlObj.origin}/v1/embeddings`
console.debug("......[generateBgeM3Embedding] apiUrl =", apiUrl)
const response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
// OpenAI 兼容请求字段
input: text,
model: "text-embedding-bge-m3"
})
});
if (!response.ok) {
throw new Error(`Failed to fetch BGE-M3 embedding: ${response.status}`);
}
// 返回结构为 { object, data: [{ embedding, … }], model, usage }
const result = await response.json();
// 取 data[0].embedding
if (Array.isArray(result.data) && result.data.length > 0) {
return result.data[0].embedding as number[];
} else {
console.error("Unexpected embedding response format:", result);
return null;
}
} catch (err) {
console.error("Error in generateBgeM3Embedding:", err);
return null;
}
}

View File

@ -6,39 +6,6 @@
// return process.env[key]; // return process.env[key];
// } // }
// let _env: Record<string, string> | null = null
// export function getRuntimeEnv(key: string): string | undefined {
// console.log("============>>Getting Supabase API URL.")
// if (typeof window !== "undefined") {
// if (!_env && typeof window.RUNTIME_ENV !== "undefined") {
// _env = window.RUNTIME_ENV
// console.log("[browser-side] Retrieved API endpoint from window.RUNTIME_ENV:", _env);
// }
// if (_env) {
// console.log("[browser-side]Returning cached API from _env:", _env)
// return _env[key]
// }
// console.log("[browser-side]No window.RUNTIME_ENV found in browser")
// return undefined
// }
// // 服务端始终动态读取 process.env
// const val = process.env[key]
// console.log("[server-side] Falling back to process.env for key:", key, "value:", val)
// return val
// }
let _env: Record<string, string> | null = null let _env: Record<string, string> | null = null
// 最大重试次数 // 最大重试次数
@ -47,7 +14,7 @@ const MAX_RETRIES = 5
const RETRY_DELAY = 1000 const RETRY_DELAY = 1000
export function getRuntimeEnv(key: string): string | undefined { export function getRuntimeEnv(key: string): string | undefined {
console.log("============>>Getting Supabase API URL.") //console.log("============>>Getting Supabase API URL.")
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
if (!_env && typeof window.RUNTIME_ENV !== "undefined") { if (!_env && typeof window.RUNTIME_ENV !== "undefined") {
@ -88,6 +55,6 @@ export function getRuntimeEnv(key: string): string | undefined {
// 服务端始终动态读取 process.env // 服务端始终动态读取 process.env
const val = process.env[key] const val = process.env[key]
console.log("[server-side] Falling back to process.env for key:", key, "value:", val) //console.log("[server-side] Falling back to process.env for key:", key, "value:", val)
return val // 服务端直接返回 process.env 的值 return val // 服务端直接返回 process.env 的值
} }

View File

@ -58,8 +58,19 @@ export const fetchOllamaModels = async () => {
process.env.NEXT_PUBLIC_OLLAMA_URL + "/api/tags" process.env.NEXT_PUBLIC_OLLAMA_URL + "/api/tags"
) )
// const response = await fetch(
// process.env.NEXT_PUBLIC_OLLAMA_URL + "/api/tags",
// {
// method: "GET",
// headers: {
// "Authorization": "Bearer token-abc123",
// "Content-Type": "application/json"
// }
// }
// )
if (!response.ok) { if (!response.ok) {
throw new Error(`Ollama server is not responding.`) throw new Error(`LLM server is not responding.`)
} }
const data = await response.json() const data = await response.json()

View File

@ -7,19 +7,6 @@ import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
export async function getServerProfile() { export async function getServerProfile() {
// const cookieStore = cookies()
// const supabase = createServerClient<Database>(
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
// process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
// {
// cookies: {
// get(name: string) {
// return cookieStore.get(name)?.value
// }
// }
// }
// )
const supabase = getSupabaseServerClient() const supabase = getSupabaseServerClient()
const user = (await supabase.auth.getUser()).data.user const user = (await supabase.auth.getUser()).data.user

View File

@ -19,6 +19,10 @@ module.exports = withBundleAnalyzer(
protocol: "http", protocol: "http",
hostname: "127.0.0.1" hostname: "127.0.0.1"
}, },
{
protocol: "http",
hostname: "**"
},
{ {
protocol: "https", protocol: "https",
hostname: "**" hostname: "**"

View File

@ -1,3 +0,0 @@
window.RUNTIME_ENV = {
"SUPABASE_URL": "http://localhost:8000"
};

View File

@ -36,7 +36,9 @@
"back": "رجوع", "back": "رجوع",
"next": "التالي", "next": "التالي",
"WelcomeToChatDeskUI": "مرحبًا بك في ChatDesk، منصة الواجهة الأمامية لتطوير أبحاث دردشة الذكاء الاصطناعي", "WelcomeToChatDeskUI": "مرحبًا بك في ChatDesk، منصة الواجهة الأمامية لتطوير أبحاث دردشة الذكاء الاصطناعي",
"ClickNextToStartChatting": "انقر على 'التالي' لبدء الدردشة مع نموذج اللغة الكبير باستخدام ChatDesk." "ClickNextToStartChatting": "انقر على 'التالي' لبدء الدردشة مع نموذج اللغة الكبير باستخدام ChatDesk.",
"Default": "افتراضي",
"DefaultLLMModelCreatedDuringOnboarding": "تم إنشاء نموذج LLM الافتراضي أثناء الإعداد"
}, },
"login": { "login": {
"email": "البريد الإلكتروني", "email": "البريد الإلكتروني",
@ -61,7 +63,8 @@
"signupNotAllowed": "البريد الإلكتروني {{email}} غير مسموح له بالتسجيل.", "signupNotAllowed": "البريد الإلكتروني {{email}} غير مسموح له بالتسجيل.",
"unexpectedError": "حدث خطأ غير متوقع", "unexpectedError": "حدث خطأ غير متوقع",
"invalidCredentials": "البريد الإلكتروني أو كلمة المرور غير صحيحة.", "invalidCredentials": "البريد الإلكتروني أو كلمة المرور غير صحيحة.",
"clearing": "جارٍ مسح حالة تسجيل الدخول..." "clearing": "جارٍ مسح حالة تسجيل الدخول...",
"sessionExpired": "انتهت صلاحية الجلسة الخاصة بك. يرجى التسجيل أو تسجيل الدخول مرة أخرى."
}, },
"chat": { "chat": {
"defaultChatTitle": "الدردشة", "defaultChatTitle": "الدردشة",
@ -85,7 +88,9 @@
"selectModel": "اختر نموذجًا", "selectModel": "اختر نموذجًا",
"hosted": "مستضاف", "hosted": "مستضاف",
"advancedSettings": "الإعدادات المتقدمة", "advancedSettings": "الإعدادات المتقدمة",
"searchModelsPlaceholder": "ابحث عن النماذج..." "searchModelsPlaceholder": "ابحث عن النماذج...",
"showThinking": "عرض عملية التفكير",
"hideThinking": "إخفاء عملية التفكير"
}, },
"profile": { "profile": {
"settingsTitle": "إعدادات المستخدم", "settingsTitle": "إعدادات المستخدم",
@ -283,5 +288,19 @@
"enabled": "مفعل", "enabled": "مفعل",
"disabled": "معطل", "disabled": "معطل",
"startNewChat": "ابدأ محادثة جديدة" "startNewChat": "ابدأ محادثة جديدة"
},
"RAG": {
"prompt": "الموجه",
"searchingFiles": "جارٍ البحث في الملفات...",
"usingTool": "يتم استخدام {{tool}}...",
"saveAndSend": "حفظ وإرسال",
"cancel": "إلغاء",
"hideFiles": "إخفاء الملفات",
"retrievalEnabled": "تم تمكين استرجاع الملفات على الملفات المحددة لهذه الرسالة. انقر على المؤشر للتعطيل.",
"retrievalDisabled": "انقر على المؤشر لتمكين استرجاع الملفات لهذه الرسالة.",
"viewFiles": "عرض {{count}} {{count, plural, one {ملف} other {ملفات}}}",
"sourceCount": "عدد المصادر:",
"adjustRetrievalSettings": "ضبط إعدادات الاسترجاع.",
"saveAndClose": "حفظ وإغلاق"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "পেছনে", "back": "পেছনে",
"next": "পরবর্তী", "next": "পরবর্তী",
"WelcomeToChatDeskUI": "ChatDesk-এ স্বাগতম, এটি একটি ফ্রন্টএন্ড প্ল্যাটফর্ম AI চ্যাট গবেষণা ও উন্নয়নের জন্য।", "WelcomeToChatDeskUI": "ChatDesk-এ স্বাগতম, এটি একটি ফ্রন্টএন্ড প্ল্যাটফর্ম AI চ্যাট গবেষণা ও উন্নয়নের জন্য।",
"ClickNextToStartChatting": "'পরবর্তী' ক্লিক করে আপনার LLM-এর সাথে ChatDesk ব্যবহার করে চ্যাটিং শুরু করুন।" "ClickNextToStartChatting": "'পরবর্তী' ক্লিক করে আপনার LLM-এর সাথে ChatDesk ব্যবহার করে চ্যাটিং শুরু করুন।",
"Default": "ডিফল্ট",
"DefaultLLMModelCreatedDuringOnboarding": "অনবোর্ডিং চলাকালীন ডিফল্ট LLM মডেল তৈরি করা হয়েছে"
}, },
"login": { "login": {
"email": "ইমেইল", "email": "ইমেইল",
@ -61,7 +63,8 @@
"signupNotAllowed": "ইমেইল {{email}} দিয়ে সাইন আপ করা অনুমোদিত নয়।", "signupNotAllowed": "ইমেইল {{email}} দিয়ে সাইন আপ করা অনুমোদিত নয়।",
"unexpectedError": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে", "unexpectedError": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে",
"invalidCredentials": "অবৈধ ইমেইল বা পাসওয়ার্ড।", "invalidCredentials": "অবৈধ ইমেইল বা পাসওয়ার্ড।",
"clearing": "লগইন অবস্থা মুছে ফেলা হচ্ছে..." "clearing": "লগইন অবস্থা মুছে ফেলা হচ্ছে...",
"sessionExpired": "আপনার সেশনটির মেয়াদ শেষ হয়েছে। অনুগ্রহ করে আবার সাইন আপ বা লগইন করুন।"
}, },
"chat": { "chat": {
"defaultChatTitle": "চ্যাট", "defaultChatTitle": "চ্যাট",
@ -85,7 +88,9 @@
"selectModel": "একটি মডেল নির্বাচন করুন", "selectModel": "একটি মডেল নির্বাচন করুন",
"hosted": "হোস্টেড", "hosted": "হোস্টেড",
"advancedSettings": "উন্নত সেটিংস", "advancedSettings": "উন্নত সেটিংস",
"searchModelsPlaceholder": "মডেল খুঁজুন..." "searchModelsPlaceholder": "মডেল খুঁজুন...",
"showThinking": "ভাবনার প্রক্রিয়া দেখান",
"hideThinking": "ভাবনার প্রক্রিয়া লুকান"
}, },
"profile": { "profile": {
"settingsTitle": "ব্যবহারকারী সেটিংস", "settingsTitle": "ব্যবহারকারী সেটিংস",
@ -283,5 +288,19 @@
"enabled": "সক্রিয়", "enabled": "সক্রিয়",
"disabled": "নিষ্ক্রিয়", "disabled": "নিষ্ক্রিয়",
"startNewChat": "নতুন চ্যাট শুরু করুন" "startNewChat": "নতুন চ্যাট শুরু করুন"
},
"RAG": {
"prompt": "প্রম্পট",
"searchingFiles": "ফাইল অনুসন্ধান করা হচ্ছে...",
"usingTool": "{{tool}} ব্যবহার করা হচ্ছে...",
"saveAndSend": "সংরক্ষণ এবং প্রেরণ",
"cancel": "বাতিল করুন",
"hideFiles": "ফাইল লুকান",
"retrievalEnabled": "এই বার্তার জন্য নির্বাচিত ফাইলগুলিতে ফাইল পুনরুদ্ধার সক্রিয় করা হয়েছে। নিষ্ক্রিয় করতে সূচকে ক্লিক করুন।",
"retrievalDisabled": "এই বার্তার জন্য ফাইল পুনরুদ্ধার সক্রিয় করতে সূচকে ক্লিক করুন।",
"viewFiles": "{{count}} {{count, plural, one {ফাইল} other {ফাইলগুলি}}} দেখান",
"sourceCount": "উৎসের সংখ্যা:",
"adjustRetrievalSettings": "পুনরুদ্ধার সেটিংস সমন্বয় করুন।",
"saveAndClose": "সংরক্ষণ করুন এবং বন্ধ করুন"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Zurück", "back": "Zurück",
"next": "Weiter", "next": "Weiter",
"WelcomeToChatDeskUI": "Willkommen bei ChatDesk, der Front-End-Plattform für KI-Chat-Forschung und -Entwicklung", "WelcomeToChatDeskUI": "Willkommen bei ChatDesk, der Front-End-Plattform für KI-Chat-Forschung und -Entwicklung",
"ClickNextToStartChatting": "Klicke auf 'Weiter', um mit deinem LLM über ChatDesk zu chatten." "ClickNextToStartChatting": "Klicke auf 'Weiter', um mit deinem LLM über ChatDesk zu chatten.",
"Default": "Standard",
"DefaultLLMModelCreatedDuringOnboarding": "Standard-LLM-Modell während der Einrichtung erstellt"
}, },
"login": { "login": {
"email": "E-Mail", "email": "E-Mail",
@ -61,7 +63,8 @@
"signupNotAllowed": "Die E-Mail-Adresse {{email}} ist nicht zur Registrierung zugelassen.", "signupNotAllowed": "Die E-Mail-Adresse {{email}} ist nicht zur Registrierung zugelassen.",
"unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.",
"invalidCredentials": "Ungültige E-Mail-Adresse oder ungültiges Passwort.", "invalidCredentials": "Ungültige E-Mail-Adresse oder ungültiges Passwort.",
"clearing": "Anmeldestatus wird gelöscht..." "clearing": "Anmeldestatus wird gelöscht...",
"sessionExpired": "Ihre Sitzung ist abgelaufen. Bitte registrieren Sie sich erneut oder melden Sie sich erneut an."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chat", "defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Modell auswählen", "selectModel": "Modell auswählen",
"hosted": "Gehostet", "hosted": "Gehostet",
"advancedSettings": "Erweiterte Einstellungen", "advancedSettings": "Erweiterte Einstellungen",
"searchModelsPlaceholder": "Modelle suchen..." "searchModelsPlaceholder": "Modelle suchen...",
"showThinking": "Denkprozess anzeigen",
"hideThinking": "Denkprozess ausblenden"
}, },
"profile": { "profile": {
"settingsTitle": "Benutzereinstellungen", "settingsTitle": "Benutzereinstellungen",
@ -283,5 +288,19 @@
"enabled": "Aktiviert", "enabled": "Aktiviert",
"disabled": "Deaktiviert", "disabled": "Deaktiviert",
"startNewChat": "Neuen Chat starten" "startNewChat": "Neuen Chat starten"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Dateien werden durchsucht...",
"usingTool": "{{tool}} wird verwendet...",
"saveAndSend": "Speichern & Senden",
"cancel": "Abbrechen",
"hideFiles": "Dateien ausblenden",
"retrievalEnabled": "Die Dateiwiederherstellung ist für die ausgewählten Dateien dieser Nachricht aktiviert. Klicken Sie auf den Indikator, um sie zu deaktivieren.",
"retrievalDisabled": "Klicken Sie auf den Indikator, um die Dateiwiederherstellung für diese Nachricht zu aktivieren.",
"viewFiles": "{{count}} {{count, plural, one {Datei} other {Dateien}}} anzeigen",
"sourceCount": "Quellenanzahl:",
"adjustRetrievalSettings": "Abrufeinstellungen anpassen.",
"saveAndClose": "Speichern & Schließen"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Back", "back": "Back",
"next": "Next", "next": "Next",
"WelcomeToChatDeskUI": "Welcome to ChatDesk, the Front-End Platform for AI Chat R&D", "WelcomeToChatDeskUI": "Welcome to ChatDesk, the Front-End Platform for AI Chat R&D",
"ClickNextToStartChatting": "Click 'Next' to start chatting with your LLM using ChatDesk." "ClickNextToStartChatting": "Click 'Next' to start chatting with your LLM using ChatDesk.",
"Default": "Default",
"DefaultLLMModelCreatedDuringOnboarding": "Default LLM model created during onboarding"
}, },
"login": { "login": {
"email": "Email", "email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} is not allowed to sign up.", "signupNotAllowed": "Email {{email}} is not allowed to sign up.",
"unexpectedError": "An unexpected error occurred", "unexpectedError": "An unexpected error occurred",
"invalidCredentials": "Invalid email or password.", "invalidCredentials": "Invalid email or password.",
"clearing": "Clearing login state..." "clearing": "Clearing login state...",
"sessionExpired": "Your session has expired. Please sign up or log in again."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chat", "defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Select a model", "selectModel": "Select a model",
"hosted": "Hosted", "hosted": "Hosted",
"advancedSettings": "Advanced Settings", "advancedSettings": "Advanced Settings",
"searchModelsPlaceholder": "Search models..." "searchModelsPlaceholder": "Search models...",
"showThinking": "Show Thought Process",
"hideThinking": "Hide Thought Process"
}, },
"profile": { "profile": {
"settingsTitle": "User Settings", "settingsTitle": "User Settings",
@ -283,5 +288,19 @@
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"startNewChat": "Start a new chat" "startNewChat": "Start a new chat"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Searching files...",
"usingTool": "Using {{tool}}...",
"saveAndSend": "Save & Send",
"cancel": "Cancel",
"hideFiles": "Hide files",
"retrievalEnabled": "File retrieval is enabled on the selected files for this message. Click the indicator to disable.",
"retrievalDisabled": "Click the indicator to enable file retrieval for this message.",
"viewFiles": "View {{count}} {{count, plural, one {file} other {files}}}",
"sourceCount": "Source Count:",
"adjustRetrievalSettings": "Adjust retrieval settings.",
"saveAndClose": "Save & Close"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Atrás", "back": "Atrás",
"next": "Siguiente", "next": "Siguiente",
"WelcomeToChatDeskUI": "Bienvenido a ChatDesk, la plataforma frontend para el desarrollo de chats con IA", "WelcomeToChatDeskUI": "Bienvenido a ChatDesk, la plataforma frontend para el desarrollo de chats con IA",
"ClickNextToStartChatting": "Haz clic en 'Siguiente' para comenzar a chatear con tu LLM usando ChatDesk." "ClickNextToStartChatting": "Haz clic en 'Siguiente' para comenzar a chatear con tu LLM usando ChatDesk.",
"Default": "Predeterminado",
"DefaultLLMModelCreatedDuringOnboarding": "Modelo LLM predeterminado creado durante la incorporación"
}, },
"login": { "login": {
"email": "Correo electrónico", "email": "Correo electrónico",
@ -61,7 +63,8 @@
"signupNotAllowed": "El correo {{email}} no está permitido para registrarse.", "signupNotAllowed": "El correo {{email}} no está permitido para registrarse.",
"unexpectedError": "Ocurrió un error inesperado.", "unexpectedError": "Ocurrió un error inesperado.",
"invalidCredentials": "Correo electrónico o contraseña inválidos.", "invalidCredentials": "Correo electrónico o contraseña inválidos.",
"clearing": "Eliminando el estado de inicio de sesión..." "clearing": "Eliminando el estado de inicio de sesión...",
"sessionExpired": "Tu sesión ha expirado. Por favor, regístrate o inicia sesión nuevamente."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chat", "defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Seleccionar modelo", "selectModel": "Seleccionar modelo",
"hosted": "Alojado", "hosted": "Alojado",
"advancedSettings": "Configuración avanzada", "advancedSettings": "Configuración avanzada",
"searchModelsPlaceholder": "Buscar modelos..." "searchModelsPlaceholder": "Buscar modelos...",
"showThinking": "Mostrar proceso de pensamiento",
"hideThinking": "Ocultar proceso de pensamiento"
}, },
"profile": { "profile": {
"settingsTitle": "Configuración del usuario", "settingsTitle": "Configuración del usuario",
@ -283,5 +288,19 @@
"enabled": "Activado", "enabled": "Activado",
"disabled": "Desactivado", "disabled": "Desactivado",
"startNewChat": "Iniciar nuevo chat" "startNewChat": "Iniciar nuevo chat"
},
"RAG": {
"prompt": "Indicador",
"searchingFiles": "Buscando archivos...",
"usingTool": "Usando {{tool}}...",
"saveAndSend": "Guardar y enviar",
"cancel": "Cancelar",
"hideFiles": "Ocultar archivos",
"retrievalEnabled": "La recuperación de archivos está habilitada en los archivos seleccionados para este mensaje. Haz clic en el indicador para desactivar.",
"retrievalDisabled": "Haz clic en el indicador para habilitar la recuperación de archivos para este mensaje.",
"viewFiles": "Ver {{count}} {{count, plural, one {archivo} other {archivos}}}",
"sourceCount": "Cantidad de fuentes:",
"adjustRetrievalSettings": "Ajustar configuración de recuperación.",
"saveAndClose": "Guardar y cerrar"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Retour", "back": "Retour",
"next": "Suivant", "next": "Suivant",
"WelcomeToChatDeskUI": "Bienvenue sur ChatDesk, la plateforme frontale pour la R&D en chat IA", "WelcomeToChatDeskUI": "Bienvenue sur ChatDesk, la plateforme frontale pour la R&D en chat IA",
"ClickNextToStartChatting": "Cliquez sur 'Suivant' pour commencer à discuter avec votre LLM via ChatDesk." "ClickNextToStartChatting": "Cliquez sur 'Suivant' pour commencer à discuter avec votre LLM via ChatDesk.",
"Default": "Par défaut",
"DefaultLLMModelCreatedDuringOnboarding": "Modèle LLM par défaut créé lors de l'intégration"
}, },
"login": { "login": {
"email": "E-mail", "email": "E-mail",
@ -61,7 +63,8 @@
"signupNotAllowed": "Le-mail {{email}} nest pas autorisé à sinscrire.", "signupNotAllowed": "Le-mail {{email}} nest pas autorisé à sinscrire.",
"unexpectedError": "Une erreur inattendue sest produite", "unexpectedError": "Une erreur inattendue sest produite",
"invalidCredentials": "E-mail ou mot de passe invalide.", "invalidCredentials": "E-mail ou mot de passe invalide.",
"clearing": "Suppression de l'état de connexion..." "clearing": "Suppression de l'état de connexion...",
"sessionExpired": "Votre session a expiré. Veuillez vous inscrire ou vous reconnecter."
}, },
"chat": { "chat": {
"defaultChatTitle": "Discussion", "defaultChatTitle": "Discussion",
@ -85,7 +88,9 @@
"selectModel": "Sélectionner un modèle", "selectModel": "Sélectionner un modèle",
"hosted": "Hébergé", "hosted": "Hébergé",
"advancedSettings": "Paramètres avancés", "advancedSettings": "Paramètres avancés",
"searchModelsPlaceholder": "Rechercher des modèles..." "searchModelsPlaceholder": "Rechercher des modèles...",
"showThinking": "Afficher le processus de réflexion",
"hideThinking": "Masquer le processus de réflexion"
}, },
"profile": { "profile": {
"settingsTitle": "Paramètres utilisateur", "settingsTitle": "Paramètres utilisateur",
@ -283,5 +288,19 @@
"enabled": "Activé", "enabled": "Activé",
"disabled": "Désactivé", "disabled": "Désactivé",
"startNewChat": "Commencer une nouvelle discussion" "startNewChat": "Commencer une nouvelle discussion"
},
"RAG": {
"prompt": "Invite",
"searchingFiles": "Recherche de fichiers...",
"usingTool": "Utilisation de {{tool}}...",
"saveAndSend": "Enregistrer et envoyer",
"cancel": "Annuler",
"hideFiles": "Masquer les fichiers",
"retrievalEnabled": "La récupération de fichiers est activée sur les fichiers sélectionnés pour ce message. Cliquez sur l'indicateur pour désactiver.",
"retrievalDisabled": "Cliquez sur l'indicateur pour activer la récupération de fichiers pour ce message.",
"viewFiles": "Voir {{count}} {{count, plural, one {fichier} other {fichiers}}}",
"sourceCount": "Nombre de sources :",
"adjustRetrievalSettings": "Ajuster les paramètres de récupération.",
"saveAndClose": "Enregistrer et fermer"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "חזור", "back": "חזור",
"next": "הבא", "next": "הבא",
"WelcomeToChatDeskUI": "ברוך הבא ל-ChatDesk, פלטפורמת ממשק קדמי למחקר ופיתוח של צ'אט בינה מלאכותית", "WelcomeToChatDeskUI": "ברוך הבא ל-ChatDesk, פלטפורמת ממשק קדמי למחקר ופיתוח של צ'אט בינה מלאכותית",
"ClickNextToStartChatting": "לחץ על 'הבא' כדי להתחיל לשוחח עם מודל השפה שלך באמצעות ChatDesk." "ClickNextToStartChatting": "לחץ על 'הבא' כדי להתחיל לשוחח עם מודל השפה שלך באמצעות ChatDesk.",
"Default": "ברירת מחדל",
"DefaultLLMModelCreatedDuringOnboarding": "מודל LLM ברירת מחדל שנוצר במהלך ההגדרה הראשונית"
}, },
"login": { "login": {
"email": "אימייל", "email": "אימייל",
@ -61,7 +63,8 @@
"signupNotAllowed": "האימייל {{email}} אינו מורשה להירשם.", "signupNotAllowed": "האימייל {{email}} אינו מורשה להירשם.",
"unexpectedError": "אירעה שגיאה בלתי צפויה", "unexpectedError": "אירעה שגיאה בלתי צפויה",
"invalidCredentials": "אימייל או סיסמה לא נכונים.", "invalidCredentials": "אימייל או סיסמה לא נכונים.",
"clearing": "מנקה את מצב ההתחברות..." "clearing": "מנקה את מצב ההתחברות...",
"sessionExpired": "ההפעלה שלך פגה. אנא הירשם או התחבר מחדש."
}, },
"chat": { "chat": {
"defaultChatTitle": "צ'אט", "defaultChatTitle": "צ'אט",
@ -85,7 +88,10 @@
"selectModel": "בחר מודל", "selectModel": "בחר מודל",
"hosted": "מארח", "hosted": "מארח",
"advancedSettings": "הגדרות מתקדמות", "advancedSettings": "הגדרות מתקדמות",
"searchModelsPlaceholder": "חפש מודלים..." "searchModelsPlaceholder": "חפש מודלים...",
"showThinking": "הצג תהליך חשיבה",
"hideThinking": "הסתר תהליך חשיבה"
}, },
"profile": { "profile": {
"settingsTitle": "הגדרות משתמש", "settingsTitle": "הגדרות משתמש",
@ -283,5 +289,19 @@
"enabled": "מופעל", "enabled": "מופעל",
"disabled": "מנוטרל", "disabled": "מנוטרל",
"startNewChat": "התחל צ׳אט חדש" "startNewChat": "התחל צ׳אט חדש"
},
"RAG": {
"prompt": "הנחיה",
"searchingFiles": "מחפש קבצים...",
"usingTool": "משתמש ב־{{tool}}...",
"saveAndSend": "שמור ושלח",
"cancel": "ביטול",
"hideFiles": "הסתר קבצים",
"retrievalEnabled": "שליפת קבצים מופעלת לקבצים שנבחרו עבור הודעה זו. לחץ על הסמן כדי להשבית.",
"retrievalDisabled": "לחץ על הסמן כדי להפעיל שליפת קבצים להודעה זו.",
"viewFiles": "הצג {{count}} {{count, plural, one {קובץ} other {קבצים}}}",
"sourceCount": "כמות מקורות:",
"adjustRetrievalSettings": "התאם הגדרות שליפה.",
"saveAndClose": "שמור וסגור"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Kembali", "back": "Kembali",
"next": "Berikutnya", "next": "Berikutnya",
"WelcomeToChatDeskUI": "Selamat datang di ChatDesk, Platform Front-End untuk R&D Chat AI", "WelcomeToChatDeskUI": "Selamat datang di ChatDesk, Platform Front-End untuk R&D Chat AI",
"ClickNextToStartChatting": "Klik 'Berikutnya' untuk mulai mengobrol dengan LLM Anda menggunakan ChatDesk." "ClickNextToStartChatting": "Klik 'Berikutnya' untuk mulai mengobrol dengan LLM Anda menggunakan ChatDesk.",
"Default": "Bawaan",
"DefaultLLMModelCreatedDuringOnboarding": "Model LLM default dibuat selama proses onboarding"
}, },
"login": { "login": {
"email": "Email", "email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} tidak diizinkan untuk mendaftar.", "signupNotAllowed": "Email {{email}} tidak diizinkan untuk mendaftar.",
"unexpectedError": "Terjadi kesalahan yang tidak terduga", "unexpectedError": "Terjadi kesalahan yang tidak terduga",
"invalidCredentials": "Email atau kata sandi salah.", "invalidCredentials": "Email atau kata sandi salah.",
"clearing": "Menghapus status login..." "clearing": "Menghapus status login...",
"sessionExpired": "Sesi Anda telah kedaluwarsa. Silakan daftar atau masuk kembali."
}, },
"chat": { "chat": {
"defaultChatTitle": "Obrolan", "defaultChatTitle": "Obrolan",
@ -85,7 +88,9 @@
"selectModel": "Pilih model", "selectModel": "Pilih model",
"hosted": "Hosted", "hosted": "Hosted",
"advancedSettings": "Pengaturan Lanjutan", "advancedSettings": "Pengaturan Lanjutan",
"searchModelsPlaceholder": "Cari model..." "searchModelsPlaceholder": "Cari model...",
"showThinking": "Tampilkan proses berpikir",
"hideThinking": "Sembunyikan proses berpikir"
}, },
"profile": { "profile": {
"settingsTitle": "Pengaturan Pengguna", "settingsTitle": "Pengaturan Pengguna",
@ -283,5 +288,19 @@
"enabled": "Aktif", "enabled": "Aktif",
"disabled": "Nonaktif", "disabled": "Nonaktif",
"startNewChat": "Mulai Obrolan Baru" "startNewChat": "Mulai Obrolan Baru"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Mencari file...",
"usingTool": "Menggunakan {{tool}}...",
"saveAndSend": "Simpan & Kirim",
"cancel": "Batal",
"hideFiles": "Sembunyikan file",
"retrievalEnabled": "Pengambilan file diaktifkan pada file yang dipilih untuk pesan ini. Klik indikator untuk menonaktifkan.",
"retrievalDisabled": "Klik indikator untuk mengaktifkan pengambilan file untuk pesan ini.",
"viewFiles": "Lihat {{count}} berkas",
"sourceCount": "Jumlah Sumber:",
"adjustRetrievalSettings": "Sesuaikan pengaturan pengambilan.",
"saveAndClose": "Simpan & Tutup"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Indietro", "back": "Indietro",
"next": "Avanti", "next": "Avanti",
"WelcomeToChatDeskUI": "Benvenuto in ChatDesk, la piattaforma frontend per la ricerca e sviluppo delle chat basate su AI", "WelcomeToChatDeskUI": "Benvenuto in ChatDesk, la piattaforma frontend per la ricerca e sviluppo delle chat basate su AI",
"ClickNextToStartChatting": "Fai clic su 'Avanti' per iniziare a chattare con il tuo LLM utilizzando ChatDesk." "ClickNextToStartChatting": "Fai clic su 'Avanti' per iniziare a chattare con il tuo LLM utilizzando ChatDesk.",
"Default": "Predefinito",
"DefaultLLMModelCreatedDuringOnboarding": "Modello LLM predefinito creato durante la configurazione"
}, },
"login": { "login": {
"email": "Email", "email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "L'indirizzo email {{email}} non è autorizzato a registrarsi.", "signupNotAllowed": "L'indirizzo email {{email}} non è autorizzato a registrarsi.",
"unexpectedError": "Si è verificato un errore imprevisto", "unexpectedError": "Si è verificato un errore imprevisto",
"invalidCredentials": "Email o password non validi.", "invalidCredentials": "Email o password non validi.",
"clearing": "Pulizia dello stato di accesso..." "clearing": "Pulizia dello stato di accesso...",
"sessionExpired": "La tua sessione è scaduta. Registrati o accedi di nuovo."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chat", "defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Seleziona un modello", "selectModel": "Seleziona un modello",
"hosted": "Ospitato", "hosted": "Ospitato",
"advancedSettings": "Impostazioni avanzate", "advancedSettings": "Impostazioni avanzate",
"searchModelsPlaceholder": "Cerca modelli..." "searchModelsPlaceholder": "Cerca modelli...",
"showThinking": "Mostra il processo di pensiero",
"hideThinking": "Nascondi il processo di pensiero"
}, },
"profile": { "profile": {
"settingsTitle": "Impostazioni utente", "settingsTitle": "Impostazioni utente",
@ -283,5 +288,19 @@
"enabled": "Abilitato", "enabled": "Abilitato",
"disabled": "Disabilitato", "disabled": "Disabilitato",
"startNewChat": "Avvia una nuova chat" "startNewChat": "Avvia una nuova chat"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Ricerca dei file in corso...",
"usingTool": "Utilizzo di {{tool}}...",
"saveAndSend": "Salva e invia",
"cancel": "Annulla",
"hideFiles": "Nascondi file",
"retrievalEnabled": "Il recupero dei file è abilitato sui file selezionati per questo messaggio. Clicca sull'indicatore per disattivare.",
"retrievalDisabled": "Clicca sull'indicatore per abilitare il recupero dei file per questo messaggio.",
"viewFiles": "Visualizza {{count}} {{count, plural, one {file} other {file}}}",
"sourceCount": "Numero di fonti:",
"adjustRetrievalSettings": "Regola le impostazioni di recupero.",
"saveAndClose": "Salva e Chiudi"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "戻る", "back": "戻る",
"next": "次へ", "next": "次へ",
"WelcomeToChatDeskUI": "対話工房へようこそ — AIチャット開発のためのフロントエンドプラットフォーム", "WelcomeToChatDeskUI": "対話工房へようこそ — AIチャット開発のためのフロントエンドプラットフォーム",
"ClickNextToStartChatting": "「次へ」をクリックして、対話工房でLLMとのチャットを始めましょう。" "ClickNextToStartChatting": "「次へ」をクリックして、対話工房でLLMとのチャットを始めましょう。",
"Default": "デフォルト",
"DefaultLLMModelCreatedDuringOnboarding": "初期設定時に作成されたデフォルトのLLMモデル"
}, },
"login": { "login": {
"email": "メールアドレス", "email": "メールアドレス",
@ -61,7 +63,8 @@
"signupNotAllowed": "メールアドレス {{email}} は登録できません。", "signupNotAllowed": "メールアドレス {{email}} は登録できません。",
"unexpectedError": "予期しないエラーが発生しました", "unexpectedError": "予期しないエラーが発生しました",
"invalidCredentials": "メールアドレスまたはパスワードが正しくありません。", "invalidCredentials": "メールアドレスまたはパスワードが正しくありません。",
"clearing": "ログイン状態をクリアしています..." "clearing": "ログイン状態をクリアしています...",
"sessionExpired": "セッションの有効期限が切れました。再登録またはログインしてください。"
}, },
"chat": { "chat": {
"defaultChatTitle": "会話", "defaultChatTitle": "会話",
@ -85,7 +88,9 @@
"selectModel": "モデルを選択", "selectModel": "モデルを選択",
"hosted": "ホステッド", "hosted": "ホステッド",
"advancedSettings": "高度な設定", "advancedSettings": "高度な設定",
"searchModelsPlaceholder": "モデルを検索..." "searchModelsPlaceholder": "モデルを検索...",
"showThinking": "思考過程を表示",
"hideThinking": "思考過程を非表示"
}, },
"profile": { "profile": {
"settingsTitle": "ユーザー設定", "settingsTitle": "ユーザー設定",
@ -283,6 +288,20 @@
"enabled": "有効", "enabled": "有効",
"disabled": "無効", "disabled": "無効",
"startNewChat": "新しいチャットを開始" "startNewChat": "新しいチャットを開始"
},
"RAG": {
"prompt": "プロンプト",
"searchingFiles": "ファイルを検索中...",
"usingTool": "{{tool}} を使用中...",
"saveAndSend": "保存して送信",
"cancel": "キャンセル",
"hideFiles": "ファイルを非表示",
"retrievalEnabled": "このメッセージでは選択されたファイルの取得が有効です。無効にするにはインジケーターをクリックしてください。",
"retrievalDisabled": "このメッセージのファイル取得を有効にするには、インジケーターをクリックしてください。",
"viewFiles": "{{count}} 件のファイルを表示",
"sourceCount": "ソース数:",
"adjustRetrievalSettings": "取得設定を調整する。",
"saveAndClose": "保存して閉じる"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "뒤로", "back": "뒤로",
"next": "다음", "next": "다음",
"WelcomeToChatDeskUI": "ChatDesk에 오신 것을 환영합니다. AI 챗 R&D를 위한 프론트엔드 플랫폼입니다.", "WelcomeToChatDeskUI": "ChatDesk에 오신 것을 환영합니다. AI 챗 R&D를 위한 프론트엔드 플랫폼입니다.",
"ClickNextToStartChatting": "'다음'을 클릭하여 ChatDesk를 통해 LLM과의 채팅을 시작하세요." "ClickNextToStartChatting": "'다음'을 클릭하여 ChatDesk를 통해 LLM과의 채팅을 시작하세요.",
"Default": "기본값",
"DefaultLLMModelCreatedDuringOnboarding": "온보딩 중에 생성된 기본 LLM 모델"
}, },
"login": { "login": {
"email": "이메일", "email": "이메일",
@ -61,7 +63,8 @@
"signupNotAllowed": "이메일 {{email}}은(는) 가입할 수 없습니다.", "signupNotAllowed": "이메일 {{email}}은(는) 가입할 수 없습니다.",
"unexpectedError": "예상치 못한 오류가 발생했습니다.", "unexpectedError": "예상치 못한 오류가 발생했습니다.",
"invalidCredentials": "이메일 또는 비밀번호가 올바르지 않습니다.", "invalidCredentials": "이메일 또는 비밀번호가 올바르지 않습니다.",
"clearing": "로그인 상태를 정리하는 중..." "clearing": "로그인 상태를 정리하는 중...",
"sessionExpired": "세션이 만료되었습니다. 다시 가입하거나 로그인해주세요."
}, },
"chat": { "chat": {
"defaultChatTitle": "채팅", "defaultChatTitle": "채팅",
@ -85,7 +88,9 @@
"selectModel": "모델 선택", "selectModel": "모델 선택",
"hosted": "호스팅됨", "hosted": "호스팅됨",
"advancedSettings": "고급 설정", "advancedSettings": "고급 설정",
"searchModelsPlaceholder": "모델 검색..." "searchModelsPlaceholder": "모델 검색...",
"showThinking": "생각 과정을 표시",
"hideThinking": "생각 과정을 숨기기"
}, },
"profile": { "profile": {
"settingsTitle": "사용자 설정", "settingsTitle": "사용자 설정",
@ -283,5 +288,19 @@
"enabled": "활성화됨", "enabled": "활성화됨",
"disabled": "비활성화됨", "disabled": "비활성화됨",
"startNewChat": "새 채팅 시작" "startNewChat": "새 채팅 시작"
},
"RAG": {
"prompt": "프롬프트",
"searchingFiles": "파일 검색 중...",
"usingTool": "{{tool}} 사용 중...",
"saveAndSend": "저장 및 전송",
"cancel": "취소",
"hideFiles": "파일 숨기기",
"retrievalEnabled": "이 메시지에 대해 선택한 파일에서 파일 검색이 활성화되었습니다. 비활성화하려면 표시기를 클릭하세요.",
"retrievalDisabled": "이 메시지에 대해 파일 검색을 활성화하려면 표시기를 클릭하세요.",
"viewFiles": "{{count}}개의 파일 보기",
"sourceCount": "출처 수:",
"adjustRetrievalSettings": "검색 설정 조정.",
"saveAndClose": "저장 후 닫기"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Voltar", "back": "Voltar",
"next": "Próximo", "next": "Próximo",
"WelcomeToChatDeskUI": "Bem-vindo ao ChatDesk, a plataforma frontend para P&D de chat com IA", "WelcomeToChatDeskUI": "Bem-vindo ao ChatDesk, a plataforma frontend para P&D de chat com IA",
"ClickNextToStartChatting": "Clique em 'Próximo' para começar a conversar com seu LLM usando o ChatDesk." "ClickNextToStartChatting": "Clique em 'Próximo' para começar a conversar com seu LLM usando o ChatDesk.",
"Default": "Padrão",
"DefaultLLMModelCreatedDuringOnboarding": "Modelo LLM padrão criado durante a integração"
}, },
"login": { "login": {
"email": "Email", "email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "O email {{email}} não está autorizado a se registrar.", "signupNotAllowed": "O email {{email}} não está autorizado a se registrar.",
"unexpectedError": "Ocorreu um erro inesperado", "unexpectedError": "Ocorreu um erro inesperado",
"invalidCredentials": "Email ou senha inválidos.", "invalidCredentials": "Email ou senha inválidos.",
"clearing": "Limpando o estado de login..." "clearing": "Limpando o estado de login...",
"sessionExpired": "Sua sessão expirou. Por favor, cadastre-se ou faça login novamente."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chat", "defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Selecionar modelo", "selectModel": "Selecionar modelo",
"hosted": "Hospedado", "hosted": "Hospedado",
"advancedSettings": "Configurações Avançadas", "advancedSettings": "Configurações Avançadas",
"searchModelsPlaceholder": "Pesquisar modelos..." "searchModelsPlaceholder": "Pesquisar modelos...",
"showThinking": "Mostrar processo de pensamento",
"hideThinking": "Ocultar processo de pensamento"
}, },
"profile": { "profile": {
"settingsTitle": "Configurações do Usuário", "settingsTitle": "Configurações do Usuário",
@ -283,5 +288,19 @@
"enabled": "Ativado", "enabled": "Ativado",
"disabled": "Desativado", "disabled": "Desativado",
"startNewChat": "Iniciar nova conversa" "startNewChat": "Iniciar nova conversa"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Procurando arquivos...",
"usingTool": "Usando {{tool}}...",
"saveAndSend": "Salvar e Enviar",
"cancel": "Cancelar",
"hideFiles": "Ocultar arquivos",
"retrievalEnabled": "A recuperação de arquivos está ativada nos arquivos selecionados para esta mensagem. Clique no indicador para desativar.",
"retrievalDisabled": "Clique no indicador para ativar a recuperação de arquivos para esta mensagem.",
"viewFiles": "Ver {{count}} {{count, plural, one {arquivo} other {arquivos}}}",
"sourceCount": "Quantidade de fontes:",
"adjustRetrievalSettings": "Ajustar configurações de recuperação.",
"saveAndClose": "Salvar e Fechar"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Назад", "back": "Назад",
"next": "Далее", "next": "Далее",
"WelcomeToChatDeskUI": "Добро пожаловать в ChatDesk — фронтенд-платформу для исследований и разработок в области чат-ИИ", "WelcomeToChatDeskUI": "Добро пожаловать в ChatDesk — фронтенд-платформу для исследований и разработок в области чат-ИИ",
"ClickNextToStartChatting": "Нажмите «Далее», чтобы начать общение с вашей LLM через ChatDesk." "ClickNextToStartChatting": "Нажмите «Далее», чтобы начать общение с вашей LLM через ChatDesk.",
"Default": "По умолчанию",
"DefaultLLMModelCreatedDuringOnboarding": "Модель LLM по умолчанию, созданная во время начальной настройки"
}, },
"login": { "login": {
"email": "Эл. почта", "email": "Эл. почта",
@ -61,7 +63,8 @@
"signupNotAllowed": "Электронная почта {{email}} не разрешена для регистрации.", "signupNotAllowed": "Электронная почта {{email}} не разрешена для регистрации.",
"unexpectedError": "Произошла непредвиденная ошибка", "unexpectedError": "Произошла непредвиденная ошибка",
"invalidCredentials": "Неверный адрес эл. почты или пароль.", "invalidCredentials": "Неверный адрес эл. почты или пароль.",
"clearing": "Очистка состояния входа..." "clearing": "Очистка состояния входа...",
"sessionExpired": "Ваша сессия истекла. Пожалуйста, зарегистрируйтесь или войдите снова."
}, },
"chat": { "chat": {
"defaultChatTitle": "Чат", "defaultChatTitle": "Чат",
@ -85,7 +88,9 @@
"selectModel": "Выбрать модель", "selectModel": "Выбрать модель",
"hosted": "Хостинг", "hosted": "Хостинг",
"advancedSettings": "Дополнительные настройки", "advancedSettings": "Дополнительные настройки",
"searchModelsPlaceholder": "Поиск моделей..." "searchModelsPlaceholder": "Поиск моделей...",
"showThinking": "Показать ход мыслей",
"hideThinking": "Скрыть ход мыслей"
}, },
"profile": { "profile": {
"settingsTitle": "Настройки пользователя", "settingsTitle": "Настройки пользователя",
@ -283,5 +288,19 @@
"enabled": "Включено", "enabled": "Включено",
"disabled": "Отключено", "disabled": "Отключено",
"startNewChat": "Начать новый чат" "startNewChat": "Начать новый чат"
},
"RAG": {
"prompt": "Подсказка",
"searchingFiles": "Поиск файлов...",
"usingTool": "Используется {{tool}}...",
"saveAndSend": "Сохранить и отправить",
"cancel": "Отмена",
"hideFiles": "Скрыть файлы",
"retrievalEnabled": "Извлечение файлов включено для выбранных файлов этого сообщения. Нажмите на индикатор, чтобы отключить.",
"retrievalDisabled": "Нажмите на индикатор, чтобы включить извлечение файлов для этого сообщения.",
"viewFiles": "Просмотреть {{count}} {{count, plural, one {файл} few {файла} many {файлов} other {файла}}}",
"sourceCount": "Количество источников:",
"adjustRetrievalSettings": "Настроить параметры извлечения.",
"saveAndClose": "Сохранить и закрыть"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "ආපසු", "back": "ආපසු",
"next": "ඊළඟ", "next": "ඊළඟ",
"WelcomeToChatDeskUI": "ChatDesk වෙත සාදරයෙන් පිළිගනිමු — AI කථා බස සොයාගැනීම් සහ සංවර්ධනය සඳහා ඉදිරිපස වේදිකාව", "WelcomeToChatDeskUI": "ChatDesk වෙත සාදරයෙන් පිළිගනිමු — AI කථා බස සොයාගැනීම් සහ සංවර්ධනය සඳහා ඉදිරිපස වේදිකාව",
"ClickNextToStartChatting": "ඔබගේ LLM සමඟ ChatDesk භාවිතයෙන් කතා කිරීම ආරම්භ කිරීමට 'ඊළඟ' ක්ලික් කරන්න." "ClickNextToStartChatting": "ඔබගේ LLM සමඟ ChatDesk භාවිතයෙන් කතා කිරීම ආරම්භ කිරීමට 'ඊළඟ' ක්ලික් කරන්න.",
"Default": "පෙරනිමි",
"DefaultLLMModelCreatedDuringOnboarding": "ඇරඹුම් අවස්ථාවේදී නිර්මාණය කරන ලද පෙරනිමි LLM ආදර්ශය"
}, },
"login": { "login": {
"email": "ඊමේල්", "email": "ඊමේල්",
@ -61,7 +63,8 @@
"signupNotAllowed": "{{email}} යන්න ලියාපදිංචි වීමට අවසර නැත.", "signupNotAllowed": "{{email}} යන්න ලියාපදිංචි වීමට අවසර නැත.",
"unexpectedError": "予期 නොකළ දෝෂයක් ඇතිවිය", "unexpectedError": "予期 නොකළ දෝෂයක් ඇතිවිය",
"invalidCredentials": "අවලංගු ඊමේල් හෝ මුරපදයක්.", "invalidCredentials": "අවලංගු ඊමේල් හෝ මුරපදයක්.",
"clearing": "ඇතුල් වීමේ තත්වය මකාදමමින්..." "clearing": "ඇතුල් වීමේ තත්වය මකාදමමින්...",
"sessionExpired": "ඔබගේ සැසිය අවසන් වී ඇත. කරුණාකර නැවත ලියාපදිංචි වන්න හෝ පිවිසෙන්න."
}, },
"chat": { "chat": {
"defaultChatTitle": "කතාබස්", "defaultChatTitle": "කතාබස්",
@ -85,7 +88,9 @@
"selectModel": "මාදිලිය තෝරන්න", "selectModel": "මාදිලිය තෝරන්න",
"hosted": "සත්කාරකකළ", "hosted": "සත්කාරකකළ",
"advancedSettings": "උසස් සැකසුම්", "advancedSettings": "උසස් සැකසුම්",
"searchModelsPlaceholder": "මාදිලි සෙවීම..." "searchModelsPlaceholder": "මාදිලි සෙවීම...",
"showThinking": "සිතීමේ ක්‍රියාවලිය පෙන්වන්න",
"hideThinking": "සිතීමේ ක්‍රියාවලිය සඟවන්න"
}, },
"profile": { "profile": {
"settingsTitle": "පරිශීලක සැකසුම්", "settingsTitle": "පරිශීලක සැකසුම්",
@ -283,5 +288,19 @@
"enabled": "සක්‍රිය", "enabled": "සක්‍රිය",
"disabled": "අක්‍රිය", "disabled": "අක්‍රිය",
"startNewChat": "නව කතාබස් ආරම්භ කරන්න" "startNewChat": "නව කතාබස් ආරම්භ කරන්න"
},
"RAG": {
"prompt": "ප්‍රෝම්ප්ට්",
"searchingFiles": "ගොනු සෙවීම...",
"usingTool": "{{tool}} භාවිතා කරමින්...",
"saveAndSend": "සුරකින්න සහ යවන්න",
"cancel": "අවලංගු කරන්න",
"hideFiles": "ගොනු සඟවන්න",
"retrievalEnabled": "මෙම පණිවිඩය සඳහා තෝරාගත් ගොනු සඳහා ගොනු සෙවීම සක්‍රිය කර ඇත. අක්‍රිය කිරීමට දර්ශකය ක්ලික් කරන්න.",
"retrievalDisabled": "මෙම පණිවිඩය සඳහා ගොනු සෙවීම සක්‍රිය කිරීමට දර්ශකය ක්ලික් කරන්න.",
"viewFiles": "{{count}} {{count, plural, one {ගොනුවක්} other {ගොනු}}} බලන්න",
"sourceCount": "මූලාශ්‍ර ගණන:",
"adjustRetrievalSettings": "ප්‍රතිසාධන සැකසුම් සෙවීම සකසන්න.",
"saveAndClose": "සුරකින්න සහ වසන්න"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Tillbaka", "back": "Tillbaka",
"next": "Nästa", "next": "Nästa",
"WelcomeToChatDeskUI": "Välkommen till ChatDesk frontendlösningen för AI-chattforskning och utveckling", "WelcomeToChatDeskUI": "Välkommen till ChatDesk frontendlösningen för AI-chattforskning och utveckling",
"ClickNextToStartChatting": "Klicka på 'Nästa' för att börja chatta med din LLM via ChatDesk." "ClickNextToStartChatting": "Klicka på 'Nästa' för att börja chatta med din LLM via ChatDesk.",
"Default": "Standard",
"DefaultLLMModelCreatedDuringOnboarding": "Standard LLM-modell skapad under introduktionen"
}, },
"login": { "login": {
"email": "E-post", "email": "E-post",
@ -61,7 +63,8 @@
"signupNotAllowed": "E-postadressen {{email}} är inte tillåten för registrering.", "signupNotAllowed": "E-postadressen {{email}} är inte tillåten för registrering.",
"unexpectedError": "Ett oväntat fel inträffade", "unexpectedError": "Ett oväntat fel inträffade",
"invalidCredentials": "Ogiltig e-postadress eller lösenord.", "invalidCredentials": "Ogiltig e-postadress eller lösenord.",
"clearing": "Rensar inloggningsstatus..." "clearing": "Rensar inloggningsstatus...",
"sessionExpired": "Din session har gått ut. Registrera dig eller logga in igen."
}, },
"chat": { "chat": {
"defaultChatTitle": "Chatt", "defaultChatTitle": "Chatt",
@ -85,7 +88,9 @@
"selectModel": "Välj en modell", "selectModel": "Välj en modell",
"hosted": "Hostad", "hosted": "Hostad",
"advancedSettings": "Avancerade inställningar", "advancedSettings": "Avancerade inställningar",
"searchModelsPlaceholder": "Sök modeller..." "searchModelsPlaceholder": "Sök modeller...",
"showThinking": "Visa tankegång",
"hideThinking": "Dölj tankegång"
}, },
"profile": { "profile": {
"settingsTitle": "Användarinställningar", "settingsTitle": "Användarinställningar",
@ -283,5 +288,19 @@
"enabled": "Aktiverad", "enabled": "Aktiverad",
"disabled": "Avaktiverad", "disabled": "Avaktiverad",
"startNewChat": "Starta ny chatt" "startNewChat": "Starta ny chatt"
},
"RAG": {
"prompt": "Prompt",
"searchingFiles": "Söker efter filer...",
"usingTool": "Använder {{tool}}...",
"saveAndSend": "Spara och skicka",
"cancel": "Avbryt",
"hideFiles": "Dölj filer",
"retrievalEnabled": "Filhämtning är aktiverat för de valda filerna för detta meddelande. Klicka på indikatorn för att inaktivera.",
"retrievalDisabled": "Klicka på indikatorn för att aktivera filhämtning för detta meddelande.",
"viewFiles": "Visa {{count}} {{count, plural, one {fil} other {filer}}}",
"sourceCount": "Antal källor:",
"adjustRetrievalSettings": "Justera hämtningsinställningar.",
"saveAndClose": "Spara och stäng"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "వెనక్కి", "back": "వెనక్కి",
"next": "తరువాత", "next": "తరువాత",
"WelcomeToChatDeskUI": "ChatDesk కు స్వాగతం — ఇది AI చాట్ R&D కోసం ముందు భాగం ప్లాట్‌ఫారమ్", "WelcomeToChatDeskUI": "ChatDesk కు స్వాగతం — ఇది AI చాట్ R&D కోసం ముందు భాగం ప్లాట్‌ఫారమ్",
"ClickNextToStartChatting": "'తరువాత' క్లిక్ చేసి ChatDesk ద్వారా మీ LLMతో చాట్ చేయడం ప్రారంభించండి." "ClickNextToStartChatting": "'తరువాత' క్లిక్ చేసి ChatDesk ద్వారా మీ LLMతో చాట్ చేయడం ప్రారంభించండి.",
"Default": "డీఫాల్ట్",
"DefaultLLMModelCreatedDuringOnboarding": "ఆన్బోర్డింగ్ సమయంలో సృష్టించబడిన డీఫాల్ట్ LLM మోడల్"
}, },
"login": { "login": {
"email": "ఈమెయిల్", "email": "ఈమెయిల్",
@ -61,7 +63,8 @@
"signupNotAllowed": "{{email}} చిరునామా నమోదు చేసుకోడానికి అనుమతించబడలేదు.", "signupNotAllowed": "{{email}} చిరునామా నమోదు చేసుకోడానికి అనుమతించబడలేదు.",
"unexpectedError": "అనుకోని లోపం సంభవించింది", "unexpectedError": "అనుకోని లోపం సంభవించింది",
"invalidCredentials": "చెల్లని ఈమెయిల్ లేదా పాస్వర్డ్.", "invalidCredentials": "చెల్లని ఈమెయిల్ లేదా పాస్వర్డ్.",
"clearing": "లాగిన్ స్థితిని క్లియర్ చేస్తోంది..." "clearing": "లాగిన్ స్థితిని క్లియర్ చేస్తోంది...",
"sessionExpired": "మీ సెషన్ గడువు ముగిసింది. దయచేసి మళ్లీ నమోదు చేసుకోండి లేదా లాగిన్ అవ్వండి."
}, },
"chat": { "chat": {
"defaultChatTitle": "చాట్", "defaultChatTitle": "చాట్",
@ -85,7 +88,9 @@
"selectModel": "ఒక మోడల్‌ను ఎంచుకోండి", "selectModel": "ఒక మోడల్‌ను ఎంచుకోండి",
"hosted": "హోస్ట్ చేయబడింది", "hosted": "హోస్ట్ చేయబడింది",
"advancedSettings": "అధునాతన సెట్టింగులు", "advancedSettings": "అధునాతన సెట్టింగులు",
"searchModelsPlaceholder": "మోడల్స్ శోధించండి..." "searchModelsPlaceholder": "మోడల్స్ శోధించండి...",
"showThinking": "ఆలోచనా ప్రక్రియ చూపించు",
"hideThinking": "ఆలోచనా ప్రక్రియ దాచు"
}, },
"profile": { "profile": {
"settingsTitle": "వినియోగదారు సెట్టింగులు", "settingsTitle": "వినియోగదారు సెట్టింగులు",
@ -283,5 +288,19 @@
"enabled": "ప్రదర్శించబడింది", "enabled": "ప్రదర్శించబడింది",
"disabled": "అచేతనం", "disabled": "అచేతనం",
"startNewChat": "కొత్త చాట్ ప్రారంభించు" "startNewChat": "కొత్త చాట్ ప్రారంభించు"
},
"RAG": {
"prompt": "ప్రాంప్ట్",
"searchingFiles": "ఫైళ్లను శోధిస్తోంది...",
"usingTool": "{{tool}} ఉపయోగిస్తోంది...",
"saveAndSend": "సేవ్ చేసి పంపు",
"cancel": "రద్దు చేయి",
"hideFiles": "ఫైళ్లను దాచు",
"retrievalEnabled": "ఈ సందేశానికి ఎంపిక చేసిన ఫైళ్లపై ఫైల్ రిట్రీవల్ ప్రారంభించబడింది. నిలిపివేయడానికి సూచికపై క్లిక్ చేయండి.",
"retrievalDisabled": "ఈ సందేశానికి ఫైల్ రిట్రీవల్ ప్రారంభించడానికి సూచికపై క్లిక్ చేయండి.",
"viewFiles": "{{count}} {{count, plural, one {ఫైల్} other {ఫైల్స్}}} చూపించు",
"sourceCount": "మూలాల సంఖ్య:",
"adjustRetrievalSettings": "రిట్రీవల్ సెట్టింగులను సర్దుబాటు చేయండి.",
"saveAndClose": "సేవ్ చేసి మూసివేయండి"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "Quay lại", "back": "Quay lại",
"next": "Tiếp theo", "next": "Tiếp theo",
"WelcomeToChatDeskUI": "Chào mừng đến với ChatDesk nền tảng giao diện dành cho nghiên cứu và phát triển trò chuyện AI", "WelcomeToChatDeskUI": "Chào mừng đến với ChatDesk nền tảng giao diện dành cho nghiên cứu và phát triển trò chuyện AI",
"ClickNextToStartChatting": "Nhấn 'Tiếp theo' để bắt đầu trò chuyện với LLM của bạn qua ChatDesk." "ClickNextToStartChatting": "Nhấn 'Tiếp theo' để bắt đầu trò chuyện với LLM của bạn qua ChatDesk.",
"Default": "Mặc định",
"DefaultLLMModelCreatedDuringOnboarding": "Mô hình LLM mặc định được tạo trong quá trình thiết lập"
}, },
"login": { "login": {
"email": "Email", "email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} không được phép đăng ký.", "signupNotAllowed": "Email {{email}} không được phép đăng ký.",
"unexpectedError": "Đã xảy ra lỗi không mong muốn", "unexpectedError": "Đã xảy ra lỗi không mong muốn",
"invalidCredentials": "Email hoặc mật khẩu không hợp lệ.", "invalidCredentials": "Email hoặc mật khẩu không hợp lệ.",
"clearing": "Đang xóa trạng thái đăng nhập..." "clearing": "Đang xóa trạng thái đăng nhập...",
"sessionExpired": "Phiên của bạn đã hết hạn. Vui lòng đăng ký hoặc đăng nhập lại."
}, },
"chat": { "chat": {
"defaultChatTitle": "Trò chuyện", "defaultChatTitle": "Trò chuyện",
@ -85,7 +88,9 @@
"selectModel": "Chọn một mô hình", "selectModel": "Chọn một mô hình",
"hosted": "Được lưu trữ", "hosted": "Được lưu trữ",
"advancedSettings": "Cài đặt nâng cao", "advancedSettings": "Cài đặt nâng cao",
"searchModelsPlaceholder": "Tìm kiếm mô hình..." "searchModelsPlaceholder": "Tìm kiếm mô hình...",
"showThinking": "Hiển thị quá trình suy nghĩ",
"hideThinking": "Ẩn quá trình suy nghĩ"
}, },
"profile": { "profile": {
"settingsTitle": "Cài đặt người dùng", "settingsTitle": "Cài đặt người dùng",
@ -283,5 +288,19 @@
"enabled": "Đã bật", "enabled": "Đã bật",
"disabled": "Đã tắt", "disabled": "Đã tắt",
"startNewChat": "Bắt đầu cuộc trò chuyện mới" "startNewChat": "Bắt đầu cuộc trò chuyện mới"
},
"RAG": {
"prompt": "Gợi ý",
"searchingFiles": "Đang tìm kiếm tệp...",
"usingTool": "Đang sử dụng {{tool}}...",
"saveAndSend": "Lưu và gửi",
"cancel": "Hủy",
"hideFiles": "Ẩn tệp",
"retrievalEnabled": "Truy xuất tệp đã được bật cho các tệp đã chọn trong tin nhắn này. Nhấn vào biểu tượng để tắt.",
"retrievalDisabled": "Nhấn vào biểu tượng để bật truy xuất tệp cho tin nhắn này.",
"viewFiles": "Xem {{count}} tệp",
"sourceCount": "Số lượng nguồn:",
"adjustRetrievalSettings": "Điều chỉnh cài đặt truy xuất.",
"saveAndClose": "Lưu và Đóng"
} }
} }

View File

@ -36,7 +36,9 @@
"back": "返回", "back": "返回",
"next": "下一步", "next": "下一步",
"WelcomeToChatDeskUI": "欢迎使用 对话工坊 —— 面向 AI 对话研发的前端平台", "WelcomeToChatDeskUI": "欢迎使用 对话工坊 —— 面向 AI 对话研发的前端平台",
"ClickNextToStartChatting": "点击“下一步”,开始使用对话工坊与您的 LLM 进行对话。" "ClickNextToStartChatting": "点击“下一步”,开始使用对话工坊与您的 LLM 进行对话。",
"Default": "默认",
"DefaultLLMModelCreatedDuringOnboarding": "在初始化过程中创建的默认LLM模型"
}, },
"login": { "login": {
"email": "电子邮件", "email": "电子邮件",
@ -61,7 +63,8 @@
"signupNotAllowed": "邮箱 {{email}} 不允许注册。", "signupNotAllowed": "邮箱 {{email}} 不允许注册。",
"unexpectedError": "发生了未知错误", "unexpectedError": "发生了未知错误",
"invalidCredentials": "邮箱或密码错误。", "invalidCredentials": "邮箱或密码错误。",
"clearing": "正在清除登录状态..." "clearing": "正在清除登录状态...",
"sessionExpired": "您的会话已过期,请重新注册或登录。"
}, },
"chat": { "chat": {
"defaultChatTitle": "对话", "defaultChatTitle": "对话",
@ -85,7 +88,9 @@
"selectModel": "选择一个模型", "selectModel": "选择一个模型",
"hosted": "托管的", "hosted": "托管的",
"advancedSettings": "高级设置", "advancedSettings": "高级设置",
"searchModelsPlaceholder": "搜索模型..." "searchModelsPlaceholder": "搜索模型...",
"showThinking": "显示思考过程",
"hideThinking": "隐藏思考过程"
}, },
"profile": { "profile": {
"settingsTitle": "用户设置", "settingsTitle": "用户设置",
@ -283,5 +288,19 @@
"enabled": "启用", "enabled": "启用",
"disabled": "禁用", "disabled": "禁用",
"startNewChat": "开始新的对话" "startNewChat": "开始新的对话"
},
"RAG": {
"prompt": "提示词",
"searchingFiles": "正在搜索文件...",
"usingTool": "正在使用 {{tool}}...",
"saveAndSend": "保存并发送",
"cancel": "取消",
"hideFiles": "隐藏文件",
"retrievalEnabled": "当前消息启用了所选文件的检索。点击图标以禁用。",
"retrievalDisabled": "点击图标以启用此消息的文件检索。",
"viewFiles": "查看 {{count}} 个文件",
"sourceCount": "来源数量:",
"adjustRetrievalSettings": "调整检索设置。",
"saveAndClose": "保存并关闭"
} }
} }

View File

@ -50,8 +50,8 @@ LANGUAGE 'plpgsql'
SECURITY DEFINER SECURITY DEFINER
AS $$ AS $$
DECLARE DECLARE
project_url TEXT := 'http://supabase_kong_chatbotui:8000'; project_url TEXT := 'http://localhost:8000';
service_role_key TEXT := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'; -- full access needed for http request to storage service_role_key TEXT := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UtZGVtbyIsImV4cCI6MjA2MjkyNTU2NSwiaWF0IjoxNzQ3NTY1NTY1fQ.18Lxnd9JrkNyV9q38l_8oQB8pwtZK8JwpLwpH2b4JaA'; -- full access needed for http request to storage
url TEXT := project_url || '/storage/v1/object/' || bucket || '/' || object; url TEXT := project_url || '/storage/v1/object/' || bucket || '/' || object;
BEGIN BEGIN
SELECT SELECT

View File

@ -126,11 +126,11 @@ BEGIN
TRUE, TRUE,
'Home', 'Home',
4096, 4096,
'gpt-4-1106-preview', 'GPT',
'You are a friendly, helpful AI assistant.', 'You are a friendly, helpful AI assistant.',
0.5, 0.5,
'My home workspace.', 'My home workspace.',
'openai', 'bge-m3',
TRUE, TRUE,
TRUE, TRUE,
'' ''

View File

@ -19,6 +19,7 @@ create table file_items (
content TEXT NOT NULL, content TEXT NOT NULL,
local_embedding vector(384), -- 384 works for local w/ Xenova/all-MiniLM-L6-v2 local_embedding vector(384), -- 384 works for local w/ Xenova/all-MiniLM-L6-v2
openai_embedding vector(1536), -- 1536 for OpenAI openai_embedding vector(1536), -- 1536 for OpenAI
bge_m3_embedding vector(1024), -- 1024 for BGE-M3
tokens INT NOT NULL tokens INT NOT NULL
); );
@ -32,6 +33,9 @@ CREATE INDEX file_items_embedding_idx ON file_items
CREATE INDEX file_items_local_embedding_idx ON file_items CREATE INDEX file_items_local_embedding_idx ON file_items
USING hnsw (local_embedding vector_cosine_ops); USING hnsw (local_embedding vector_cosine_ops);
CREATE INDEX file_items_bge_m3_embedding_idx ON file_items
USING hnsw (bge_m3_embedding vector_cosine_ops);
-- RLS -- RLS
ALTER TABLE file_items ENABLE ROW LEVEL SECURITY; ALTER TABLE file_items ENABLE ROW LEVEL SECURITY;
@ -114,3 +118,64 @@ begin
limit match_count; limit match_count;
end; end;
$$; $$;
-- create function match_file_items_bge_m3 (
-- query_embedding vector(1024),
-- match_count int DEFAULT null,
-- file_ids UUID[] DEFAULT null
-- ) returns table (
-- id UUID,
-- file_id UUID,
-- content TEXT,
-- tokens INT,
-- similarity float
-- )
-- language plpgsql
-- as $$
-- #variable_conflict use_column
-- begin
-- return query
-- select
-- id,
-- file_id,
-- content,
-- tokens,
-- 1 - (file_items.bge_m3_embedding <=> query_embedding) as similarity
-- from file_items
-- where (file_id = ANY(file_ids))
-- order by file_items.bge_m3_embedding <=> query_embedding
-- limit match_count;
-- end;
-- $$;
create function match_file_items_bge_m3 (
query_embedding vector(1024),
match_count int DEFAULT null,
file_ids UUID[] DEFAULT null
) returns table (
id UUID,
file_id UUID,
content TEXT,
tokens INT,
similarity float
)
language plpgsql
as $$
#variable_conflict use_column
begin
-- 显式设置 schema 搜索路径
SET LOCAL search_path = extensions, public;
return query
select
id,
file_id,
content,
tokens,
1 - (file_items.bge_m3_embedding <=> query_embedding) as similarity
from file_items
where file_id = ANY(file_ids)
order by file_items.bge_m3_embedding <=> query_embedding
limit match_count;
end;
$$;

View File

@ -1,7 +1,7 @@
-- WORKSPACES -- WORKSPACES
UPDATE workspaces UPDATE workspaces
SET default_model = 'gpt-4-turbo-preview' SET default_model = 'GPT'
WHERE default_model = 'gpt-4-1106-preview'; WHERE default_model = 'gpt-4-1106-preview';
UPDATE workspaces UPDATE workspaces
@ -11,7 +11,7 @@ WHERE default_model = 'gpt-3.5-turbo-1106';
-- PRESETS -- PRESETS
UPDATE presets UPDATE presets
SET model = 'gpt-4-turbo-preview' SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview'; WHERE model = 'gpt-4-1106-preview';
UPDATE presets UPDATE presets
@ -21,7 +21,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- ASSISTANTS -- ASSISTANTS
UPDATE assistants UPDATE assistants
SET model = 'gpt-4-turbo-preview' SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview'; WHERE model = 'gpt-4-1106-preview';
UPDATE assistants UPDATE assistants
@ -31,7 +31,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- CHATS -- CHATS
UPDATE chats UPDATE chats
SET model = 'gpt-4-turbo-preview' SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview'; WHERE model = 'gpt-4-1106-preview';
UPDATE chats UPDATE chats
@ -41,7 +41,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- MESSAGES -- MESSAGES
UPDATE messages UPDATE messages
SET model = 'gpt-4-turbo-preview' SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview'; WHERE model = 'gpt-4-1106-preview';
UPDATE messages UPDATE messages
@ -93,11 +93,11 @@ BEGIN
TRUE, TRUE,
'Home', 'Home',
4096, 4096,
'gpt-4-turbo-preview', -- Updated default model 'GPT', -- Updated default model
'You are a friendly, helpful AI assistant.', 'You are a friendly, helpful AI assistant.',
0.5, 0.5,
'My home workspace.', 'My home workspace.',
'openai', 'bge-m3',
TRUE, TRUE,
TRUE, TRUE,
'' ''

View File

@ -1348,6 +1348,20 @@ export type Database = {
similarity: number similarity: number
}[] }[]
} }
match_file_items_bge_m3: {
Args: {
query_embedding: string
match_count?: number
file_ids?: string[]
}
Returns: {
id: string
file_id: string
content: string
tokens: number
similarity: number
}[]
}
non_private_assistant_exists: { non_private_assistant_exists: {
Args: { Args: {
p_name: string p_name: string

View File

@ -8,7 +8,7 @@ export interface ChatSettings {
contextLength: number contextLength: number
includeProfileContext: boolean includeProfileContext: boolean
includeWorkspaceInstructions: boolean includeWorkspaceInstructions: boolean
embeddingsProvider: "openai" | "local" embeddingsProvider: "openai" | "local" | "bge-m3"
} }
export interface ChatPayload { export interface ChatPayload {

View File

@ -10,6 +10,7 @@ export type LLMID =
// OpenAI Models (UPDATED 5/13/24) // OpenAI Models (UPDATED 5/13/24)
export type OpenAILLMID = export type OpenAILLMID =
| "GPT" // API compatiable
| "gpt-4o" // GPT-4o | "gpt-4o" // GPT-4o
| "gpt-4-turbo-preview" // GPT-4 Turbo | "gpt-4-turbo-preview" // GPT-4 Turbo
| "gpt-4-vision-preview" // GPT-4 Vision | "gpt-4-vision-preview" // GPT-4 Vision

View File

@ -1,175 +1,248 @@
-- ✅ 创建 schema (幂等)
DO $$ DO $$
BEGIN BEGIN
IF NOT EXISTS(SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'storage') THEN IF NOT EXISTS (
SELECT schema_name FROM information_schema.schemata
WHERE schema_name = 'storage'
) THEN
CREATE SCHEMA storage; CREATE SCHEMA storage;
END IF; END IF;
END$$; END$$;
-- ✅ 创建角色和默认权限(幂等)
DO $$ DO $$
DECLARE DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true'); install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
anon_role text = COALESCE(current_setting('storage.anon_role', true), 'anon'); anon_role text := COALESCE(current_setting('storage.anon_role', true), 'anon');
authenticated_role text = COALESCE(current_setting('storage.authenticated_role', true), 'authenticated'); authenticated_role text := COALESCE(current_setting('storage.authenticated_role', true), 'authenticated');
service_role text = COALESCE(current_setting('storage.service_role', true), 'service_role'); service_role text := COALESCE(current_setting('storage.service_role', true), 'service_role');
BEGIN BEGIN
IF install_roles != 'true' THEN IF install_roles != 'true' THEN RETURN; END IF;
RETURN;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = anon_role) THEN
EXECUTE 'CREATE ROLE ' || quote_ident(anon_role) || ' NOLOGIN NOINHERIT';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = authenticated_role) THEN
EXECUTE 'CREATE ROLE ' || quote_ident(authenticated_role) || ' NOLOGIN NOINHERIT';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = service_role) THEN
EXECUTE 'CREATE ROLE ' || quote_ident(service_role) || ' NOLOGIN NOINHERIT BYPASSRLS';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticator') THEN
CREATE USER authenticator NOINHERIT;
END IF; END IF;
-- Install ROLES EXECUTE 'GRANT ' || quote_ident(anon_role) || ',' ||
EXECUTE 'CREATE ROLE ' || anon_role || ' NOLOGIN NOINHERIT'; quote_ident(authenticated_role) || ',' ||
EXECUTE 'CREATE ROLE ' || authenticated_role || ' NOLOGIN NOINHERIT'; quote_ident(service_role) || ' TO authenticator';
EXECUTE 'CREATE ROLE ' || service_role || ' NOLOGIN NOINHERIT bypassrls';
create user authenticator noinherit; EXECUTE 'GRANT USAGE ON SCHEMA storage TO ' ||
EXECUTE 'grant ' || anon_role || ' to authenticator'; quote_ident(anon_role) || ',' ||
EXECUTE 'grant ' || authenticated_role || ' to authenticator'; quote_ident(authenticated_role) || ',' ||
EXECUTE 'grant ' || service_role || ' to authenticator'; quote_ident(service_role);
grant postgres to authenticator;
EXECUTE 'grant usage on schema storage to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON TABLES TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
EXECUTE 'alter default privileges in schema storage grant all on tables to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON FUNCTIONS TO ' ||
EXECUTE 'alter default privileges in schema storage grant all on functions to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
EXECUTE 'alter default privileges in schema storage grant all on sequences to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON SEQUENCES TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
END$$; END$$;
-- ✅ 创建 migrations 表(幂等)
CREATE TABLE IF NOT EXISTS "storage"."migrations" ( CREATE TABLE IF NOT EXISTS storage.migrations (
id integer PRIMARY KEY, id integer PRIMARY KEY,
name varchar(100) UNIQUE NOT NULL, name varchar(100) UNIQUE NOT NULL,
hash varchar(40) NOT NULL, -- sha1 hex encoded hash of the file name and contents, to ensure it hasn't been altered since applying the migration hash varchar(40) NOT NULL,
executed_at timestamp DEFAULT current_timestamp executed_at timestamp DEFAULT current_timestamp
); );
CREATE TABLE IF NOT EXISTS "storage"."buckets" ( -- ✅ 创建 buckets 表(不含 public 字段)
"id" text not NULL, CREATE TABLE IF NOT EXISTS storage.buckets (
"name" text NOT NULL, id text NOT NULL,
"owner" uuid, name text NOT NULL,
"created_at" timestamptz DEFAULT now(), owner uuid,
"updated_at" timestamptz DEFAULT now(), created_at timestamptz DEFAULT now(),
PRIMARY KEY ("id") updated_at timestamptz DEFAULT now(),
PRIMARY KEY (id)
); );
CREATE UNIQUE INDEX IF NOT EXISTS "bname" ON "storage"."buckets" USING BTREE ("name"); CREATE UNIQUE INDEX IF NOT EXISTS bname ON storage.buckets (name);
CREATE TABLE IF NOT EXISTS "storage"."objects" ( -- ✅ 补 public 字段(兼容升级)
"id" uuid NOT NULL DEFAULT gen_random_uuid(), DO $$
"bucket_id" text, BEGIN
"name" text, IF NOT EXISTS (
"owner" uuid, SELECT 1 FROM information_schema.columns
"created_at" timestamptz DEFAULT now(), WHERE table_schema = 'storage'
"updated_at" timestamptz DEFAULT now(), AND table_name = 'buckets'
"last_accessed_at" timestamptz DEFAULT now(), AND column_name = 'public'
"metadata" jsonb, ) THEN
CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"), ALTER TABLE storage.buckets
PRIMARY KEY ("id") ADD COLUMN public boolean NOT NULL DEFAULT false;
END IF;
END$$;
-- ✅ 创建 objects 表
CREATE TABLE IF NOT EXISTS storage.objects (
id uuid NOT NULL DEFAULT gen_random_uuid(),
bucket_id text,
name text,
owner uuid,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now(),
last_accessed_at timestamptz DEFAULT now(),
metadata jsonb,
CONSTRAINT objects_bucketId_fkey FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id),
PRIMARY KEY (id)
); );
CREATE UNIQUE INDEX IF NOT EXISTS "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name"); CREATE UNIQUE INDEX IF NOT EXISTS bucketid_objname ON storage.objects (bucket_id, name);
CREATE INDEX IF NOT EXISTS name_prefix_search ON storage.objects(name text_pattern_ops); CREATE INDEX IF NOT EXISTS name_prefix_search ON storage.objects (name text_pattern_ops);
-- ✅ 启用 RLS
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
drop function if exists storage.foldername; -- ✅ 内置函数(可重用)
CREATE OR REPLACE FUNCTION storage.foldername(name text) CREATE OR REPLACE FUNCTION storage.foldername(name text)
RETURNS text[] RETURNS text[] LANGUAGE plpgsql AS $$
LANGUAGE plpgsql DECLARE _parts text[];
AS $function$
DECLARE
_parts text[];
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
return _parts[1:array_length(_parts,1)-1]; RETURN _parts[1:array_length(_parts,1)-1];
END END$$;
$function$;
drop function if exists storage.filename;
CREATE OR REPLACE FUNCTION storage.filename(name text) CREATE OR REPLACE FUNCTION storage.filename(name text)
RETURNS text RETURNS text LANGUAGE plpgsql AS $$
LANGUAGE plpgsql DECLARE _parts text[];
AS $function$
DECLARE
_parts text[];
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
return _parts[array_length(_parts,1)]; RETURN _parts[array_length(_parts,1)];
END END$$;
$function$;
drop function if exists storage.extension;
CREATE OR REPLACE FUNCTION storage.extension(name text) CREATE OR REPLACE FUNCTION storage.extension(name text)
RETURNS text RETURNS text LANGUAGE plpgsql AS $$
LANGUAGE plpgsql DECLARE _parts text[]; _filename text;
AS $function$
DECLARE
_parts text[];
_filename text;
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
select _parts[array_length(_parts,1)] into _filename; SELECT _parts[array_length(_parts,1)] INTO _filename;
-- @todo return the last part instead of 2 RETURN reverse(split_part(reverse(_filename), '.', 1));
return reverse(split_part(reverse(_filename), '.', 1)); END$$;
END
$function$;
-- @todo can this query be optimised further? -- ✅ 文件夹搜索函数(用于前端分层目录浏览)
drop function if exists storage.search; CREATE OR REPLACE FUNCTION storage.search(
CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0) prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0
)
RETURNS TABLE ( RETURNS TABLE (
name text, name text,
id uuid, id uuid,
updated_at TIMESTAMPTZ, updated_at timestamptz,
created_at TIMESTAMPTZ, created_at timestamptz,
last_accessed_at TIMESTAMPTZ, last_accessed_at timestamptz,
metadata jsonb metadata jsonb
) )
LANGUAGE plpgsql LANGUAGE plpgsql AS $$
AS $function$
BEGIN BEGIN
return query RETURN QUERY
with files_folders as ( WITH files_folders AS (
select ((string_to_array(objects.name, '/'))[levels]) as folder SELECT (string_to_array(objects.name, '/'))[levels] AS folder
from objects FROM storage.objects
where objects.name ilike prefix || '%' WHERE objects.name ILIKE prefix || '%'
and bucket_id = bucketname AND bucket_id = bucketname
GROUP by folder GROUP BY folder
limit limits LIMIT limits OFFSET offsets
offset offsets )
) SELECT
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders files_folders.folder AS name,
left join objects objects.id,
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname; objects.updated_at,
END objects.created_at,
$function$; objects.last_accessed_at,
objects.metadata
FROM files_folders
LEFT JOIN storage.objects
ON prefix || files_folders.folder = objects.name
AND objects.bucket_id = bucketname;
END$$;
-- ✅ 管理员角色创建(幂等)
DO $$ DO $$
DECLARE DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true'); install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
super_user text = COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin'); super_user text := COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin');
BEGIN BEGIN
IF install_roles != 'true' THEN IF install_roles != 'true' THEN RETURN; END IF;
RETURN;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = super_user) THEN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = super_user) THEN
EXECUTE 'CREATE USER ' || super_user || ' NOINHERIT CREATEROLE LOGIN NOREPLICATION'; EXECUTE 'CREATE ROLE ' || quote_ident(super_user) || ' NOINHERIT CREATEROLE LOGIN NOREPLICATION';
END IF; END IF;
-- Grant privileges to Super User EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || super_user; EXECUTE 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO ' || super_user; EXECUTE 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO ' || super_user;
IF super_user != 'postgres' THEN IF super_user != 'postgres' THEN
EXECUTE 'ALTER USER ' || super_user || ' SET search_path = "storage"'; EXECUTE 'ALTER ROLE ' || quote_ident(super_user) || ' SET search_path = "storage"';
END IF; END IF;
EXECUTE 'ALTER table "storage".objects owner to ' || super_user; EXECUTE 'ALTER TABLE storage.objects OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER table "storage".buckets owner to ' || super_user; EXECUTE 'ALTER TABLE storage.buckets OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER table "storage".migrations OWNER TO ' || super_user; EXECUTE 'ALTER TABLE storage.migrations OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".foldername(text) owner to ' || super_user;
EXECUTE 'ALTER function "storage".filename(text) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.foldername(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".extension(text) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.filename(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".search(text,text,int,int,int) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.extension(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER FUNCTION storage.search(text,text,int,int,int) OWNER TO ' || quote_ident(super_user);
END$$;
-- ✅ 插入默认 bucket公开访问用于头像
INSERT INTO storage.buckets (id, name, owner, public)
VALUES ('profile_images', 'profile_images', NULL, true)
ON CONFLICT (id) DO UPDATE SET public = true;
-- ✅ RLS 策略定义(幂等)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon upload profile_images'
) THEN
EXECUTE 'CREATE POLICY "anon upload profile_images"
ON storage.objects
FOR INSERT
TO anon
WITH CHECK (bucket_id = ''profile_images'')';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon read profile_images'
) THEN
EXECUTE 'CREATE POLICY "anon read profile_images"
ON storage.objects
FOR SELECT
TO anon
USING (bucket_id = ''profile_images'')';
END IF;
END$$;
-- ✅ RLS 策略定义files bucket幂等
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon upload files'
) THEN
EXECUTE 'CREATE POLICY "anon upload files"
ON storage.objects
FOR INSERT
TO anon
WITH CHECK (bucket_id = ''files'')';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon read files'
) THEN
EXECUTE 'CREATE POLICY "anon read files"
ON storage.objects
FOR SELECT
TO anon
USING (bucket_id = ''files'')';
END IF;
END$$; END$$;

View File

@ -17,22 +17,27 @@ serverurl=unix:///var/run/supervisor.sock
[program:postgres] [program:postgres]
command=/bin/bash /supabase/postgres/wrapper.sh command=/bin/bash /supabase/postgres/wrapper.sh
priority=10
user=root user=root
autorestart=true autorestart=true
stdout_logfile=/var/log/postgres.out.log stdout_logfile=/var/log/postgres.out.log
stderr_logfile=/var/log/postgres.err.log stderr_logfile=/var/log/postgres.err.log
environment=POSTGRES_PASSWORD="postgres" environment=POSTGRES_PASSWORD="postgres"
[program:postgrest] [program:postgrest]
command=/bin/bash /supabase/postgrest/wrapper.sh command=/bin/bash /supabase/postgrest/wrapper.sh
priority=20
autorestart=true autorestart=true
user=root user=root
stderr_logfile=/var/log/postgrest.err.log stderr_logfile=/var/log/postgrest.err.log
stdout_logfile=/var/log/postgrest.out.log stdout_logfile=/var/log/postgrest.out.log
environment=PGRST_DB_URI="postgres://postgres:postgres@localhost:5432/postgres",PGRST_DB_ANON_ROLE="anon",PGRST_DB_SCHEMA="public",PGRST_JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long",PGRST_SERVER_PORT="3000",PGRST_LOG_LEVEL="debug" environment=PGRST_DB_URI="postgres://postgres:postgres@localhost:5432/postgres",PGRST_DB_ANON_ROLE="anon",PGRST_DB_SCHEMA="public",PGRST_JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long",PGRST_SERVER_PORT="3000",PGRST_LOG_LEVEL="debug"
[program:auth] [program:auth]
command=/bin/bash /supabase/gotrue/wrapper.sh command=/bin/bash /supabase/gotrue/wrapper.sh
priority=40
autorestart=true autorestart=true
user=1000 user=1000
stderr_logfile=/var/log/auth.err.log stderr_logfile=/var/log/auth.err.log
@ -41,12 +46,16 @@ stdout_logfile=/var/log/auth.out.log
[program:storage-api] [program:storage-api]
directory=/supabase/storage-api directory=/supabase/storage-api
command=/bin/bash /supabase/storage-api/wrapper.sh command=/bin/bash /supabase/storage-api/wrapper.sh
priority=50
autorestart=true autorestart=true
stderr_logfile=/var/log/storage-api.err.log stderr_logfile=/var/log/storage-api.err.log
stdout_logfile=/var/log/storage-api.out.log stdout_logfile=/var/log/storage-api.out.log
environment=MULTI_TENANT="false"
[program:kong] [program:kong]
command=/bin/bash /supabase/kong/wrapper.sh command=/bin/bash /supabase/kong/wrapper.sh
priority=30
user=root user=root
autorestart=true autorestart=true
redirect_stderr=true redirect_stderr=true
@ -55,6 +64,7 @@ stdout_logfile=auto
[program:chatdesk-ui] [program:chatdesk-ui]
command=/bin/bash /supabase/chatdesk/wrapper.sh command=/bin/bash /supabase/chatdesk/wrapper.sh
priority=60
user=root user=root
autorestart=true autorestart=true
stderr_logfile=/var/log/chatdesk-ui.err.log stderr_logfile=/var/log/chatdesk-ui.err.log

View File

@ -6,11 +6,24 @@ export SERVER_PORT="5000"
export AUTH_JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long" export AUTH_JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long"
export AUTH_JWT_ALGORITHM="HS256" export AUTH_JWT_ALGORITHM="HS256"
export DATABASE_URL="postgres://supabase_admin:postgres@127.0.0.1:5432/postgres" export DATABASE_URL="postgres://supabase_admin:postgres@127.0.0.1:5432/postgres"
export DATABASE_POOL_URL="postgresql://postgres:postgres@127.0.0.1:6432/postgres" #export DATABASE_POOL_URL="postgresql://postgres:postgres@127.0.0.1:6432/postgres"
#export DB_INSTALL_ROLES="true" #export DB_INSTALL_ROLES="true"
export STORAGE_BACKEND="file" export STORAGE_BACKEND="file"
export FILE_SIZE_LIMIT=52428800 # 50 * 1024 * 1024
export FILE_STORAGE_BACKEND_PATH="/var/lib/storage" export FILE_STORAGE_BACKEND_PATH="/var/lib/storage"
export DB_INSTALL_ROLES="false" export DB_INSTALL_ROLES="true"
# ✅ 明确告诉系统是单租户,避免 undefined
export MULTI_TENANT="false"
export TENANT_ID="storage-single-tenant"
export REGION="chatdesk"
export SUPABASE_PROJECT_REF="storage-single-tenant"
# ✅ 日志调试(可选)
echo "[DEBUG] MULTI_TENANT=$MULTI_TENANT"
echo "[DEBUG] TENANT_ID=$TENANT_ID"
echo "[DEBUG] FILE_STORAGE_BACKEND_PATH=$FILE_STORAGE_BACKEND_PATH"
# 等待 gotrue 服务可用(健康检查) # 等待 gotrue 服务可用(健康检查)
echo "[storage-api] Waiting for GoTrue at /user..." echo "[storage-api] Waiting for GoTrue at /user..."