)
}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index a5a0fc7daf..d248b2bb5d 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -1,28 +1,9 @@
-"use client"
-
-import { ChatbotUISVG } from "@/components/icons/chatbotui-svg"
-import { IconArrowRight } from "@tabler/icons-react"
-import { useTheme } from "next-themes"
-import Link from "next/link"
-
-export default function HomePage() {
- const { theme } = useTheme()
+import Chat from "@/components/chat/chat-ui"
+export default function LocalePage() {
return (
-
-
-
-
-
-
Chatbot UI
-
-
- Start Chatting
-
-
+
+
)
}
diff --git a/app/auth/callback/route.ts b/app/api/auth/callback/route.ts
similarity index 58%
rename from app/auth/callback/route.ts
rename to app/api/auth/callback/route.ts
index acf1c65daa..cff7afb33b 100644
--- a/app/auth/callback/route.ts
+++ b/app/api/auth/callback/route.ts
@@ -10,7 +10,18 @@ export async function GET(request: Request) {
if (code) {
const cookieStore = cookies()
const supabase = createClient(cookieStore)
- await supabase.auth.exchangeCodeForSession(code)
+ const { data, error } = await supabase.auth.exchangeCodeForSession(code)
+
+ if (error) {
+ console.error("Error exchanging code:", error)
+ }
+
+ if (data?.session) {
+ // Redirect back to home page with tokens in query params
+ return NextResponse.redirect(
+ `${requestUrl.origin}/?access_token=${data.session.access_token}&refresh_token=${data.session.refresh_token}`
+ )
+ }
}
if (next) {
diff --git a/app/api/chat/google/route.ts b/app/api/chat/google/route.ts
index ad79139646..cb9820ee88 100644
--- a/app/api/chat/google/route.ts
+++ b/app/api/chat/google/route.ts
@@ -44,7 +44,6 @@ export async function POST(request: Request) {
return new Response(readableStream, {
headers: { "Content-Type": "text/plain" }
})
-
} catch (error: any) {
let errorMessage = error.message || "An unexpected error occurred"
const errorCode = error.status || 500
diff --git a/app/api/chat/openai/route.ts b/app/api/chat/openai/route.ts
deleted file mode 100644
index a0f8ad0c93..0000000000
--- a/app/api/chat/openai/route.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers"
-import { ChatSettings } from "@/types"
-import { OpenAIStream, StreamingTextResponse } from "ai"
-import { ServerRuntime } from "next"
-import OpenAI from "openai"
-import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs"
-
-export const runtime: ServerRuntime = "edge"
-
-export async function POST(request: Request) {
- const json = await request.json()
- const { chatSettings, messages } = json as {
- chatSettings: ChatSettings
- messages: any[]
- }
-
- try {
- const profile = await getServerProfile()
-
- checkApiKey(profile.openai_api_key, "OpenAI")
-
- const openai = new OpenAI({
- apiKey: profile.openai_api_key || "",
- organization: profile.openai_organization_id
- })
-
- const response = await openai.chat.completions.create({
- model: chatSettings.model as ChatCompletionCreateParamsBase["model"],
- messages: messages as ChatCompletionCreateParamsBase["messages"],
- temperature: chatSettings.temperature,
- max_tokens:
- chatSettings.model === "gpt-4-vision-preview" ||
- chatSettings.model === "gpt-4o"
- ? 4096
- : null, // TODO: Fix
- stream: true
- })
-
- const stream = OpenAIStream(response)
-
- return new StreamingTextResponse(stream)
- } catch (error: any) {
- let errorMessage = error.message || "An unexpected error occurred"
- const errorCode = error.status || 500
-
- if (errorMessage.toLowerCase().includes("api key not found")) {
- errorMessage =
- "OpenAI API Key not found. Please set it in your profile settings."
- } else if (errorMessage.toLowerCase().includes("incorrect api key")) {
- errorMessage =
- "OpenAI API Key is incorrect. Please fix it in your profile settings."
- }
-
- return new Response(JSON.stringify({ message: errorMessage }), {
- status: errorCode
- })
- }
-}
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
new file mode 100644
index 0000000000..642cf94b9f
--- /dev/null
+++ b/app/api/chat/route.ts
@@ -0,0 +1,29 @@
+import { NextResponse } from "next/server"
+
+export async function POST(req: Request) {
+ console.log("OPENAI_API_KEY:", process.env.OPENAI_API_KEY)
+
+ const body = await req.json()
+ const { messages } = body
+
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`
+ },
+ body: JSON.stringify({
+ model: "gpt-3.5-turbo", // or gpt-4 if your key supports it
+ messages,
+ temperature: 0.7
+ })
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: errorText }, { status: response.status })
+ }
+
+ const data = await response.json()
+ return NextResponse.json({ result: data.choices[0].message.content })
+}
diff --git a/app/api/session/route.ts b/app/api/session/route.ts
new file mode 100644
index 0000000000..333af40813
--- /dev/null
+++ b/app/api/session/route.ts
@@ -0,0 +1,35 @@
+import { cookies } from "next/headers"
+import { NextResponse } from "next/server"
+import { createServerClient } from "@supabase/ssr"
+import { Database } from "@/supabase/types"
+
+export async function GET() {
+ const cookieStore = cookies()
+
+ const supabase = createServerClient
(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ get(name) {
+ return cookieStore.get(name)?.value
+ }
+ }
+ }
+ )
+
+ const {
+ data: { session },
+ error
+ } = await supabase.auth.getSession()
+
+ if (error) {
+ console.error("Error getting session:", error)
+ return NextResponse.json(
+ { session: null, error: error.message },
+ { status: 500 }
+ )
+ }
+
+ return NextResponse.json({ session })
+}
diff --git a/app/[locale]/[workspaceid]/chat/[chatid]/page.tsx b/app/en/[workspaceid]/chat/[chatid]/page.tsx
similarity index 100%
rename from app/[locale]/[workspaceid]/chat/[chatid]/page.tsx
rename to app/en/[workspaceid]/chat/[chatid]/page.tsx
diff --git a/app/[locale]/[workspaceid]/chat/page.tsx b/app/en/[workspaceid]/chat/page.tsx
similarity index 100%
rename from app/[locale]/[workspaceid]/chat/page.tsx
rename to app/en/[workspaceid]/chat/page.tsx
diff --git a/app/[locale]/[workspaceid]/layout.tsx b/app/en/[workspaceid]/layout.tsx
similarity index 97%
rename from app/[locale]/[workspaceid]/layout.tsx
rename to app/en/[workspaceid]/layout.tsx
index 227a6e7903..1a882d2fd2 100644
--- a/app/[locale]/[workspaceid]/layout.tsx
+++ b/app/en/[workspaceid]/layout.tsx
@@ -95,9 +95,9 @@ export default function WorkspaceLayout({ children }: WorkspaceLayoutProps) {
setSelectedWorkspace(workspace)
const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)
- setAssistants(assistantData.assistants)
+ setAssistants(assistantData.assistant_workspaces)
- for (const assistant of assistantData.assistants) {
+ for (const assistant of assistantData.assistant_workspaces) {
let url = ""
if (assistant.image_path) {
diff --git a/app/en/[workspaceid]/loading.tsx b/app/en/[workspaceid]/loading.tsx
new file mode 100644
index 0000000000..4cfc63fdec
--- /dev/null
+++ b/app/en/[workspaceid]/loading.tsx
@@ -0,0 +1,9 @@
+import { IconLoader2 } from "@tabler/icons-react"
+
+export default function Loading() {
+ return (
+
+
+
+ )
+}
diff --git a/app/[locale]/[workspaceid]/page.tsx b/app/en/[workspaceid]/page.tsx
similarity index 100%
rename from app/[locale]/[workspaceid]/page.tsx
rename to app/en/[workspaceid]/page.tsx
diff --git a/app/en/chat/page.tsx b/app/en/chat/page.tsx
new file mode 100644
index 0000000000..7ce4fe8c09
--- /dev/null
+++ b/app/en/chat/page.tsx
@@ -0,0 +1,41 @@
+import Chat from "@/components/Chat"
+import { cookies } from "next/headers"
+import { createServerClient } from "@supabase/ssr"
+import { Database } from "@/supabase/types"
+import { redirect } from "next/navigation"
+
+export default async function ChatPage() {
+ const cookieStore = cookies()
+
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ get(name: string) {
+ return cookieStore.get(name)?.value
+ }
+ }
+ }
+ )
+
+ const {
+ data: { session }
+ } = await supabase.auth.getSession()
+
+ if (!session) {
+ // Not logged in — redirect to login page
+ return redirect("/login")
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/app/[locale]/globals.css b/app/en/globals.css
similarity index 100%
rename from app/[locale]/globals.css
rename to app/en/globals.css
diff --git a/app/[locale]/help/page.tsx b/app/en/help/page.tsx
similarity index 100%
rename from app/[locale]/help/page.tsx
rename to app/en/help/page.tsx
diff --git a/app/en/layout.tsx b/app/en/layout.tsx
new file mode 100644
index 0000000000..474058a910
--- /dev/null
+++ b/app/en/layout.tsx
@@ -0,0 +1,107 @@
+import { Toaster } from "@/components/ui/sonner"
+import { GlobalState } from "@/components/utility/global-state"
+import { Providers } from "@/components/utility/providers"
+import TranslationsProvider from "@/components/utility/translations-provider"
+import initTranslations from "@/lib/i18n"
+import { Database } from "@/supabase/types"
+import { createServerClient } from "@supabase/ssr"
+import { Metadata, Viewport } from "next"
+import { Inter } from "next/font/google"
+import { cookies } from "next/headers"
+import { ReactNode } from "react"
+import "./globals.css"
+
+const inter = Inter({ subsets: ["latin"] })
+const APP_NAME = "Chatbot UI"
+const APP_DEFAULT_TITLE = "Chatbot UI"
+const APP_TITLE_TEMPLATE = "%s - Chatbot UI"
+const APP_DESCRIPTION = "Chabot UI PWA!"
+
+interface RootLayoutProps {
+ children: ReactNode
+ params: {
+ locale: string
+ }
+}
+
+export const metadata: Metadata = {
+ applicationName: APP_NAME,
+ title: {
+ default: APP_DEFAULT_TITLE,
+ template: APP_TITLE_TEMPLATE
+ },
+ description: APP_DESCRIPTION,
+ manifest: "/manifest.json",
+ appleWebApp: {
+ capable: true,
+ statusBarStyle: "black",
+ title: APP_DEFAULT_TITLE
+ // startUpImage: [],
+ },
+ formatDetection: {
+ telephone: false
+ },
+ openGraph: {
+ type: "website",
+ siteName: APP_NAME,
+ title: {
+ default: APP_DEFAULT_TITLE,
+ template: APP_TITLE_TEMPLATE
+ },
+ description: APP_DESCRIPTION
+ },
+ twitter: {
+ card: "summary",
+ title: {
+ default: APP_DEFAULT_TITLE,
+ template: APP_TITLE_TEMPLATE
+ },
+ description: APP_DESCRIPTION
+ }
+}
+
+export const viewport: Viewport = {
+ themeColor: "#000000"
+}
+
+const i18nNamespaces = ["translation"]
+
+export default async function RootLayout({
+ children,
+ params: { locale }
+}: RootLayoutProps) {
+ const cookieStore = cookies()
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ get(name: string) {
+ return cookieStore.get(name)?.value
+ }
+ }
+ }
+ )
+ const session = (await supabase.auth.getSession()).data.session
+
+ const { t, resources } = await initTranslations(locale, i18nNamespaces)
+
+ return (
+
+
+
+
+
+
+ {session ? {children} : children}
+
+
+
+
+
+ )
+}
diff --git a/app/[locale]/login/page.tsx b/app/en/login/page.tsx
similarity index 64%
rename from app/[locale]/login/page.tsx
rename to app/en/login/page.tsx
index 527422f65a..50d953e27c 100644
--- a/app/[locale]/login/page.tsx
+++ b/app/en/login/page.tsx
@@ -31,10 +31,11 @@ export default async function Login({
}
}
)
+
const session = (await supabase.auth.getSession()).data.session
if (session) {
- const { data: homeWorkspace, error } = await supabase
+ let { data: homeWorkspace, error: homeWorkspaceError } = await supabase
.from("workspaces")
.select("*")
.eq("user_id", session.user.id)
@@ -42,12 +43,43 @@ export default async function Login({
.single()
if (!homeWorkspace) {
- throw new Error(error.message)
+ const { data: newWorkspace, error: createError } = await supabase
+ .from("workspaces")
+ .insert([
+ { user_id: session.user.id, is_home: true, name: "Home Workspace" }
+ ])
+ .select()
+ .single()
+
+ if (!newWorkspace || createError) {
+ throw new Error(
+ createError?.message ||
+ homeWorkspaceError?.message ||
+ "Failed to create home workspace"
+ )
+ }
+
+ homeWorkspace = newWorkspace
}
return redirect(`/${homeWorkspace.id}/chat`)
}
+ const handleOAuthLogin = async () => {
+ "use server"
+
+ const origin = headers().get("origin")
+ const cookieStore = cookies()
+ const supabase = createClient(cookieStore)
+
+ await supabase.auth.signInWithOAuth({
+ provider: "google",
+ options: {
+ redirectTo: `${origin}/auth/callback`
+ }
+ })
+ }
+
const signIn = async (formData: FormData) => {
"use server"
@@ -61,11 +93,13 @@ export default async function Login({
password
})
+ console.log("signIn result", data, error)
+
if (error) {
- return redirect(`/login?message=${error.message}`)
+ return redirect(`/login?message=${encodeURIComponent(error.message)}`)
}
- const { data: homeWorkspace, error: homeWorkspaceError } = await supabase
+ let { data: homeWorkspace, error: homeWorkspaceError } = await supabase
.from("workspaces")
.select("*")
.eq("user_id", data.user.id)
@@ -73,73 +107,26 @@ export default async function Login({
.single()
if (!homeWorkspace) {
- throw new Error(
- homeWorkspaceError?.message || "An unexpected error occurred"
- )
- }
-
- return redirect(`/${homeWorkspace.id}/chat`)
- }
-
- const getEnvVarOrEdgeConfigValue = async (name: string) => {
- "use server"
- if (process.env.EDGE_CONFIG) {
- return await get(name)
- }
-
- return process.env[name]
- }
-
- const signUp = async (formData: FormData) => {
- "use server"
-
- const email = formData.get("email") as string
- const password = formData.get("password") as string
-
- const emailDomainWhitelistPatternsString = await getEnvVarOrEdgeConfigValue(
- "EMAIL_DOMAIN_WHITELIST"
- )
- const emailDomainWhitelist = emailDomainWhitelistPatternsString?.trim()
- ? emailDomainWhitelistPatternsString?.split(",")
- : []
- const emailWhitelistPatternsString =
- await getEnvVarOrEdgeConfigValue("EMAIL_WHITELIST")
- const emailWhitelist = emailWhitelistPatternsString?.trim()
- ? emailWhitelistPatternsString?.split(",")
- : []
-
- // If there are whitelist patterns, check if the email is allowed to sign up
- if (emailDomainWhitelist.length > 0 || emailWhitelist.length > 0) {
- const domainMatch = emailDomainWhitelist?.includes(email.split("@")[1])
- const emailMatch = emailWhitelist?.includes(email)
- if (!domainMatch && !emailMatch) {
- return redirect(
- `/login?message=Email ${email} is not allowed to sign up.`
+ const { data: newWorkspace, error: createError } = await supabase
+ .from("workspaces")
+ .insert([
+ { user_id: data.user.id, is_home: true, name: "Home Workspace" }
+ ])
+ .select()
+ .single()
+
+ if (!newWorkspace || createError) {
+ throw new Error(
+ createError?.message ||
+ homeWorkspaceError?.message ||
+ "Failed to create home workspace"
)
}
- }
-
- const cookieStore = cookies()
- const supabase = createClient(cookieStore)
-
- const { error } = await supabase.auth.signUp({
- email,
- password,
- options: {
- // USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE
- // emailRedirectTo: `${origin}/auth/callback`
- }
- })
- if (error) {
- console.error(error)
- return redirect(`/login?message=${error.message}`)
+ homeWorkspace = newWorkspace
}
- return redirect("/setup")
-
- // USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE
- // return redirect("/login?message=Check email to continue sign in process")
+ return redirect(`/${homeWorkspace.id}/chat`)
}
const handleResetPassword = async (formData: FormData) => {
@@ -193,13 +180,6 @@ export default async function Login({
Login
-
- Sign Up
-
-
Forgot your password?
)}
+
+
)
}
diff --git a/app/[locale]/login/password/page.tsx b/app/en/login/password/page.tsx
similarity index 100%
rename from app/[locale]/login/password/page.tsx
rename to app/en/login/password/page.tsx
diff --git a/app/en/page.tsx b/app/en/page.tsx
new file mode 100644
index 0000000000..06782ca4e6
--- /dev/null
+++ b/app/en/page.tsx
@@ -0,0 +1,51 @@
+"use client"
+
+import { ChatbotUISVG } from "@/components/icons/chatbotui-svg"
+import { IconArrowRight } from "@tabler/icons-react"
+import { useTheme } from "next-themes"
+import Link from "next/link"
+import Chat from "@/components/Chat" // Import your Chat component
+import { useEffect } from "react"
+import { supabase } from "@/lib/supabase/browser-client"
+
+export default function HomePage() {
+ const { theme } = useTheme()
+
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search)
+ const accessToken = params.get("access_token")
+ const refreshToken = params.get("refresh_token")
+
+ if (accessToken && refreshToken) {
+ supabase.auth.setSession({
+ access_token: accessToken,
+ refresh_token: refreshToken
+ })
+ // Optional: remove tokens from URL
+ window.history.replaceState({}, document.title, window.location.pathname)
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+
Chatbot UI
+
+ {/* Chat UI inserted here */}
+
+
+
+
+
+ Start Chatting
+
+
+
+ )
+}
diff --git a/app/[locale]/setup/page.tsx b/app/en/setup/page.tsx
similarity index 100%
rename from app/[locale]/setup/page.tsx
rename to app/en/setup/page.tsx
diff --git a/components/Chat.tsx b/components/Chat.tsx
new file mode 100644
index 0000000000..f0ff17ad72
--- /dev/null
+++ b/components/Chat.tsx
@@ -0,0 +1,137 @@
+"use client"
+
+import React, { useState, useEffect, useRef } from "react"
+
+interface Message {
+ id: string
+ role: "user" | "assistant"
+ content: string
+}
+
+export default function Chat() {
+ const [messages, setMessages] = useState([])
+ const [input, setInput] = useState("")
+ const [isLoading, setIsLoading] = useState(false)
+ const messagesEndRef = useRef(null)
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
+ }
+
+ useEffect(() => {
+ scrollToBottom()
+ }, [messages])
+
+ const handleNewChat = () => {
+ setMessages([])
+ setInput("")
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!input.trim()) return
+
+ const userMessage: Message = {
+ id: crypto.randomUUID(),
+ role: "user",
+ content: input.trim()
+ }
+
+ setMessages(prev => [...prev, userMessage])
+ setInput("")
+ setIsLoading(true)
+
+ try {
+ const response = await fetch("/api/chat", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ messages: [...messages, userMessage].map(({ role, content }) => ({
+ role,
+ content
+ }))
+ })
+ })
+
+ if (!response.ok) {
+ throw new Error("Failed to get response from server")
+ }
+
+ const data = await response.json()
+
+ const assistantMessage: Message = {
+ id: crypto.randomUUID(),
+ role: "assistant",
+ content: data.result || "Sorry, I didn't understand that."
+ }
+
+ setMessages(prev => [...prev, assistantMessage])
+ } catch (error) {
+ console.error(error)
+ const errorMessage: Message = {
+ id: crypto.randomUUID(),
+ role: "assistant",
+ content: "There was an error. Please try again later."
+ }
+ setMessages(prev => [...prev, errorMessage])
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+
+
+ New Chat
+
+
+
+ {messages.length === 0 && (
+
Start the conversation...
+ )}
+ {messages.map(({ id, role, content }) => (
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/components/chat/chat-helpers/index.ts b/components/chat/chat-helpers/index.ts
index 17a2089638..ec4a548cdc 100644
--- a/components/chat/chat-helpers/index.ts
+++ b/components/chat/chat-helpers/index.ts
@@ -208,9 +208,12 @@ export const handleHostedChat = async (
let draftMessages = await buildFinalMessages(payload, profile, chatImages)
- let formattedMessages : any[] = []
+ let formattedMessages: any[] = []
if (provider === "google") {
- formattedMessages = await adaptMessagesForGoogleGemini(payload, draftMessages)
+ formattedMessages = await adaptMessagesForGoogleGemini(
+ payload,
+ draftMessages
+ )
} else {
formattedMessages = draftMessages
}
diff --git a/components/chat/chat-hooks/use-chat-handler.tsx b/components/chat/chat-hooks/use-chat-handler.tsx
index f5ab04a25a..d8e8127be5 100644
--- a/components/chat/chat-hooks/use-chat-handler.tsx
+++ b/components/chat/chat-hooks/use-chat-handler.tsx
@@ -1,190 +1,34 @@
-import { ChatbotUIContext } from "@/context/context"
-import { getAssistantCollectionsByAssistantId } from "@/db/assistant-collections"
-import { getAssistantFilesByAssistantId } from "@/db/assistant-files"
-import { getAssistantToolsByAssistantId } from "@/db/assistant-tools"
-import { updateChat } from "@/db/chats"
-import { getCollectionFilesByCollectionId } from "@/db/collection-files"
-import { deleteMessagesIncludingAndAfter } from "@/db/messages"
-import { buildFinalMessages } from "@/lib/build-prompt"
-import { Tables } from "@/supabase/types"
-import { ChatMessage, ChatPayload, LLMID, ModelProvider } from "@/types"
-import { useRouter } from "next/navigation"
-import { useContext, useEffect, useRef } from "react"
-import { LLM_LIST } from "../../../lib/models/llm/llm-list"
-import {
- createTempMessages,
- handleCreateChat,
- handleCreateMessages,
- handleHostedChat,
- handleLocalChat,
- handleRetrieval,
- processResponse,
- validateChatSettings
-} from "../chat-helpers"
+"use client"
-export const useChatHandler = () => {
- const router = useRouter()
-
- const {
- userInput,
- chatFiles,
- setUserInput,
- setNewMessageImages,
- profile,
- setIsGenerating,
- setChatMessages,
- setFirstTokenReceived,
- selectedChat,
- selectedWorkspace,
- setSelectedChat,
- setChats,
- setSelectedTools,
- availableLocalModels,
- availableOpenRouterModels,
- abortController,
- setAbortController,
- chatSettings,
- newMessageImages,
- selectedAssistant,
- chatMessages,
- chatImages,
- setChatImages,
- setChatFiles,
- setNewMessageFiles,
- setShowFilesDisplay,
- newMessageFiles,
- chatFileItems,
- setChatFileItems,
- setToolInUse,
- useRetrieval,
- sourceCount,
- setIsPromptPickerOpen,
- setIsFilePickerOpen,
- selectedTools,
- selectedPreset,
- setChatSettings,
- models,
- isPromptPickerOpen,
- isFilePickerOpen,
- isToolPickerOpen
- } = useContext(ChatbotUIContext)
-
- const chatInputRef = useRef(null)
-
- useEffect(() => {
- if (!isPromptPickerOpen || !isFilePickerOpen || !isToolPickerOpen) {
- chatInputRef.current?.focus()
- }
- }, [isPromptPickerOpen, isFilePickerOpen, isToolPickerOpen])
-
- const handleNewChat = async () => {
- if (!selectedWorkspace) return
-
- setUserInput("")
- setChatMessages([])
- setSelectedChat(null)
- setChatFileItems([])
-
- setIsGenerating(false)
- setFirstTokenReceived(false)
-
- setChatFiles([])
- setChatImages([])
- setNewMessageFiles([])
- setNewMessageImages([])
- setShowFilesDisplay(false)
- setIsPromptPickerOpen(false)
- setIsFilePickerOpen(false)
-
- setSelectedTools([])
- setToolInUse("none")
-
- if (selectedAssistant) {
- setChatSettings({
- model: selectedAssistant.model as LLMID,
- prompt: selectedAssistant.prompt,
- temperature: selectedAssistant.temperature,
- contextLength: selectedAssistant.context_length,
- includeProfileContext: selectedAssistant.include_profile_context,
- includeWorkspaceInstructions:
- selectedAssistant.include_workspace_instructions,
- embeddingsProvider: selectedAssistant.embeddings_provider as
- | "openai"
- | "local"
- })
+import { ChatMessage, Chat } from "@/types"
+import { useState } from "react"
+import { v4 as uuidv4 } from "uuid"
+import { useChatStore } from "@/providers/chat-store"
+import { useMessageHandler } from "@/lib/hooks/use-message-handler"
+import { useMessages } from "@/lib/hooks/use-messages"
- let allFiles = []
-
- const assistantFiles = (
- await getAssistantFilesByAssistantId(selectedAssistant.id)
- ).files
- allFiles = [...assistantFiles]
- const assistantCollections = (
- await getAssistantCollectionsByAssistantId(selectedAssistant.id)
- ).collections
- for (const collection of assistantCollections) {
- const collectionFiles = (
- await getCollectionFilesByCollectionId(collection.id)
- ).files
- allFiles = [...allFiles, ...collectionFiles]
- }
- const assistantTools = (
- await getAssistantToolsByAssistantId(selectedAssistant.id)
- ).tools
-
- setSelectedTools(assistantTools)
- setChatFiles(
- allFiles.map(file => ({
- id: file.id,
- name: file.name,
- type: file.type,
- file: null
- }))
- )
-
- if (allFiles.length > 0) setShowFilesDisplay(true)
- } else if (selectedPreset) {
- setChatSettings({
- model: selectedPreset.model as LLMID,
- prompt: selectedPreset.prompt,
- temperature: selectedPreset.temperature,
- contextLength: selectedPreset.context_length,
- includeProfileContext: selectedPreset.include_profile_context,
- includeWorkspaceInstructions:
- selectedPreset.include_workspace_instructions,
- embeddingsProvider: selectedPreset.embeddings_provider as
- | "openai"
- | "local"
- })
- } else if (selectedWorkspace) {
- // setChatSettings({
- // model: (selectedWorkspace.default_model ||
- // "gpt-4-1106-preview") as LLMID,
- // prompt:
- // selectedWorkspace.default_prompt ||
- // "You are a friendly, helpful AI assistant.",
- // temperature: selectedWorkspace.default_temperature || 0.5,
- // contextLength: selectedWorkspace.default_context_length || 4096,
- // includeProfileContext:
- // selectedWorkspace.include_profile_context || true,
- // includeWorkspaceInstructions:
- // selectedWorkspace.include_workspace_instructions || true,
- // embeddingsProvider:
- // (selectedWorkspace.embeddings_provider as "openai" | "local") ||
- // "openai"
- // })
+export const useChatHandler = () => {
+ const { currentChat, setCurrentChat, setChats } = useChatStore()
+ const { setChatMessages } = useMessageHandler()
+ const { handleCreateMessages } = useMessages()
+ const [isTyping, setIsTyping] = useState(false)
+
+ const handleCreateChat = (initialMessage?: string): Chat => {
+ const newChat: Chat = {
+ id: uuidv4(),
+ title: initialMessage ? initialMessage.slice(0, 20) : "New Chat",
+ messages: []
}
- return router.push(`/${selectedWorkspace.id}/chat`)
+ setChats(prev => [newChat, ...prev])
+ setCurrentChat(newChat)
+ return newChat
}
const handleFocusChatInput = () => {
- chatInputRef.current?.focus()
- }
-
- const handleStopMessage = () => {
- if (abortController) {
- abortController.abort()
+ const inputElement = document.querySelector("input")
+ if (inputElement) {
+ inputElement.focus()
}
}
@@ -193,230 +37,83 @@ export const useChatHandler = () => {
chatMessages: ChatMessage[],
isRegeneration: boolean
) => {
- const startingInput = messageContent
-
- try {
- setUserInput("")
- setIsGenerating(true)
- setIsPromptPickerOpen(false)
- setIsFilePickerOpen(false)
- setNewMessageImages([])
-
- const newAbortController = new AbortController()
- setAbortController(newAbortController)
-
- const modelData = [
- ...models.map(model => ({
- modelId: model.model_id as LLMID,
- modelName: model.name,
- provider: "custom" as ModelProvider,
- hostedId: model.id,
- platformLink: "",
- imageInput: false
- })),
- ...LLM_LIST,
- ...availableLocalModels,
- ...availableOpenRouterModels
- ].find(llm => llm.modelId === chatSettings?.model)
+ console.log("🚀 handleSendMessage triggered:", {
+ messageContent,
+ chatMessages,
+ isRegeneration
+ })
- validateChatSettings(
- chatSettings,
- modelData,
- profile,
- selectedWorkspace,
- messageContent
- )
+ let activeChat = currentChat
- let currentChat = selectedChat ? { ...selectedChat } : null
-
- const b64Images = newMessageImages.map(image => image.base64)
-
- let retrievedFileItems: Tables<"file_items">[] = []
-
- if (
- (newMessageFiles.length > 0 || chatFiles.length > 0) &&
- useRetrieval
- ) {
- setToolInUse("retrieval")
-
- retrievedFileItems = await handleRetrieval(
- userInput,
- newMessageFiles,
- chatFiles,
- chatSettings!.embeddingsProvider,
- sourceCount
- )
- }
-
- const { tempUserChatMessage, tempAssistantChatMessage } =
- createTempMessages(
- messageContent,
- chatMessages,
- chatSettings!,
- b64Images,
- isRegeneration,
- setChatMessages,
- selectedAssistant
- )
-
- let payload: ChatPayload = {
- chatSettings: chatSettings!,
- workspaceInstructions: selectedWorkspace!.instructions || "",
- chatMessages: isRegeneration
- ? [...chatMessages]
- : [...chatMessages, tempUserChatMessage],
- assistant: selectedChat?.assistant_id ? selectedAssistant : null,
- messageFileItems: retrievedFileItems,
- chatFileItems: chatFileItems
- }
-
- let generatedText = ""
-
- if (selectedTools.length > 0) {
- setToolInUse("Tools")
-
- const formattedMessages = await buildFinalMessages(
- payload,
- profile!,
- chatImages
- )
-
- const response = await fetch("/api/chat/tools", {
- method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({
- chatSettings: payload.chatSettings,
- messages: formattedMessages,
- selectedTools
- })
- })
+ if (!activeChat) {
+ activeChat = handleCreateChat(messageContent)
+ }
- setToolInUse("none")
+ const userMessage: ChatMessage = {
+ id: uuidv4(),
+ chat_id: activeChat.id,
+ content: messageContent,
+ role: "user",
+ created_at: new Date().toISOString()
+ }
- generatedText = await processResponse(
- response,
- isRegeneration
- ? payload.chatMessages[payload.chatMessages.length - 1]
- : tempAssistantChatMessage,
- true,
- newAbortController,
- setFirstTokenReceived,
- setChatMessages,
- setToolInUse
- )
- } else {
- if (modelData!.provider === "ollama") {
- generatedText = await handleLocalChat(
- payload,
- profile!,
- chatSettings!,
- tempAssistantChatMessage,
- isRegeneration,
- newAbortController,
- setIsGenerating,
- setFirstTokenReceived,
- setChatMessages,
- setToolInUse
- )
- } else {
- generatedText = await handleHostedChat(
- payload,
- profile!,
- modelData!,
- tempAssistantChatMessage,
- isRegeneration,
- newAbortController,
- newMessageImages,
- chatImages,
- setIsGenerating,
- setFirstTokenReceived,
- setChatMessages,
- setToolInUse
- )
- }
- }
+ const updatedMessages = [...chatMessages, userMessage]
- if (!currentChat) {
- currentChat = await handleCreateChat(
- chatSettings!,
- profile!,
- selectedWorkspace!,
- messageContent,
- selectedAssistant!,
- newMessageFiles,
- setSelectedChat,
- setChats,
- setChatFiles
- )
- } else {
- const updatedChat = await updateChat(currentChat.id, {
- updated_at: new Date().toISOString()
- })
+ console.log(
+ "🧠 Setting chat messages (before generation):",
+ updatedMessages
+ )
- setChats(prevChats => {
- const updatedChats = prevChats.map(prevChat =>
- prevChat.id === updatedChat.id ? updatedChat : prevChat
- )
+ setChatMessages(updatedMessages)
+ setIsTyping(true)
- return updatedChats
- })
- }
+ let generatedText = ""
- await handleCreateMessages(
- chatMessages,
- currentChat,
- profile!,
- modelData!,
- messageContent,
- generatedText,
- newMessageImages,
- isRegeneration,
- retrievedFileItems,
- setChatMessages,
- setChatFileItems,
- setChatImages,
- selectedAssistant
- )
+ try {
+ const response = await fetch("/api/generate", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({ messages: updatedMessages })
+ })
- setIsGenerating(false)
- setFirstTokenReceived(false)
+ const data = await response.json()
+ generatedText = data.reply
} catch (error) {
- setIsGenerating(false)
- setFirstTokenReceived(false)
- setUserInput(startingInput)
+ console.error("Failed to get AI response:", error)
+ generatedText = "⚠️ Sorry, I couldn't get a response from the AI."
}
- }
- const handleSendEdit = async (
- editedContent: string,
- sequenceNumber: number
- ) => {
- if (!selectedChat) return
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ chat_id: activeChat.id,
+ content: generatedText,
+ role: "assistant",
+ created_at: new Date().toISOString()
+ }
- await deleteMessagesIncludingAndAfter(
- selectedChat.user_id,
- selectedChat.id,
- sequenceNumber
- )
+ const finalMessages = [...updatedMessages, assistantMessage]
- const filteredMessages = chatMessages.filter(
- chatMessage => chatMessage.message.sequence_number < sequenceNumber
- )
+ console.log("🧠 Setting chat messages (final):", finalMessages)
+
+ setChatMessages(finalMessages)
+ setIsTyping(false)
- setChatMessages(filteredMessages)
+ console.log("📨 Creating messages with:", {
+ activeChat,
+ messageContent,
+ generatedText,
+ chatMessagesLength: chatMessages.length
+ })
- handleSendMessage(editedContent, filteredMessages, false)
+ await handleCreateMessages([userMessage, assistantMessage])
}
return {
- chatInputRef,
- prompt,
- handleNewChat,
- handleSendMessage,
+ handleCreateChat,
handleFocusChatInput,
- handleStopMessage,
- handleSendEdit
+ handleSendMessage,
+ isTyping
}
}
diff --git a/components/chat/chat-settings.tsx b/components/chat/chat-settings.tsx
index 8230d5f4d4..43084d606c 100644
--- a/components/chat/chat-settings.tsx
+++ b/components/chat/chat-settings.tsx
@@ -66,7 +66,7 @@ export const ChatSettings: FC = ({}) => {
return (
-
+
= ({}) => {
{fullModel?.modelName || chatSettings.model}
-
diff --git a/components/messages/message.tsx b/components/messages/message.tsx
index d0867d68ab..c9aea1bb27 100644
--- a/components/messages/message.tsx
+++ b/components/messages/message.tsx
@@ -262,7 +262,7 @@ export const Message: FC = ({
: selectedAssistant
? selectedAssistant?.name
: MODEL_DATA?.modelName
- : profile?.display_name ?? profile?.username}
+ : (profile?.display_name ?? profile?.username)}