@@ -83,73 +76,35 @@ const Page = () => {
-
- {topics.map((t, i) => (
-
setActiveTopic(t.key)}
- >
- {t.display}
-
- ))}
-
-
- {loading ? (
-
- ) : (
-
- {discover &&
- discover?.map((item, i) => (
-
-

-
-
- {item.title.slice(0, 100)}...
-
-
- {item.content.slice(0, 100)}...
-
+
+ {discover &&
+ discover?.map((item, i) => (
+
+

+
+
+ {item.title.slice(0, 100)}...
-
- ))}
-
- )}
+
+ {item.content.slice(0, 100)}...
+
+
+
+ ))}
+
>
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 25981b5..e18aca9 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,5 +1,4 @@
import ChatWindow from '@/components/ChatWindow';
-import { ChatProvider } from '@/lib/hooks/useChat';
import { Metadata } from 'next';
import { Suspense } from 'react';
@@ -12,9 +11,7 @@ const Home = () => {
return (
-
-
-
+
);
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
index 6fb8255..045226c 100644
--- a/src/app/settings/page.tsx
+++ b/src/app/settings/page.tsx
@@ -21,7 +21,6 @@ interface SettingsType {
anthropicApiKey: string;
geminiApiKey: string;
ollamaApiUrl: string;
- ollamaApiKey: string;
lmStudioApiUrl: string;
deepseekApiKey: string;
aimlApiKey: string;
@@ -149,9 +148,7 @@ const Page = () => {
const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
const [systemInstructions, setSystemInstructions] = useState
('');
- const [measureUnit, setMeasureUnit] = useState<'Imperial' | 'Metric'>(
- 'Metric',
- );
+ const [temperatureUnit, setTemperatureUnit] = useState<'C' | 'F'>('C');
const [savingStates, setSavingStates] = useState>({});
useEffect(() => {
@@ -214,9 +211,7 @@ const Page = () => {
setSystemInstructions(localStorage.getItem('systemInstructions')!);
- setMeasureUnit(
- localStorage.getItem('measureUnit')! as 'Imperial' | 'Metric',
- );
+ setTemperatureUnit(localStorage.getItem('temperatureUnit')! as 'C' | 'F');
setIsLoading(false);
};
@@ -376,8 +371,8 @@ const Page = () => {
localStorage.setItem('embeddingModel', value);
} else if (key === 'systemInstructions') {
localStorage.setItem('systemInstructions', value);
- } else if (key === 'measureUnit') {
- localStorage.setItem('measureUnit', value.toString());
+ } else if (key === 'temperatureUnit') {
+ localStorage.setItem('temperatureUnit', value.toString());
}
} catch (err) {
console.error('Failed to save:', err);
@@ -435,22 +430,22 @@ const Page = () => {
-
- Ollama API Key (Can be left blank)
-
-
{
- setConfig((prev) => ({
- ...prev!,
- ollamaApiKey: e.target.value,
- }));
- }}
- onSave={(value) => saveConfig('ollamaApiKey', value)}
- />
-
-
GROQ API Key
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
index a5d8cf9..0cf125b 100644
--- a/src/components/Chat.tsx
+++ b/src/components/Chat.tsx
@@ -5,11 +5,28 @@ import MessageInput from './MessageInput';
import { File, Message } from './ChatWindow';
import MessageBox from './MessageBox';
import MessageBoxLoading from './MessageBoxLoading';
-import { useChat } from '@/lib/hooks/useChat';
-
-const Chat = () => {
- const { messages, loading, messageAppeared } = useChat();
+const Chat = ({
+ loading,
+ messages,
+ sendMessage,
+ messageAppeared,
+ rewrite,
+ fileIds,
+ setFileIds,
+ files,
+ setFiles,
+}: {
+ messages: Message[];
+ sendMessage: (message: string) => void;
+ loading: boolean;
+ messageAppeared: boolean;
+ rewrite: (messageId: string) => void;
+ fileIds: string[];
+ setFileIds: (fileIds: string[]) => void;
+ files: File[];
+ setFiles: (files: File[]) => void;
+}) => {
const [dividerWidth, setDividerWidth] = useState(0);
const dividerRef = useRef(null);
const messageEnd = useRef(null);
@@ -55,8 +72,12 @@ const Chat = () => {
key={i}
message={msg}
messageIndex={i}
+ history={messages}
+ loading={loading}
dividerRef={isLast ? dividerRef : undefined}
isLast={isLast}
+ rewrite={rewrite}
+ sendMessage={sendMessage}
/>
{!isLast && msg.role === 'assistant' && (
@@ -71,7 +92,14 @@ const Chat = () => {
className="bottom-24 lg:bottom-10 fixed z-40"
style={{ width: dividerWidth }}
>
-
+
)}
diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx
index 0d40c83..67a5d0c 100644
--- a/src/components/ChatWindow.tsx
+++ b/src/components/ChatWindow.tsx
@@ -1,13 +1,17 @@
'use client';
+import { useEffect, useRef, useState } from 'react';
import { Document } from '@langchain/core/documents';
import Navbar from './Navbar';
import Chat from './Chat';
import EmptyChat from './EmptyChat';
+import crypto from 'crypto';
+import { toast } from 'sonner';
+import { useSearchParams } from 'next/navigation';
+import { getSuggestions } from '@/lib/actions';
import { Settings } from 'lucide-react';
import Link from 'next/link';
import NextError from 'next/error';
-import { useChat } from '@/lib/hooks/useChat';
export type Message = {
messageId: string;
@@ -25,8 +29,539 @@ export interface File {
fileId: string;
}
-const ChatWindow = () => {
- const { hasError, isReady, notFound, messages } = useChat();
+interface ChatModelProvider {
+ name: string;
+ provider: string;
+}
+
+interface EmbeddingModelProvider {
+ name: string;
+ provider: string;
+}
+
+const checkConfig = async (
+ setChatModelProvider: (provider: ChatModelProvider) => void,
+ setEmbeddingModelProvider: (provider: EmbeddingModelProvider) => void,
+ setIsConfigReady: (ready: boolean) => void,
+ setHasError: (hasError: boolean) => void,
+) => {
+ try {
+ let chatModel = localStorage.getItem('chatModel');
+ let chatModelProvider = localStorage.getItem('chatModelProvider');
+ let embeddingModel = localStorage.getItem('embeddingModel');
+ let embeddingModelProvider = localStorage.getItem('embeddingModelProvider');
+
+ const autoImageSearch = localStorage.getItem('autoImageSearch');
+ const autoVideoSearch = localStorage.getItem('autoVideoSearch');
+
+ if (!autoImageSearch) {
+ localStorage.setItem('autoImageSearch', 'true');
+ }
+
+ if (!autoVideoSearch) {
+ localStorage.setItem('autoVideoSearch', 'false');
+ }
+
+ const providers = await fetch(`/api/models`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }).then(async (res) => {
+ if (!res.ok)
+ throw new Error(
+ `Failed to fetch models: ${res.status} ${res.statusText}`,
+ );
+ return res.json();
+ });
+
+ if (
+ !chatModel ||
+ !chatModelProvider ||
+ !embeddingModel ||
+ !embeddingModelProvider
+ ) {
+ if (!chatModel || !chatModelProvider) {
+ const chatModelProviders = providers.chatModelProviders;
+ const chatModelProvidersKeys = Object.keys(chatModelProviders);
+
+ if (!chatModelProviders || chatModelProvidersKeys.length === 0) {
+ return toast.error('No chat models available');
+ } else {
+ chatModelProvider =
+ chatModelProvidersKeys.find(
+ (provider) =>
+ Object.keys(chatModelProviders[provider]).length > 0,
+ ) || chatModelProvidersKeys[0];
+ }
+
+ if (
+ chatModelProvider === 'custom_openai' &&
+ Object.keys(chatModelProviders[chatModelProvider]).length === 0
+ ) {
+ toast.error(
+ "Looks like you haven't configured any chat model providers. Please configure them from the settings page or the config file.",
+ );
+ return setHasError(true);
+ }
+
+ chatModel = Object.keys(chatModelProviders[chatModelProvider])[0];
+ }
+
+ if (!embeddingModel || !embeddingModelProvider) {
+ const embeddingModelProviders = providers.embeddingModelProviders;
+
+ if (
+ !embeddingModelProviders ||
+ Object.keys(embeddingModelProviders).length === 0
+ )
+ return toast.error('No embedding models available');
+
+ embeddingModelProvider = Object.keys(embeddingModelProviders)[0];
+ embeddingModel = Object.keys(
+ embeddingModelProviders[embeddingModelProvider],
+ )[0];
+ }
+
+ localStorage.setItem('chatModel', chatModel!);
+ localStorage.setItem('chatModelProvider', chatModelProvider);
+ localStorage.setItem('embeddingModel', embeddingModel!);
+ localStorage.setItem('embeddingModelProvider', embeddingModelProvider);
+ } else {
+ const chatModelProviders = providers.chatModelProviders;
+ const embeddingModelProviders = providers.embeddingModelProviders;
+
+ if (
+ Object.keys(chatModelProviders).length > 0 &&
+ (!chatModelProviders[chatModelProvider] ||
+ Object.keys(chatModelProviders[chatModelProvider]).length === 0)
+ ) {
+ const chatModelProvidersKeys = Object.keys(chatModelProviders);
+ chatModelProvider =
+ chatModelProvidersKeys.find(
+ (key) => Object.keys(chatModelProviders[key]).length > 0,
+ ) || chatModelProvidersKeys[0];
+
+ localStorage.setItem('chatModelProvider', chatModelProvider);
+ }
+
+ if (
+ chatModelProvider &&
+ !chatModelProviders[chatModelProvider][chatModel]
+ ) {
+ if (
+ chatModelProvider === 'custom_openai' &&
+ Object.keys(chatModelProviders[chatModelProvider]).length === 0
+ ) {
+ toast.error(
+ "Looks like you haven't configured any chat model providers. Please configure them from the settings page or the config file.",
+ );
+ return setHasError(true);
+ }
+
+ chatModel = Object.keys(
+ chatModelProviders[
+ Object.keys(chatModelProviders[chatModelProvider]).length > 0
+ ? chatModelProvider
+ : Object.keys(chatModelProviders)[0]
+ ],
+ )[0];
+
+ localStorage.setItem('chatModel', chatModel);
+ }
+
+ if (
+ Object.keys(embeddingModelProviders).length > 0 &&
+ !embeddingModelProviders[embeddingModelProvider]
+ ) {
+ embeddingModelProvider = Object.keys(embeddingModelProviders)[0];
+ localStorage.setItem('embeddingModelProvider', embeddingModelProvider);
+ }
+
+ if (
+ embeddingModelProvider &&
+ !embeddingModelProviders[embeddingModelProvider][embeddingModel]
+ ) {
+ embeddingModel = Object.keys(
+ embeddingModelProviders[embeddingModelProvider],
+ )[0];
+ localStorage.setItem('embeddingModel', embeddingModel);
+ }
+ }
+
+ setChatModelProvider({
+ name: chatModel!,
+ provider: chatModelProvider,
+ });
+
+ setEmbeddingModelProvider({
+ name: embeddingModel!,
+ provider: embeddingModelProvider,
+ });
+
+ setIsConfigReady(true);
+ } catch (err) {
+ console.error('An error occurred while checking the configuration:', err);
+ setIsConfigReady(false);
+ setHasError(true);
+ }
+};
+
+const loadMessages = async (
+ chatId: string,
+ setMessages: (messages: Message[]) => void,
+ setIsMessagesLoaded: (loaded: boolean) => void,
+ setChatHistory: (history: [string, string][]) => void,
+ setFocusMode: (mode: string) => void,
+ setNotFound: (notFound: boolean) => void,
+ setFiles: (files: File[]) => void,
+ setFileIds: (fileIds: string[]) => void,
+) => {
+ const res = await fetch(`/api/chats/${chatId}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (res.status === 404) {
+ setNotFound(true);
+ setIsMessagesLoaded(true);
+ return;
+ }
+
+ const data = await res.json();
+
+ const messages = data.messages.map((msg: any) => {
+ return {
+ ...msg,
+ ...JSON.parse(msg.metadata),
+ };
+ }) as Message[];
+
+ setMessages(messages);
+
+ const history = messages.map((msg) => {
+ return [msg.role, msg.content];
+ }) as [string, string][];
+
+ console.debug(new Date(), 'app:messages_loaded');
+
+ document.title = messages[0].content;
+
+ const files = data.chat.files.map((file: any) => {
+ return {
+ fileName: file.name,
+ fileExtension: file.name.split('.').pop(),
+ fileId: file.fileId,
+ };
+ });
+
+ setFiles(files);
+ setFileIds(files.map((file: File) => file.fileId));
+
+ setChatHistory(history);
+ setFocusMode(data.chat.focusMode);
+ setIsMessagesLoaded(true);
+};
+
+const ChatWindow = ({ id }: { id?: string }) => {
+ const searchParams = useSearchParams();
+ const initialMessage = searchParams.get('q');
+
+ const [chatId, setChatId] = useState