Compare commits

..

No commits in common. "main" and "v1.0.1" have entirely different histories.
main ... v1.0.1

57 changed files with 421 additions and 1473 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,103 +1,38 @@
// import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler"
// import { ChatbotUIContext } from "@/context/context"
// import { Tables } from "@/supabase/types"
// import { FC, useContext, useState } from "react"
// import { Message } from "../messages/message"
// interface ChatMessagesProps {}
// export const ChatMessages: FC<ChatMessagesProps> = ({}) => {
// const { chatMessages, chatFileItems } = useContext(ChatbotUIContext)
// const { handleSendEdit } = useChatHandler()
// const [editingMessage, setEditingMessage] = useState<Tables<"messages">>()
// return chatMessages
// .sort((a, b) => a.message.sequence_number - b.message.sequence_number)
// .map((chatMessage, index, array) => {
// const messageFileItems = chatFileItems.filter(
// (chatFileItem, _, self) =>
// chatMessage.fileItems.includes(chatFileItem.id) &&
// self.findIndex(item => item.id === chatFileItem.id) === _
// )
// return (
// <Message
// key={chatMessage.message.sequence_number}
// message={chatMessage.message}
// fileItems={messageFileItems}
// isEditing={editingMessage?.id === chatMessage.message.id}
// isLast={index === array.length - 1}
// onStartEdit={setEditingMessage}
// onCancelEdit={() => setEditingMessage(undefined)}
// onSubmitEdit={handleSendEdit}
// />
// )
// })
// }
import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler"
import { ChatbotUIContext } from "@/context/context" import { ChatbotUIContext } from "@/context/context"
import { Tables } from "@/supabase/types" import { Tables } from "@/supabase/types"
import { FC, useContext, useState, useRef, useEffect } from "react" import { FC, useContext, useState } from "react"
import { Message } from "../messages/message" import { Message } from "../messages/message"
interface ChatMessagesProps {} interface ChatMessagesProps {}
export const ChatMessages: FC<ChatMessagesProps> = ({}) => { export const ChatMessages: FC<ChatMessagesProps> = ({}) => {
const { chatMessages, chatFileItems } = useContext(ChatbotUIContext) const { chatMessages, chatFileItems } = useContext(ChatbotUIContext)
const { handleSendEdit } = useChatHandler() const { handleSendEdit } = useChatHandler()
const [editingMessage, setEditingMessage] = useState<Tables<"messages">>() const [editingMessage, setEditingMessage] = useState<Tables<"messages">>()
// 1. 创建一个 ref 指向滚动容器
const containerRef = useRef<HTMLDivElement>(null)
// 2. 每次 chatMessages 变化时,滚到底部 return chatMessages
useEffect(() => { .sort((a, b) => a.message.sequence_number - b.message.sequence_number)
const dom = containerRef.current .map((chatMessage, index, array) => {
if (dom) { const messageFileItems = chatFileItems.filter(
dom.scrollTo({ top: dom.scrollHeight, behavior: "smooth" }) (chatFileItem, _, self) =>
} chatMessage.fileItems.includes(chatFileItem.id) &&
}, [chatMessages]) self.findIndex(item => item.id === chatFileItem.id) === _
)
// 先排序 return (
const sorted = [...chatMessages].sort( <Message
(a, b) => a.message.sequence_number - b.message.sequence_number key={chatMessage.message.sequence_number}
) message={chatMessage.message}
fileItems={messageFileItems}
return ( isEditing={editingMessage?.id === chatMessage.message.id}
// 3. 给外层 div 加上 overflow-auto并且要有高度限制 isLast={index === array.length - 1}
<div onStartEdit={setEditingMessage}
ref={containerRef} onCancelEdit={() => setEditingMessage(undefined)}
className="w-full h-screen overflow-auto" onSubmitEdit={handleSendEdit}
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,14 +11,10 @@ import {
import { Label } from "../ui/label" import { Label } from "../ui/label"
import { Slider } from "../ui/slider" import { Slider } from "../ui/slider"
import { WithTooltip } from "../ui/with-tooltip" import { WithTooltip } from "../ui/with-tooltip"
import { useTranslation } from "react-i18next"
interface ChatRetrievalSettingsProps {} interface ChatRetrievalSettingsProps {}
export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => { export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
const { t } = useTranslation()
const { sourceCount, setSourceCount } = useContext(ChatbotUIContext) const { sourceCount, setSourceCount } = useContext(ChatbotUIContext)
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
@ -29,7 +25,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<WithTooltip <WithTooltip
delayDuration={0} delayDuration={0}
side="top" side="top"
display={<div>{t("RAG.adjustRetrievalSettings")}.</div>} display={<div>Adjust retrieval settings.</div>}
trigger={ trigger={
<IconAdjustmentsHorizontal <IconAdjustmentsHorizontal
className="cursor-pointer pt-[4px] hover:opacity-50" className="cursor-pointer pt-[4px] hover:opacity-50"
@ -42,7 +38,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogContent> <DialogContent>
<div className="space-y-3"> <div className="space-y-3">
<Label className="flex items-center space-x-1"> <Label className="flex items-center space-x-1">
<div>{t("RAG.sourceCount")}:</div> <div>Source Count:</div>
<div>{sourceCount}</div> <div>{sourceCount}</div>
</Label> </Label>
@ -60,7 +56,7 @@ export const ChatRetrievalSettings: FC<ChatRetrievalSettingsProps> = ({}) => {
<DialogFooter> <DialogFooter>
<Button size="sm" onClick={() => setIsOpen(false)}> <Button size="sm" onClick={() => setIsOpen(false)}>
{t("RAG.saveAndClose")} Save & Close
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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