Compare commits

..

103 Commits
v1.0.1 ... 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
57 changed files with 1468 additions and 416 deletions

View File

@ -355,7 +355,7 @@ RUN chmod +x /supabase/postgres/wrapper.sh /supabase/postgrest/wrapper.sh /supab
ENTRYPOINT ["supervisord"]
#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
#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:
- `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`
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)
const modelData = await getModelWorkspacesByWorkspaceId(workspaceId)
//console.log("......Model data from DB:", 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({
model: (searchParams.get("model") ||
workspace?.default_model ||
"gpt-4-1106-preview") as LLMID,
firstModel) as LLMID,
prompt:
workspace?.default_prompt ||
t("chat.promptPlaceholder"),
@ -184,7 +191,7 @@ export default function WorkspaceLayout({ children }: WorkspaceLayoutProps) {
includeWorkspaceInstructions:
workspace?.include_workspace_instructions || true,
embeddingsProvider:
(workspace?.embeddings_provider as "openai" | "local") || "openai"
(workspace?.embeddings_provider as "openai" | "local" | "bge-m3") || "bge-m3"
})
setLoading(false)

View File

@ -4,7 +4,6 @@ import { Providers } from "@/components/utility/providers"
import TranslationsProvider from "@/components/utility/translations-provider"
import initTranslations from "@/lib/i18n"
import { Database } from "@/supabase/types"
//import { createServerClient } from "@supabase/ssr"
import { getSupabaseServerClient } from "@/lib/supabase/server"
import { Metadata, Viewport } from "next"
import { Inter } from "next/font/google"
@ -114,39 +113,22 @@ export default async function RootLayout({
console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`);
}
// const supabaseUrl = getRuntimeEnv("SUPABASE_URL");
// console.log("==========>Supabase URL: ", supabaseUrl);
let supabaseUrl = "";
try {
// 等待直到获取到有效的 Supabase URL
supabaseUrl = await getValidSupabaseUrl();
console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
//console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
} catch (error) {
console.error("Supabase URL 获取失败:", error);
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 session = (await supabase.auth.getSession()).data.session
const { data, error } = await supabase.auth.getSession();
if (error) {
console.log("[layout.tsx]............Session Error: ", error);
} else {
console.log("[layout.tsx]............Session Data: ", data.session);
//console.log("[layout.tsx]............Session Data: ", data.session);
}
const { t, resources } = await initTranslations(locale, i18nNamespaces)
@ -179,20 +161,4 @@ export default async function RootLayout({
</body>
</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

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

View File

@ -21,11 +21,11 @@ import {
SETUP_STEP_COUNT,
StepContainer
} from "../../../components/setup/step-container"
import { useTranslation } from 'react-i18next'
import { usePathname } from "next/navigation"
import i18nConfig from "@/i18nConfig"
import { createModel } from "@/db/models"
import { useTranslation } from "react-i18next"
export default function SetupPage() {
const {
@ -39,6 +39,7 @@ export default function SetupPage() {
} = useContext(ChatbotUIContext)
const router = useRouter()
const { t } = useTranslation()
@ -58,8 +59,6 @@ export default function SetupPage() {
const getLocalizedPath = (subPath: string) =>
locale === defaultLocale ? `/${subPath}` : `/${locale}/${subPath}`
const { t } = useTranslation()
const [loading, setLoading] = useState(true)
const [currentStep, setCurrentStep] = useState(1)
@ -86,6 +85,10 @@ export default function SetupPage() {
const [perplexityAPIKey, setPerplexityAPIKey] = useState("")
const [openrouterAPIKey, setOpenrouterAPIKey] = useState("")
// 只允许点击一次
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
;(async () => {
//const supabaseClient = await supabase(); // Await the client creation
@ -93,10 +96,8 @@ export default function SetupPage() {
if (!session) {
// 强制跳转到带有 locale 的 login 页面
console.log("...........[setup/page.tsx]")
//return router.push(`${homePath}/login`)
//console.log("...........[setup/page.tsx]")
return router.push(getLocalizedPath("login"))
// return router.push(`/${locale}/login`)
} else {
const user = session.user
@ -123,8 +124,6 @@ export default function SetupPage() {
const homeWorkspaceId = await getHomeWorkspaceByUserId(
session.user.id
)
//return router.push(`${homePath}/${homeWorkspaceId}/chat`)
// return router.push(`/${locale}/${homeWorkspaceId}/chat`)
return router.push(getLocalizedPath(`${homeWorkspaceId}/chat`))
}
}
@ -134,7 +133,10 @@ export default function SetupPage() {
const handleShouldProceed = (proceed: boolean) => {
if (proceed) {
if (currentStep === SETUP_STEP_COUNT) {
handleSaveSetupSetting()
//handleSaveSetupSetting()
if (!isSubmitting) {
handleSaveSetupSetting()
}
} else {
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 supabaseClient = await supabase(); // Await the client creation
const session = (await supabase.auth.getSession()).data.session
if (!session) {
// return router.push(`/${locale}/login`)
//return (`${homePath}/login`)
return router.push(getLocalizedPath("login"))
if (isSubmitting) return
setIsSubmitting(true)
try {
const session = (await supabase.auth.getSession()).data.session
if (!session) {
return router.push(getLocalizedPath("login"))
}
const user = session.user
const profile = await getProfileByUserId(user.id)
const updateProfilePayload: TablesUpdate<"profiles"> = {
...profile,
has_onboarded: true,
display_name: displayName,
username,
openai_api_key: openaiAPIKey,
openai_organization_id: openaiOrgID,
anthropic_api_key: anthropicAPIKey,
google_gemini_api_key: googleGeminiAPIKey,
mistral_api_key: mistralAPIKey,
groq_api_key: groqAPIKey,
perplexity_api_key: perplexityAPIKey,
openrouter_api_key: openrouterAPIKey,
use_azure_openai: useAzureOpenai,
azure_openai_api_key: azureOpenaiAPIKey,
azure_openai_endpoint: azureOpenaiEndpoint,
azure_openai_35_turbo_id: azureOpenai35TurboID,
azure_openai_45_turbo_id: azureOpenai45TurboID,
azure_openai_45_vision_id: azureOpenai45VisionID,
azure_openai_embeddings_id: azureOpenaiEmbeddingsID
}
const updatedProfile = await updateProfile(profile.id, updateProfilePayload)
setProfile(updatedProfile)
const workspaces = await getWorkspacesByUserId(profile.user_id)
const homeWorkspace = workspaces.find(w => w.is_home)
if (!homeWorkspace) {
throw new Error("Home workspace not found.")
}
const baseUrl = typeof window !== "undefined"
? `http://${window.location.hostname}:30000/v1`
: "http://localhost:30000/v1"
if (typeof window !== "undefined") {
await createModel(
{
user_id: profile.user_id,
name: t("setup.Default"),
model_id: "GPT",
base_url: baseUrl,
api_key: "token-abc123",
description: t("setup.DefaultLLMModelCreatedDuringOnboarding"),
context_length: 131072
},
homeWorkspace.id
)
}
setSelectedWorkspace(homeWorkspace)
setWorkspaces(workspaces)
router.push(getLocalizedPath(`${homeWorkspace.id}/chat`))
} catch (err) {
console.error("Setup failed:", err)
setIsSubmitting(false) // 失败后允许重试
}
const 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) => {
switch (stepNum) {
// Profile Step
@ -271,7 +366,9 @@ export default function SetupPage() {
stepNum={currentStep}
stepTitle={t("setup.SetupComplete")}
onShouldProceed={handleShouldProceed}
showNextButton={true}
//showNextButton={true}
showNextButton={true} // 一直显示按钮
isSubmitting={isSubmitting} // 控制是否禁用
showBackButton={true}
>
<FinishStep displayName={displayName} />

View File

@ -7,13 +7,14 @@ import { createClient } from "@supabase/supabase-js"
import { NextResponse } from "next/server"
import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding"
export async function POST(req: Request) {
const json = await req.json()
const { text, fileId, embeddingsProvider, fileExtension } = json as {
text: string
fileId: string
embeddingsProvider: "openai" | "local"
embeddingsProvider: "openai" | "local" | "bge-m3"
fileExtension: string
}
@ -82,6 +83,40 @@ export async function POST(req: Request) {
})
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) => ({
@ -96,6 +131,11 @@ export async function POST(req: Request) {
local_embedding:
embeddingsProvider === "local"
? ((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
}))

View File

@ -1,4 +1,5 @@
import { generateLocalEmbedding } from "@/lib/generate-local-embedding"
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding"
import {
processCSV,
processJSON,
@ -15,6 +16,7 @@ import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
export async function POST(req: Request) {
try {
const supabaseAdmin = createClient<Database>(
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 embeddingsProvider = formData.get("embeddingsProvider") as string
console.log("......[process] Starting file processing request,embeddingsProvider=", embeddingsProvider)
const { data: fileMetadata, error: metadataError } = await supabaseAdmin
.from("files")
.select("*")
@ -87,7 +91,9 @@ export async function POST(req: Request) {
chunks = await processMarkdown(blob)
break
case "pdf":
console.log("......[process] Processing PDF...")
chunks = await processPdf(blob)
console.log("......[process] PDF Processed chunks=", chunks)
break
case "txt":
chunks = await processTxt(blob)
@ -136,6 +142,42 @@ export async function POST(req: Request) {
})
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) => ({
@ -150,6 +192,10 @@ export async function POST(req: Request) {
local_embedding:
embeddingsProvider === "local"
? ((embeddings[index] || null) as any)
: null,
bge_m3_embedding:
embeddingsProvider === "bge-m3" && embeddings[index] && embeddings[index].length === 1024
? (embeddings[index] || null) as any
: null
}))
@ -162,6 +208,7 @@ export async function POST(req: Request) {
.update({ tokens: totalTokens })
.eq("id", file_id)
console.log("......[embedding]Embed Successful.")
return new NextResponse("Embed Successful", {
status: 200
})

View File

@ -1,4 +1,5 @@
import { generateLocalEmbedding } from "@/lib/generate-local-embedding"
import { generateBgeM3Embedding } from "@/lib/generate-bgem3-embedding" // 新增
import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers"
import { Database } from "@/supabase/types"
import { createClient } from "@supabase/supabase-js"
@ -6,19 +7,28 @@ import OpenAI from "openai"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
export async function POST(request: Request) {
console.log("......[retrieve] request=", request)
const json = await request.json()
const { userInput, fileIds, embeddingsProvider, sourceCount } = json as {
userInput: string
fileIds: string[]
embeddingsProvider: "openai" | "local"
embeddingsProvider: "openai" | "local" | "bge-m3"
sourceCount: number
}
const uniqueFileIds = [...new Set(fileIds)]
try {
const rawSupaUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
const supaUrlObj = new URL(rawSupaUrl)
supaUrlObj.port = "8000"
const supabaseAdmin = createClient<Database>(
getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
supaUrlObj.origin,
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
@ -84,6 +94,37 @@ export async function POST(request: Request) {
}
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(
@ -94,6 +135,12 @@ export async function POST(request: Request) {
status: 200
})
} 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 errorCode = error.status || 500
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 { WithTooltip } from "../ui/with-tooltip"
import { ChatRetrievalSettings } from "./chat-retrieval-settings"
import { useTranslation } from "react-i18next"
interface ChatFilesDisplayProps {}
export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
const { t } = useTranslation()
useHotkey("f", () => setShowFilesDisplay(prev => !prev))
useHotkey("e", () => setUseRetrieval(prev => !prev))
@ -106,7 +108,7 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
>
<RetrievalToggle />
<div>Hide files</div>
<div>{t("RAG.hideFiles")}</div>
<div onClick={e => e.stopPropagation()}>
<ChatRetrievalSettings />
@ -233,10 +235,14 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
>
<RetrievalToggle />
<div>
{/* <div>
{" "}
View {combinedMessageFiles.length} file
{combinedMessageFiles.length > 1 ? "s" : ""}
</div> */}
<div>
{t("RAG.viewFiles", { count: combinedMessageFiles.length })}
</div>
<div onClick={e => e.stopPropagation()}>
@ -249,6 +255,7 @@ export const ChatFilesDisplay: FC<ChatFilesDisplayProps> = ({}) => {
}
const RetrievalToggle = ({}) => {
const { t } = useTranslation()
const { useRetrieval, setUseRetrieval } = useContext(ChatbotUIContext)
return (
@ -259,8 +266,8 @@ const RetrievalToggle = ({}) => {
display={
<div>
{useRetrieval
? "File retrieval is enabled on the selected files for this message. Click the indicator to disable."
: "Click the indicator to enable file retrieval for this message."}
? t("RAG.retrievalEnabled")
: t("RAG.retrievalDisabled")}
</div>
}
trigger={

View File

@ -22,6 +22,13 @@ import {
import React from "react"
import { toast } from "sonner"
import { v4 as uuidv4 } from "uuid"
import { getRuntimeEnv } from "@/lib/ipconfig"
type RetrievedFileItem = Tables<"file_items"> & {
similarity: number
}
export const validateChatSettings = (
chatSettings: ChatSettings | null,
@ -55,7 +62,7 @@ export const handleRetrieval = async (
userInput: string,
newMessageFiles: ChatFile[],
chatFiles: ChatFile[],
embeddingsProvider: "openai" | "local",
embeddingsProvider: "openai" | "local" | "bge-m3",
sourceCount: number
) => {
const response = await fetch("/api/retrieval/retrieve", {
@ -73,10 +80,86 @@ export const handleRetrieval = async (
}
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 = (

View File

@ -120,6 +120,7 @@ export const useChatHandler = () => {
embeddingsProvider: selectedAssistant.embeddings_provider as
| "openai"
| "local"
| "bge-m3"
})
let allFiles = []
@ -261,6 +262,17 @@ export const useChatHandler = () => {
let retrievedFileItems: Tables<"file_items">[] = []
console.log(
'......[handleSendMessage] newMessageFiles.length =',
newMessageFiles.length,
', chatFiles.length =',
chatFiles.length,
', useRetrieval =',
useRetrieval
)
if (
(newMessageFiles.length > 0 || chatFiles.length > 0) &&
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 { ChatbotUIContext } from "@/context/context"
import { Tables } from "@/supabase/types"
import { FC, useContext, useState } from "react"
import { FC, useContext, useState, useRef, useEffect } 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) === _
)
// 1. 创建一个 ref 指向滚动容器
const containerRef = useRef<HTMLDivElement>(null)
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}
/>
)
})
// 2. 每次 chatMessages 变化时,滚到底部
useEffect(() => {
const dom = containerRef.current
if (dom) {
dom.scrollTo({ top: dom.scrollHeight, behavior: "smooth" })
}
}, [chatMessages])
// 先排序
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 { Slider } from "../ui/slider"
import { WithTooltip } from "../ui/with-tooltip"
import { useTranslation } from "react-i18next"
interface ChatRetrievalSettingsProps {}
export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
const { t } = useTranslation()
const { sourceCount, setSourceCount } = useContext(ChatbotUIContext)
const [isOpen, setIsOpen] = useState(false)
@ -25,7 +29,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<WithTooltip
delayDuration={0}
side="top"
display={<div>Adjust retrieval settings.</div>}
display={<div>{t("RAG.adjustRetrievalSettings")}.</div>}
trigger={
<IconAdjustmentsHorizontal
className="cursor-pointer pt-[4px] hover:opacity-50"
@ -38,7 +42,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogContent>
<div className="space-y-3">
<Label className="flex items-center space-x-1">
<div>Source Count:</div>
<div>{t("RAG.sourceCount")}:</div>
<div>{sourceCount}</div>
</Label>
@ -56,7 +60,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogFooter>
<Button size="sm" onClick={() => setIsOpen(false)}>
Save & Close
{t("RAG.saveAndClose")}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -23,6 +23,8 @@ import { TextareaAutosize } from "../ui/textarea-autosize"
import { WithTooltip } from "../ui/with-tooltip"
import { MessageActions } from "./message-actions"
import { MessageMarkdown } from "./message-markdown"
import { useTranslation } from "react-i18next"
const ICON_SIZE = 32
@ -36,6 +38,100 @@ interface MessageProps {
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> = ({
message,
fileItems,
@ -62,7 +158,11 @@ export const Message: FC<MessageProps> = ({
models
} = useContext(ChatbotUIContext)
const { handleSendMessage } = useChatHandler()
const { t } = useTranslation()
const [showThink, setShowThink] = useState(false) //添加Think的状态
const { handleSendMessage } = useChatHandler()
const editInputRef = useRef<HTMLTextAreaElement>(null)
@ -209,7 +309,7 @@ export const Message: FC<MessageProps> = ({
size={ICON_SIZE}
/>
<div className="text-lg font-semibold">Prompt</div>
<div className="text-lg font-semibold">{t("RAG.prompt")}</div>
</div>
) : (
<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">
<IconFileText size={20} />
<div>Searching files...</div>
<div>{t("RAG.searchingFiles")}...</div>
</div>
)
default:
@ -290,7 +390,8 @@ export const Message: FC<MessageProps> = ({
<div className="flex animate-pulse items-center space-x-2">
<IconBolt size={20} />
<div>Using {toolInUse}...</div>
{/* <div>Using {toolInUse}...</div> */}
<div>{t("RAG.usingTool", { tool: toolInUse })}</div>
</div>
)
}
@ -305,7 +406,19 @@ export const Message: FC<MessageProps> = ({
maxRows={20}
/>
) : (
<MessageMarkdown content={message.content} />
// <MessageMarkdown content={message.content} />
<MessageContent content={message.content} />
)}
</div>
@ -407,11 +520,11 @@ export const Message: FC<MessageProps> = ({
{isEditing && (
<div className="mt-4 flex justify-center space-x-2">
<Button size="sm" onClick={handleSendEdit}>
Save & Send
{t("RAG.saveAndSend")}
</Button>
<Button size="sm" variant="outline" onClick={onCancelEdit}>
Cancel
{t("RAG.cancel")}
</Button>
</div>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -157,6 +157,12 @@ export const CHAT_SETTING_LIMITS: Record<LLMID, ChatSettingLimits> = {
MAX_TOKEN_OUTPUT_LENGTH: 4096,
MAX_CONTEXT_LENGTH: 128000
},
"GPT": {
MIN_TEMPERATURE: 0.0,
MAX_TEMPERATURE: 2.0,
MAX_TOKEN_OUTPUT_LENGTH: 4096,
MAX_CONTEXT_LENGTH: 8192
},
// PERPLEXITY MODELS
"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];
// }
// 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
// 最大重试次数
@ -47,7 +14,7 @@ const MAX_RETRIES = 5
const RETRY_DELAY = 1000
export function getRuntimeEnv(key: string): string | undefined {
console.log("============>>Getting Supabase API URL.")
//console.log("============>>Getting Supabase API URL.")
if (typeof window !== "undefined") {
if (!_env && typeof window.RUNTIME_ENV !== "undefined") {
@ -88,6 +55,6 @@ export function getRuntimeEnv(key: string): string | undefined {
// 服务端始终动态读取 process.env
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 的值
}

View File

@ -58,8 +58,19 @@ export const fetchOllamaModels = async () => {
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) {
throw new Error(`Ollama server is not responding.`)
throw new Error(`LLM server is not responding.`)
}
const data = await response.json()

View File

@ -7,19 +7,6 @@ import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
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 user = (await supabase.auth.getUser()).data.user

View File

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

View File

@ -36,7 +36,9 @@
"back": "رجوع",
"next": "التالي",
"WelcomeToChatDeskUI": "مرحبًا بك في ChatDesk، منصة الواجهة الأمامية لتطوير أبحاث دردشة الذكاء الاصطناعي",
"ClickNextToStartChatting": "انقر على 'التالي' لبدء الدردشة مع نموذج اللغة الكبير باستخدام ChatDesk."
"ClickNextToStartChatting": "انقر على 'التالي' لبدء الدردشة مع نموذج اللغة الكبير باستخدام ChatDesk.",
"Default": "افتراضي",
"DefaultLLMModelCreatedDuringOnboarding": "تم إنشاء نموذج LLM الافتراضي أثناء الإعداد"
},
"login": {
"email": "البريد الإلكتروني",
@ -61,7 +63,8 @@
"signupNotAllowed": "البريد الإلكتروني {{email}} غير مسموح له بالتسجيل.",
"unexpectedError": "حدث خطأ غير متوقع",
"invalidCredentials": "البريد الإلكتروني أو كلمة المرور غير صحيحة.",
"clearing": "جارٍ مسح حالة تسجيل الدخول..."
"clearing": "جارٍ مسح حالة تسجيل الدخول...",
"sessionExpired": "انتهت صلاحية الجلسة الخاصة بك. يرجى التسجيل أو تسجيل الدخول مرة أخرى."
},
"chat": {
"defaultChatTitle": "الدردشة",
@ -85,7 +88,9 @@
"selectModel": "اختر نموذجًا",
"hosted": "مستضاف",
"advancedSettings": "الإعدادات المتقدمة",
"searchModelsPlaceholder": "ابحث عن النماذج..."
"searchModelsPlaceholder": "ابحث عن النماذج...",
"showThinking": "عرض عملية التفكير",
"hideThinking": "إخفاء عملية التفكير"
},
"profile": {
"settingsTitle": "إعدادات المستخدم",
@ -283,5 +288,19 @@
"enabled": "مفعل",
"disabled": "معطل",
"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": "পেছনে",
"next": "পরবর্তী",
"WelcomeToChatDeskUI": "ChatDesk-এ স্বাগতম, এটি একটি ফ্রন্টএন্ড প্ল্যাটফর্ম AI চ্যাট গবেষণা ও উন্নয়নের জন্য।",
"ClickNextToStartChatting": "'পরবর্তী' ক্লিক করে আপনার LLM-এর সাথে ChatDesk ব্যবহার করে চ্যাটিং শুরু করুন।"
"ClickNextToStartChatting": "'পরবর্তী' ক্লিক করে আপনার LLM-এর সাথে ChatDesk ব্যবহার করে চ্যাটিং শুরু করুন।",
"Default": "ডিফল্ট",
"DefaultLLMModelCreatedDuringOnboarding": "অনবোর্ডিং চলাকালীন ডিফল্ট LLM মডেল তৈরি করা হয়েছে"
},
"login": {
"email": "ইমেইল",
@ -61,7 +63,8 @@
"signupNotAllowed": "ইমেইল {{email}} দিয়ে সাইন আপ করা অনুমোদিত নয়।",
"unexpectedError": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে",
"invalidCredentials": "অবৈধ ইমেইল বা পাসওয়ার্ড।",
"clearing": "লগইন অবস্থা মুছে ফেলা হচ্ছে..."
"clearing": "লগইন অবস্থা মুছে ফেলা হচ্ছে...",
"sessionExpired": "আপনার সেশনটির মেয়াদ শেষ হয়েছে। অনুগ্রহ করে আবার সাইন আপ বা লগইন করুন।"
},
"chat": {
"defaultChatTitle": "চ্যাট",
@ -85,7 +88,9 @@
"selectModel": "একটি মডেল নির্বাচন করুন",
"hosted": "হোস্টেড",
"advancedSettings": "উন্নত সেটিংস",
"searchModelsPlaceholder": "মডেল খুঁজুন..."
"searchModelsPlaceholder": "মডেল খুঁজুন...",
"showThinking": "ভাবনার প্রক্রিয়া দেখান",
"hideThinking": "ভাবনার প্রক্রিয়া লুকান"
},
"profile": {
"settingsTitle": "ব্যবহারকারী সেটিংস",
@ -283,5 +288,19 @@
"enabled": "সক্রিয়",
"disabled": "নিষ্ক্রিয়",
"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",
"next": "Weiter",
"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": {
"email": "E-Mail",
@ -61,7 +63,8 @@
"signupNotAllowed": "Die E-Mail-Adresse {{email}} ist nicht zur Registrierung zugelassen.",
"unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.",
"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": {
"defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Modell auswählen",
"hosted": "Gehostet",
"advancedSettings": "Erweiterte Einstellungen",
"searchModelsPlaceholder": "Modelle suchen..."
"searchModelsPlaceholder": "Modelle suchen...",
"showThinking": "Denkprozess anzeigen",
"hideThinking": "Denkprozess ausblenden"
},
"profile": {
"settingsTitle": "Benutzereinstellungen",
@ -283,5 +288,19 @@
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"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",
"next": "Next",
"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": {
"email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} is not allowed to sign up.",
"unexpectedError": "An unexpected error occurred",
"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": {
"defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Select a model",
"hosted": "Hosted",
"advancedSettings": "Advanced Settings",
"searchModelsPlaceholder": "Search models..."
"searchModelsPlaceholder": "Search models...",
"showThinking": "Show Thought Process",
"hideThinking": "Hide Thought Process"
},
"profile": {
"settingsTitle": "User Settings",
@ -283,5 +288,19 @@
"enabled": "Enabled",
"disabled": "Disabled",
"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",
"next": "Siguiente",
"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": {
"email": "Correo electrónico",
@ -61,7 +63,8 @@
"signupNotAllowed": "El correo {{email}} no está permitido para registrarse.",
"unexpectedError": "Ocurrió un error inesperado.",
"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": {
"defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Seleccionar modelo",
"hosted": "Alojado",
"advancedSettings": "Configuración avanzada",
"searchModelsPlaceholder": "Buscar modelos..."
"searchModelsPlaceholder": "Buscar modelos...",
"showThinking": "Mostrar proceso de pensamiento",
"hideThinking": "Ocultar proceso de pensamiento"
},
"profile": {
"settingsTitle": "Configuración del usuario",
@ -283,5 +288,19 @@
"enabled": "Activado",
"disabled": "Desactivado",
"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",
"next": "Suivant",
"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": {
"email": "E-mail",
@ -61,7 +63,8 @@
"signupNotAllowed": "Le-mail {{email}} nest pas autorisé à sinscrire.",
"unexpectedError": "Une erreur inattendue sest produite",
"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": {
"defaultChatTitle": "Discussion",
@ -85,7 +88,9 @@
"selectModel": "Sélectionner un modèle",
"hosted": "Hébergé",
"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": {
"settingsTitle": "Paramètres utilisateur",
@ -283,5 +288,19 @@
"enabled": "Activé",
"disabled": "Désactivé",
"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": "חזור",
"next": "הבא",
"WelcomeToChatDeskUI": "ברוך הבא ל-ChatDesk, פלטפורמת ממשק קדמי למחקר ופיתוח של צ'אט בינה מלאכותית",
"ClickNextToStartChatting": "לחץ על 'הבא' כדי להתחיל לשוחח עם מודל השפה שלך באמצעות ChatDesk."
"ClickNextToStartChatting": "לחץ על 'הבא' כדי להתחיל לשוחח עם מודל השפה שלך באמצעות ChatDesk.",
"Default": "ברירת מחדל",
"DefaultLLMModelCreatedDuringOnboarding": "מודל LLM ברירת מחדל שנוצר במהלך ההגדרה הראשונית"
},
"login": {
"email": "אימייל",
@ -61,7 +63,8 @@
"signupNotAllowed": "האימייל {{email}} אינו מורשה להירשם.",
"unexpectedError": "אירעה שגיאה בלתי צפויה",
"invalidCredentials": "אימייל או סיסמה לא נכונים.",
"clearing": "מנקה את מצב ההתחברות..."
"clearing": "מנקה את מצב ההתחברות...",
"sessionExpired": "ההפעלה שלך פגה. אנא הירשם או התחבר מחדש."
},
"chat": {
"defaultChatTitle": "צ'אט",
@ -85,7 +88,10 @@
"selectModel": "בחר מודל",
"hosted": "מארח",
"advancedSettings": "הגדרות מתקדמות",
"searchModelsPlaceholder": "חפש מודלים..."
"searchModelsPlaceholder": "חפש מודלים...",
"showThinking": "הצג תהליך חשיבה",
"hideThinking": "הסתר תהליך חשיבה"
},
"profile": {
"settingsTitle": "הגדרות משתמש",
@ -283,5 +289,19 @@
"enabled": "מופעל",
"disabled": "מנוטרל",
"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",
"next": "Berikutnya",
"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": {
"email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} tidak diizinkan untuk mendaftar.",
"unexpectedError": "Terjadi kesalahan yang tidak terduga",
"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": {
"defaultChatTitle": "Obrolan",
@ -85,7 +88,9 @@
"selectModel": "Pilih model",
"hosted": "Hosted",
"advancedSettings": "Pengaturan Lanjutan",
"searchModelsPlaceholder": "Cari model..."
"searchModelsPlaceholder": "Cari model...",
"showThinking": "Tampilkan proses berpikir",
"hideThinking": "Sembunyikan proses berpikir"
},
"profile": {
"settingsTitle": "Pengaturan Pengguna",
@ -283,5 +288,19 @@
"enabled": "Aktif",
"disabled": "Nonaktif",
"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",
"next": "Avanti",
"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": {
"email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "L'indirizzo email {{email}} non è autorizzato a registrarsi.",
"unexpectedError": "Si è verificato un errore imprevisto",
"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": {
"defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Seleziona un modello",
"hosted": "Ospitato",
"advancedSettings": "Impostazioni avanzate",
"searchModelsPlaceholder": "Cerca modelli..."
"searchModelsPlaceholder": "Cerca modelli...",
"showThinking": "Mostra il processo di pensiero",
"hideThinking": "Nascondi il processo di pensiero"
},
"profile": {
"settingsTitle": "Impostazioni utente",
@ -283,5 +288,19 @@
"enabled": "Abilitato",
"disabled": "Disabilitato",
"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": "戻る",
"next": "次へ",
"WelcomeToChatDeskUI": "対話工房へようこそ — AIチャット開発のためのフロントエンドプラットフォーム",
"ClickNextToStartChatting": "「次へ」をクリックして、対話工房でLLMとのチャットを始めましょう。"
"ClickNextToStartChatting": "「次へ」をクリックして、対話工房でLLMとのチャットを始めましょう。",
"Default": "デフォルト",
"DefaultLLMModelCreatedDuringOnboarding": "初期設定時に作成されたデフォルトのLLMモデル"
},
"login": {
"email": "メールアドレス",
@ -61,7 +63,8 @@
"signupNotAllowed": "メールアドレス {{email}} は登録できません。",
"unexpectedError": "予期しないエラーが発生しました",
"invalidCredentials": "メールアドレスまたはパスワードが正しくありません。",
"clearing": "ログイン状態をクリアしています..."
"clearing": "ログイン状態をクリアしています...",
"sessionExpired": "セッションの有効期限が切れました。再登録またはログインしてください。"
},
"chat": {
"defaultChatTitle": "会話",
@ -85,7 +88,9 @@
"selectModel": "モデルを選択",
"hosted": "ホステッド",
"advancedSettings": "高度な設定",
"searchModelsPlaceholder": "モデルを検索..."
"searchModelsPlaceholder": "モデルを検索...",
"showThinking": "思考過程を表示",
"hideThinking": "思考過程を非表示"
},
"profile": {
"settingsTitle": "ユーザー設定",
@ -283,6 +288,20 @@
"enabled": "有効",
"disabled": "無効",
"startNewChat": "新しいチャットを開始"
},
"RAG": {
"prompt": "プロンプト",
"searchingFiles": "ファイルを検索中...",
"usingTool": "{{tool}} を使用中...",
"saveAndSend": "保存して送信",
"cancel": "キャンセル",
"hideFiles": "ファイルを非表示",
"retrievalEnabled": "このメッセージでは選択されたファイルの取得が有効です。無効にするにはインジケーターをクリックしてください。",
"retrievalDisabled": "このメッセージのファイル取得を有効にするには、インジケーターをクリックしてください。",
"viewFiles": "{{count}} 件のファイルを表示",
"sourceCount": "ソース数:",
"adjustRetrievalSettings": "取得設定を調整する。",
"saveAndClose": "保存して閉じる"
}
}

View File

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

View File

@ -36,7 +36,9 @@
"back": "Voltar",
"next": "Próximo",
"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": {
"email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "O email {{email}} não está autorizado a se registrar.",
"unexpectedError": "Ocorreu um erro inesperado",
"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": {
"defaultChatTitle": "Chat",
@ -85,7 +88,9 @@
"selectModel": "Selecionar modelo",
"hosted": "Hospedado",
"advancedSettings": "Configurações Avançadas",
"searchModelsPlaceholder": "Pesquisar modelos..."
"searchModelsPlaceholder": "Pesquisar modelos...",
"showThinking": "Mostrar processo de pensamento",
"hideThinking": "Ocultar processo de pensamento"
},
"profile": {
"settingsTitle": "Configurações do Usuário",
@ -283,5 +288,19 @@
"enabled": "Ativado",
"disabled": "Desativado",
"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": "Назад",
"next": "Далее",
"WelcomeToChatDeskUI": "Добро пожаловать в ChatDesk — фронтенд-платформу для исследований и разработок в области чат-ИИ",
"ClickNextToStartChatting": "Нажмите «Далее», чтобы начать общение с вашей LLM через ChatDesk."
"ClickNextToStartChatting": "Нажмите «Далее», чтобы начать общение с вашей LLM через ChatDesk.",
"Default": "По умолчанию",
"DefaultLLMModelCreatedDuringOnboarding": "Модель LLM по умолчанию, созданная во время начальной настройки"
},
"login": {
"email": "Эл. почта",
@ -61,7 +63,8 @@
"signupNotAllowed": "Электронная почта {{email}} не разрешена для регистрации.",
"unexpectedError": "Произошла непредвиденная ошибка",
"invalidCredentials": "Неверный адрес эл. почты или пароль.",
"clearing": "Очистка состояния входа..."
"clearing": "Очистка состояния входа...",
"sessionExpired": "Ваша сессия истекла. Пожалуйста, зарегистрируйтесь или войдите снова."
},
"chat": {
"defaultChatTitle": "Чат",
@ -85,7 +88,9 @@
"selectModel": "Выбрать модель",
"hosted": "Хостинг",
"advancedSettings": "Дополнительные настройки",
"searchModelsPlaceholder": "Поиск моделей..."
"searchModelsPlaceholder": "Поиск моделей...",
"showThinking": "Показать ход мыслей",
"hideThinking": "Скрыть ход мыслей"
},
"profile": {
"settingsTitle": "Настройки пользователя",
@ -283,5 +288,19 @@
"enabled": "Включено",
"disabled": "Отключено",
"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": "ආපසු",
"next": "ඊළඟ",
"WelcomeToChatDeskUI": "ChatDesk වෙත සාදරයෙන් පිළිගනිමු — AI කථා බස සොයාගැනීම් සහ සංවර්ධනය සඳහා ඉදිරිපස වේදිකාව",
"ClickNextToStartChatting": "ඔබගේ LLM සමඟ ChatDesk භාවිතයෙන් කතා කිරීම ආරම්භ කිරීමට 'ඊළඟ' ක්ලික් කරන්න."
"ClickNextToStartChatting": "ඔබගේ LLM සමඟ ChatDesk භාවිතයෙන් කතා කිරීම ආරම්භ කිරීමට 'ඊළඟ' ක්ලික් කරන්න.",
"Default": "පෙරනිමි",
"DefaultLLMModelCreatedDuringOnboarding": "ඇරඹුම් අවස්ථාවේදී නිර්මාණය කරන ලද පෙරනිමි LLM ආදර්ශය"
},
"login": {
"email": "ඊමේල්",
@ -61,7 +63,8 @@
"signupNotAllowed": "{{email}} යන්න ලියාපදිංචි වීමට අවසර නැත.",
"unexpectedError": "予期 නොකළ දෝෂයක් ඇතිවිය",
"invalidCredentials": "අවලංගු ඊමේල් හෝ මුරපදයක්.",
"clearing": "ඇතුල් වීමේ තත්වය මකාදමමින්..."
"clearing": "ඇතුල් වීමේ තත්වය මකාදමමින්...",
"sessionExpired": "ඔබගේ සැසිය අවසන් වී ඇත. කරුණාකර නැවත ලියාපදිංචි වන්න හෝ පිවිසෙන්න."
},
"chat": {
"defaultChatTitle": "කතාබස්",
@ -85,7 +88,9 @@
"selectModel": "මාදිලිය තෝරන්න",
"hosted": "සත්කාරකකළ",
"advancedSettings": "උසස් සැකසුම්",
"searchModelsPlaceholder": "මාදිලි සෙවීම..."
"searchModelsPlaceholder": "මාදිලි සෙවීම...",
"showThinking": "සිතීමේ ක්‍රියාවලිය පෙන්වන්න",
"hideThinking": "සිතීමේ ක්‍රියාවලිය සඟවන්න"
},
"profile": {
"settingsTitle": "පරිශීලක සැකසුම්",
@ -283,5 +288,19 @@
"enabled": "සක්‍රිය",
"disabled": "අක්‍රිය",
"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",
"next": "Nästa",
"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": {
"email": "E-post",
@ -61,7 +63,8 @@
"signupNotAllowed": "E-postadressen {{email}} är inte tillåten för registrering.",
"unexpectedError": "Ett oväntat fel inträffade",
"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": {
"defaultChatTitle": "Chatt",
@ -85,7 +88,9 @@
"selectModel": "Välj en modell",
"hosted": "Hostad",
"advancedSettings": "Avancerade inställningar",
"searchModelsPlaceholder": "Sök modeller..."
"searchModelsPlaceholder": "Sök modeller...",
"showThinking": "Visa tankegång",
"hideThinking": "Dölj tankegång"
},
"profile": {
"settingsTitle": "Användarinställningar",
@ -283,5 +288,19 @@
"enabled": "Aktiverad",
"disabled": "Avaktiverad",
"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": "వెనక్కి",
"next": "తరువాత",
"WelcomeToChatDeskUI": "ChatDesk కు స్వాగతం — ఇది AI చాట్ R&D కోసం ముందు భాగం ప్లాట్‌ఫారమ్",
"ClickNextToStartChatting": "'తరువాత' క్లిక్ చేసి ChatDesk ద్వారా మీ LLMతో చాట్ చేయడం ప్రారంభించండి."
"ClickNextToStartChatting": "'తరువాత' క్లిక్ చేసి ChatDesk ద్వారా మీ LLMతో చాట్ చేయడం ప్రారంభించండి.",
"Default": "డీఫాల్ట్",
"DefaultLLMModelCreatedDuringOnboarding": "ఆన్బోర్డింగ్ సమయంలో సృష్టించబడిన డీఫాల్ట్ LLM మోడల్"
},
"login": {
"email": "ఈమెయిల్",
@ -61,7 +63,8 @@
"signupNotAllowed": "{{email}} చిరునామా నమోదు చేసుకోడానికి అనుమతించబడలేదు.",
"unexpectedError": "అనుకోని లోపం సంభవించింది",
"invalidCredentials": "చెల్లని ఈమెయిల్ లేదా పాస్వర్డ్.",
"clearing": "లాగిన్ స్థితిని క్లియర్ చేస్తోంది..."
"clearing": "లాగిన్ స్థితిని క్లియర్ చేస్తోంది...",
"sessionExpired": "మీ సెషన్ గడువు ముగిసింది. దయచేసి మళ్లీ నమోదు చేసుకోండి లేదా లాగిన్ అవ్వండి."
},
"chat": {
"defaultChatTitle": "చాట్",
@ -85,7 +88,9 @@
"selectModel": "ఒక మోడల్‌ను ఎంచుకోండి",
"hosted": "హోస్ట్ చేయబడింది",
"advancedSettings": "అధునాతన సెట్టింగులు",
"searchModelsPlaceholder": "మోడల్స్ శోధించండి..."
"searchModelsPlaceholder": "మోడల్స్ శోధించండి...",
"showThinking": "ఆలోచనా ప్రక్రియ చూపించు",
"hideThinking": "ఆలోచనా ప్రక్రియ దాచు"
},
"profile": {
"settingsTitle": "వినియోగదారు సెట్టింగులు",
@ -283,5 +288,19 @@
"enabled": "ప్రదర్శించబడింది",
"disabled": "అచేతనం",
"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",
"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",
"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": {
"email": "Email",
@ -61,7 +63,8 @@
"signupNotAllowed": "Email {{email}} không được phép đăng ký.",
"unexpectedError": "Đã xảy ra lỗi không mong muốn",
"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": {
"defaultChatTitle": "Trò chuyện",
@ -85,7 +88,9 @@
"selectModel": "Chọn một mô hình",
"hosted": "Được lưu trữ",
"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": {
"settingsTitle": "Cài đặt người dùng",
@ -283,5 +288,19 @@
"enabled": "Đã bật",
"disabled": "Đã tắt",
"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": "返回",
"next": "下一步",
"WelcomeToChatDeskUI": "欢迎使用 对话工坊 —— 面向 AI 对话研发的前端平台",
"ClickNextToStartChatting": "点击“下一步”,开始使用对话工坊与您的 LLM 进行对话。"
"ClickNextToStartChatting": "点击“下一步”,开始使用对话工坊与您的 LLM 进行对话。",
"Default": "默认",
"DefaultLLMModelCreatedDuringOnboarding": "在初始化过程中创建的默认LLM模型"
},
"login": {
"email": "电子邮件",
@ -61,7 +63,8 @@
"signupNotAllowed": "邮箱 {{email}} 不允许注册。",
"unexpectedError": "发生了未知错误",
"invalidCredentials": "邮箱或密码错误。",
"clearing": "正在清除登录状态..."
"clearing": "正在清除登录状态...",
"sessionExpired": "您的会话已过期,请重新注册或登录。"
},
"chat": {
"defaultChatTitle": "对话",
@ -85,7 +88,9 @@
"selectModel": "选择一个模型",
"hosted": "托管的",
"advancedSettings": "高级设置",
"searchModelsPlaceholder": "搜索模型..."
"searchModelsPlaceholder": "搜索模型...",
"showThinking": "显示思考过程",
"hideThinking": "隐藏思考过程"
},
"profile": {
"settingsTitle": "用户设置",
@ -283,5 +288,19 @@
"enabled": "启用",
"disabled": "禁用",
"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
AS $$
DECLARE
project_url TEXT := 'http://supabase_kong_chatbotui:8000';
service_role_key TEXT := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'; -- full access needed for http request to storage
project_url TEXT := 'http://localhost:8000';
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;
BEGIN
SELECT

View File

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

View File

@ -19,6 +19,7 @@ create table file_items (
content TEXT NOT NULL,
local_embedding vector(384), -- 384 works for local w/ Xenova/all-MiniLM-L6-v2
openai_embedding vector(1536), -- 1536 for OpenAI
bge_m3_embedding vector(1024), -- 1024 for BGE-M3
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
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
ALTER TABLE file_items ENABLE ROW LEVEL SECURITY;
@ -114,3 +118,64 @@ begin
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
-- 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
UPDATE workspaces
SET default_model = 'gpt-4-turbo-preview'
SET default_model = 'GPT'
WHERE default_model = 'gpt-4-1106-preview';
UPDATE workspaces
@ -11,7 +11,7 @@ WHERE default_model = 'gpt-3.5-turbo-1106';
-- PRESETS
UPDATE presets
SET model = 'gpt-4-turbo-preview'
SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview';
UPDATE presets
@ -21,7 +21,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- ASSISTANTS
UPDATE assistants
SET model = 'gpt-4-turbo-preview'
SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview';
UPDATE assistants
@ -31,7 +31,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- CHATS
UPDATE chats
SET model = 'gpt-4-turbo-preview'
SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview';
UPDATE chats
@ -41,7 +41,7 @@ WHERE model = 'gpt-3.5-turbo-1106';
-- MESSAGES
UPDATE messages
SET model = 'gpt-4-turbo-preview'
SET model = 'GPT'
WHERE model = 'gpt-4-1106-preview';
UPDATE messages
@ -93,11 +93,11 @@ BEGIN
TRUE,
'Home',
4096,
'gpt-4-turbo-preview', -- Updated default model
'GPT', -- Updated default model
'You are a friendly, helpful AI assistant.',
0.5,
'My home workspace.',
'openai',
'bge-m3',
TRUE,
TRUE,
''

View File

@ -1348,6 +1348,20 @@ export type Database = {
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: {
Args: {
p_name: string

View File

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

View File

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

View File

@ -1,175 +1,248 @@
-- ✅ 创建 schema (幂等)
DO $$
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;
END IF;
END$$;
-- ✅ 创建角色和默认权限(幂等)
DO $$
DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true');
anon_role text = COALESCE(current_setting('storage.anon_role', true), 'anon');
authenticated_role text = COALESCE(current_setting('storage.authenticated_role', true), 'authenticated');
service_role text = COALESCE(current_setting('storage.service_role', true), 'service_role');
install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
anon_role text := COALESCE(current_setting('storage.anon_role', true), 'anon');
authenticated_role text := COALESCE(current_setting('storage.authenticated_role', true), 'authenticated');
service_role text := COALESCE(current_setting('storage.service_role', true), 'service_role');
BEGIN
IF install_roles != 'true' THEN
RETURN;
IF install_roles != 'true' THEN RETURN; END IF;
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;
-- Install ROLES
EXECUTE 'CREATE ROLE ' || anon_role || ' NOLOGIN NOINHERIT';
EXECUTE 'CREATE ROLE ' || authenticated_role || ' NOLOGIN NOINHERIT';
EXECUTE 'CREATE ROLE ' || service_role || ' NOLOGIN NOINHERIT bypassrls';
EXECUTE 'GRANT ' || quote_ident(anon_role) || ',' ||
quote_ident(authenticated_role) || ',' ||
quote_ident(service_role) || ' TO authenticator';
create user authenticator noinherit;
EXECUTE 'grant ' || anon_role || ' to authenticator';
EXECUTE 'grant ' || authenticated_role || ' to authenticator';
EXECUTE 'grant ' || service_role || ' to authenticator';
grant postgres to authenticator;
EXECUTE 'GRANT USAGE ON SCHEMA storage TO ' ||
quote_ident(anon_role) || ',' ||
quote_ident(authenticated_role) || ',' ||
quote_ident(service_role);
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 postgres,' || anon_role || ',' || authenticated_role || ',' || service_role;
EXECUTE 'alter default privileges in schema storage grant all on functions to postgres,' || anon_role || ',' || authenticated_role || ',' || 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 TABLES TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON FUNCTIONS TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(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$$;
CREATE TABLE IF NOT EXISTS "storage"."migrations" (
-- ✅ 创建 migrations 表(幂等)
CREATE TABLE IF NOT EXISTS storage.migrations (
id integer PRIMARY KEY,
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
);
CREATE TABLE IF NOT EXISTS "storage"."buckets" (
"id" text not NULL,
"name" text NOT NULL,
"owner" uuid,
"created_at" timestamptz DEFAULT now(),
"updated_at" timestamptz DEFAULT now(),
PRIMARY KEY ("id")
-- ✅ 创建 buckets 表(不含 public 字段)
CREATE TABLE IF NOT EXISTS storage.buckets (
id text NOT NULL,
name text NOT NULL,
owner uuid,
created_at timestamptz DEFAULT now(),
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" (
"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")
-- ✅ 补 public 字段(兼容升级)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'storage'
AND table_name = 'buckets'
AND column_name = 'public'
) THEN
ALTER TABLE storage.buckets
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 INDEX IF NOT EXISTS name_prefix_search ON storage.objects(name text_pattern_ops);
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);
-- ✅ 启用 RLS
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
drop function if exists storage.foldername;
-- ✅ 内置函数(可重用)
CREATE OR REPLACE FUNCTION storage.foldername(name text)
RETURNS text[]
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
RETURNS text[] LANGUAGE plpgsql AS $$
DECLARE _parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
END
$function$;
SELECT string_to_array(name, '/') INTO _parts;
RETURN _parts[1:array_length(_parts,1)-1];
END$$;
drop function if exists storage.filename;
CREATE OR REPLACE FUNCTION storage.filename(name text)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
RETURNS text LANGUAGE plpgsql AS $$
DECLARE _parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
END
$function$;
SELECT string_to_array(name, '/') INTO _parts;
RETURN _parts[array_length(_parts,1)];
END$$;
drop function if exists storage.extension;
CREATE OR REPLACE FUNCTION storage.extension(name text)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
_filename text;
RETURNS text LANGUAGE plpgsql AS $$
DECLARE _parts text[]; _filename text;
BEGIN
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
-- @todo return the last part instead of 2
return reverse(split_part(reverse(_filename), '.', 1));
END
$function$;
SELECT string_to_array(name, '/') INTO _parts;
SELECT _parts[array_length(_parts,1)] INTO _filename;
RETURN reverse(split_part(reverse(_filename), '.', 1));
END$$;
-- @todo can this query be optimised further?
drop function if exists storage.search;
CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
-- ✅ 文件夹搜索函数(用于前端分层目录浏览)
CREATE OR REPLACE FUNCTION storage.search(
prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0
)
RETURNS TABLE (
name text,
id uuid,
updated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ,
last_accessed_at TIMESTAMPTZ,
updated_at timestamptz,
created_at timestamptz,
last_accessed_at timestamptz,
metadata jsonb
)
LANGUAGE plpgsql
AS $function$
)
LANGUAGE plpgsql AS $$
BEGIN
return query
with files_folders as (
select ((string_to_array(objects.name, '/'))[levels]) as folder
from objects
where objects.name ilike prefix || '%'
and bucket_id = bucketname
GROUP by folder
limit limits
offset offsets
)
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
left join objects
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
END
$function$;
RETURN QUERY
WITH files_folders AS (
SELECT (string_to_array(objects.name, '/'))[levels] AS folder
FROM storage.objects
WHERE objects.name ILIKE prefix || '%'
AND bucket_id = bucketname
GROUP BY folder
LIMIT limits OFFSET offsets
)
SELECT
files_folders.folder AS name,
objects.id,
objects.updated_at,
objects.created_at,
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 $$
DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true');
super_user text = COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin');
install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
super_user text := COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin');
BEGIN
IF install_roles != 'true' THEN
RETURN;
END IF;
IF install_roles != 'true' THEN RETURN; END IF;
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;
-- Grant privileges to Super User
EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || 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 ' || super_user;
EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO ' || quote_ident(super_user);
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;
EXECUTE 'ALTER table "storage".objects owner to ' || super_user;
EXECUTE 'ALTER table "storage".buckets owner to ' || super_user;
EXECUTE 'ALTER table "storage".migrations OWNER TO ' || 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".extension(text) owner to ' || super_user;
EXECUTE 'ALTER function "storage".search(text,text,int,int,int) owner to ' || super_user;
EXECUTE 'ALTER TABLE storage.objects OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER TABLE storage.buckets OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER TABLE storage.migrations OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER FUNCTION storage.foldername(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER FUNCTION storage.filename(text) OWNER TO ' || quote_ident(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$$;

View File

@ -17,22 +17,27 @@ serverurl=unix:///var/run/supervisor.sock
[program:postgres]
command=/bin/bash /supabase/postgres/wrapper.sh
priority=10
user=root
autorestart=true
stdout_logfile=/var/log/postgres.out.log
stderr_logfile=/var/log/postgres.err.log
environment=POSTGRES_PASSWORD="postgres"
[program:postgrest]
command=/bin/bash /supabase/postgrest/wrapper.sh
priority=20
autorestart=true
user=root
stderr_logfile=/var/log/postgrest.err.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"
[program:auth]
command=/bin/bash /supabase/gotrue/wrapper.sh
priority=40
autorestart=true
user=1000
stderr_logfile=/var/log/auth.err.log
@ -41,12 +46,16 @@ stdout_logfile=/var/log/auth.out.log
[program:storage-api]
directory=/supabase/storage-api
command=/bin/bash /supabase/storage-api/wrapper.sh
priority=50
autorestart=true
stderr_logfile=/var/log/storage-api.err.log
stdout_logfile=/var/log/storage-api.out.log
environment=MULTI_TENANT="false"
[program:kong]
command=/bin/bash /supabase/kong/wrapper.sh
priority=30
user=root
autorestart=true
redirect_stderr=true
@ -55,6 +64,7 @@ stdout_logfile=auto
[program:chatdesk-ui]
command=/bin/bash /supabase/chatdesk/wrapper.sh
priority=60
user=root
autorestart=true
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_ALGORITHM="HS256"
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 STORAGE_BACKEND="file"
export FILE_SIZE_LIMIT=52428800 # 50 * 1024 * 1024
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 服务可用(健康检查)
echo "[storage-api] Waiting for GoTrue at /user..."