mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-09-13 13:01:33 +00:00
feat(app): switch to useChat hook
This commit is contained in:
@@ -1,9 +1,17 @@
|
|||||||
import ChatWindow from '@/components/ChatWindow';
|
'use client';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const Page = ({ params }: { params: Promise<{ chatId: string }> }) => {
|
import ChatWindow from '@/components/ChatWindow';
|
||||||
const { chatId } = React.use(params);
|
import { useParams } from 'next/navigation';
|
||||||
return <ChatWindow id={chatId} />;
|
import React from 'react';
|
||||||
|
import { ChatProvider } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const { chatId }: { chatId: string } = useParams();
|
||||||
|
return (
|
||||||
|
<ChatProvider id={chatId}>
|
||||||
|
<ChatWindow />
|
||||||
|
</ChatProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import ChatWindow from '@/components/ChatWindow';
|
import ChatWindow from '@/components/ChatWindow';
|
||||||
|
import { ChatProvider } from '@/lib/hooks/useChat';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
@@ -11,7 +12,9 @@ const Home = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ChatWindow />
|
<ChatProvider>
|
||||||
|
<ChatWindow />
|
||||||
|
</ChatProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -5,28 +5,11 @@ import MessageInput from './MessageInput';
|
|||||||
import { File, Message } from './ChatWindow';
|
import { File, Message } from './ChatWindow';
|
||||||
import MessageBox from './MessageBox';
|
import MessageBox from './MessageBox';
|
||||||
import MessageBoxLoading from './MessageBoxLoading';
|
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 [dividerWidth, setDividerWidth] = useState(0);
|
||||||
const dividerRef = useRef<HTMLDivElement | null>(null);
|
const dividerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const messageEnd = useRef<HTMLDivElement | null>(null);
|
const messageEnd = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -72,12 +55,8 @@ const Chat = ({
|
|||||||
key={i}
|
key={i}
|
||||||
message={msg}
|
message={msg}
|
||||||
messageIndex={i}
|
messageIndex={i}
|
||||||
history={messages}
|
|
||||||
loading={loading}
|
|
||||||
dividerRef={isLast ? dividerRef : undefined}
|
dividerRef={isLast ? dividerRef : undefined}
|
||||||
isLast={isLast}
|
isLast={isLast}
|
||||||
rewrite={rewrite}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
/>
|
/>
|
||||||
{!isLast && msg.role === 'assistant' && (
|
{!isLast && msg.role === 'assistant' && (
|
||||||
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
@@ -92,14 +71,7 @@ const Chat = ({
|
|||||||
className="bottom-24 lg:bottom-10 fixed z-40"
|
className="bottom-24 lg:bottom-10 fixed z-40"
|
||||||
style={{ width: dividerWidth }}
|
style={{ width: dividerWidth }}
|
||||||
>
|
>
|
||||||
<MessageInput
|
<MessageInput />
|
||||||
loading={loading}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,17 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
import { Document } from '@langchain/core/documents';
|
import { Document } from '@langchain/core/documents';
|
||||||
import Navbar from './Navbar';
|
import Navbar from './Navbar';
|
||||||
import Chat from './Chat';
|
import Chat from './Chat';
|
||||||
import EmptyChat from './EmptyChat';
|
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 { Settings } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import NextError from 'next/error';
|
import NextError from 'next/error';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
@@ -29,547 +25,8 @@ export interface File {
|
|||||||
fileId: string;
|
fileId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatModelProvider {
|
const ChatWindow = () => {
|
||||||
name: string;
|
const { hasError, isReady, notFound, messages } = useChat();
|
||||||
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<string | undefined>(id);
|
|
||||||
const [newChatCreated, setNewChatCreated] = useState(false);
|
|
||||||
|
|
||||||
const [chatModelProvider, setChatModelProvider] = useState<ChatModelProvider>(
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
provider: '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const [embeddingModelProvider, setEmbeddingModelProvider] =
|
|
||||||
useState<EmbeddingModelProvider>({
|
|
||||||
name: '',
|
|
||||||
provider: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isConfigReady, setIsConfigReady] = useState(false);
|
|
||||||
const [hasError, setHasError] = useState(false);
|
|
||||||
const [isReady, setIsReady] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkConfig(
|
|
||||||
setChatModelProvider,
|
|
||||||
setEmbeddingModelProvider,
|
|
||||||
setIsConfigReady,
|
|
||||||
setHasError,
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [messageAppeared, setMessageAppeared] = useState(false);
|
|
||||||
|
|
||||||
const [chatHistory, setChatHistory] = useState<[string, string][]>([]);
|
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
|
||||||
|
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const [focusMode, setFocusMode] = useState('webSearch');
|
|
||||||
const [optimizationMode, setOptimizationMode] = useState('speed');
|
|
||||||
|
|
||||||
const [isMessagesLoaded, setIsMessagesLoaded] = useState(false);
|
|
||||||
|
|
||||||
const [notFound, setNotFound] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
chatId &&
|
|
||||||
!newChatCreated &&
|
|
||||||
!isMessagesLoaded &&
|
|
||||||
messages.length === 0
|
|
||||||
) {
|
|
||||||
loadMessages(
|
|
||||||
chatId,
|
|
||||||
setMessages,
|
|
||||||
setIsMessagesLoaded,
|
|
||||||
setChatHistory,
|
|
||||||
setFocusMode,
|
|
||||||
setNotFound,
|
|
||||||
setFiles,
|
|
||||||
setFileIds,
|
|
||||||
);
|
|
||||||
} else if (!chatId) {
|
|
||||||
setNewChatCreated(true);
|
|
||||||
setIsMessagesLoaded(true);
|
|
||||||
setChatId(crypto.randomBytes(20).toString('hex'));
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const messagesRef = useRef<Message[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
messagesRef.current = messages;
|
|
||||||
}, [messages]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMessagesLoaded && isConfigReady) {
|
|
||||||
setIsReady(true);
|
|
||||||
console.debug(new Date(), 'app:ready');
|
|
||||||
} else {
|
|
||||||
setIsReady(false);
|
|
||||||
}
|
|
||||||
}, [isMessagesLoaded, isConfigReady]);
|
|
||||||
|
|
||||||
const sendMessage = async (
|
|
||||||
message: string,
|
|
||||||
messageId?: string,
|
|
||||||
rewrite = false,
|
|
||||||
) => {
|
|
||||||
if (loading) return;
|
|
||||||
if (!isConfigReady) {
|
|
||||||
toast.error('Cannot send message before the configuration is ready');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
setMessageAppeared(false);
|
|
||||||
|
|
||||||
let sources: Document[] | undefined = undefined;
|
|
||||||
let recievedMessage = '';
|
|
||||||
let added = false;
|
|
||||||
|
|
||||||
messageId = messageId ?? crypto.randomBytes(7).toString('hex');
|
|
||||||
|
|
||||||
setMessages((prevMessages) => [
|
|
||||||
...prevMessages,
|
|
||||||
{
|
|
||||||
content: message,
|
|
||||||
messageId: messageId,
|
|
||||||
chatId: chatId!,
|
|
||||||
role: 'user',
|
|
||||||
createdAt: new Date(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const messageHandler = async (data: any) => {
|
|
||||||
if (data.type === 'error') {
|
|
||||||
toast.error(data.data);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'sources') {
|
|
||||||
sources = data.data;
|
|
||||||
if (!added) {
|
|
||||||
setMessages((prevMessages) => [
|
|
||||||
...prevMessages,
|
|
||||||
{
|
|
||||||
content: '',
|
|
||||||
messageId: data.messageId,
|
|
||||||
chatId: chatId!,
|
|
||||||
role: 'assistant',
|
|
||||||
sources: sources,
|
|
||||||
createdAt: new Date(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
added = true;
|
|
||||||
}
|
|
||||||
setMessageAppeared(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'message') {
|
|
||||||
if (!added) {
|
|
||||||
setMessages((prevMessages) => [
|
|
||||||
...prevMessages,
|
|
||||||
{
|
|
||||||
content: data.data,
|
|
||||||
messageId: data.messageId,
|
|
||||||
chatId: chatId!,
|
|
||||||
role: 'assistant',
|
|
||||||
sources: sources,
|
|
||||||
createdAt: new Date(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
added = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessages((prev) =>
|
|
||||||
prev.map((message) => {
|
|
||||||
if (message.messageId === data.messageId) {
|
|
||||||
return { ...message, content: message.content + data.data };
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
recievedMessage += data.data;
|
|
||||||
setMessageAppeared(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'messageEnd') {
|
|
||||||
setChatHistory((prevHistory) => [
|
|
||||||
...prevHistory,
|
|
||||||
['human', message],
|
|
||||||
['assistant', recievedMessage],
|
|
||||||
]);
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
|
|
||||||
|
|
||||||
const autoImageSearch = localStorage.getItem('autoImageSearch');
|
|
||||||
const autoVideoSearch = localStorage.getItem('autoVideoSearch');
|
|
||||||
|
|
||||||
if (autoImageSearch === 'true') {
|
|
||||||
document
|
|
||||||
.getElementById(`search-images-${lastMsg.messageId}`)
|
|
||||||
?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoVideoSearch === 'true') {
|
|
||||||
document
|
|
||||||
.getElementById(`search-videos-${lastMsg.messageId}`)
|
|
||||||
?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
lastMsg.role === 'assistant' &&
|
|
||||||
lastMsg.sources &&
|
|
||||||
lastMsg.sources.length > 0 &&
|
|
||||||
!lastMsg.suggestions
|
|
||||||
) {
|
|
||||||
const suggestions = await getSuggestions(messagesRef.current);
|
|
||||||
setMessages((prev) =>
|
|
||||||
prev.map((msg) => {
|
|
||||||
if (msg.messageId === lastMsg.messageId) {
|
|
||||||
return { ...msg, suggestions: suggestions };
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageIndex = messages.findIndex((m) => m.messageId === messageId);
|
|
||||||
|
|
||||||
const res = await fetch('/api/chat', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
content: message,
|
|
||||||
message: {
|
|
||||||
messageId: messageId,
|
|
||||||
chatId: chatId!,
|
|
||||||
content: message,
|
|
||||||
},
|
|
||||||
chatId: chatId!,
|
|
||||||
files: fileIds,
|
|
||||||
focusMode: focusMode,
|
|
||||||
optimizationMode: optimizationMode,
|
|
||||||
history: rewrite
|
|
||||||
? chatHistory.slice(0, messageIndex === -1 ? undefined : messageIndex)
|
|
||||||
: chatHistory,
|
|
||||||
chatModel: {
|
|
||||||
name: chatModelProvider.name,
|
|
||||||
provider: chatModelProvider.provider,
|
|
||||||
},
|
|
||||||
embeddingModel: {
|
|
||||||
name: embeddingModelProvider.name,
|
|
||||||
provider: embeddingModelProvider.provider,
|
|
||||||
},
|
|
||||||
systemInstructions: localStorage.getItem('systemInstructions'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.body) throw new Error('No response body');
|
|
||||||
|
|
||||||
const reader = res.body?.getReader();
|
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
|
|
||||||
let partialChunk = '';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { value, done } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
partialChunk += decoder.decode(value, { stream: true });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const messages = partialChunk.split('\n');
|
|
||||||
for (const msg of messages) {
|
|
||||||
if (!msg.trim()) continue;
|
|
||||||
const json = JSON.parse(msg);
|
|
||||||
messageHandler(json);
|
|
||||||
}
|
|
||||||
partialChunk = '';
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Incomplete JSON, waiting for next chunk...');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rewrite = (messageId: string) => {
|
|
||||||
const index = messages.findIndex((msg) => msg.messageId === messageId);
|
|
||||||
|
|
||||||
if (index === -1) return;
|
|
||||||
|
|
||||||
const message = messages[index - 1];
|
|
||||||
|
|
||||||
setMessages((prev) => {
|
|
||||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
|
||||||
});
|
|
||||||
setChatHistory((prev) => {
|
|
||||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
|
||||||
});
|
|
||||||
|
|
||||||
sendMessage(message.content, message.messageId, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isReady && initialMessage && isConfigReady) {
|
|
||||||
sendMessage(initialMessage);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isConfigReady, isReady, initialMessage]);
|
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -594,31 +51,11 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||||||
<div>
|
<div>
|
||||||
{messages.length > 0 ? (
|
{messages.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Navbar chatId={chatId!} messages={messages} />
|
<Navbar />
|
||||||
<Chat
|
<Chat />
|
||||||
loading={loading}
|
|
||||||
messages={messages}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
messageAppeared={messageAppeared}
|
|
||||||
rewrite={rewrite}
|
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<EmptyChat
|
<EmptyChat />
|
||||||
sendMessage={sendMessage}
|
|
||||||
focusMode={focusMode}
|
|
||||||
setFocusMode={setFocusMode}
|
|
||||||
optimizationMode={optimizationMode}
|
|
||||||
setOptimizationMode={setOptimizationMode}
|
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@@ -5,27 +5,7 @@ import Link from 'next/link';
|
|||||||
import WeatherWidget from './WeatherWidget';
|
import WeatherWidget from './WeatherWidget';
|
||||||
import NewsArticleWidget from './NewsArticleWidget';
|
import NewsArticleWidget from './NewsArticleWidget';
|
||||||
|
|
||||||
const EmptyChat = ({
|
const EmptyChat = () => {
|
||||||
sendMessage,
|
|
||||||
focusMode,
|
|
||||||
setFocusMode,
|
|
||||||
optimizationMode,
|
|
||||||
setOptimizationMode,
|
|
||||||
fileIds,
|
|
||||||
setFileIds,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
}: {
|
|
||||||
sendMessage: (message: string) => void;
|
|
||||||
focusMode: string;
|
|
||||||
setFocusMode: (mode: string) => void;
|
|
||||||
optimizationMode: string;
|
|
||||||
setOptimizationMode: (mode: string) => void;
|
|
||||||
fileIds: string[];
|
|
||||||
setFileIds: (fileIds: string[]) => void;
|
|
||||||
files: File[];
|
|
||||||
setFiles: (files: File[]) => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
|
<div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
|
||||||
@@ -38,17 +18,7 @@ const EmptyChat = ({
|
|||||||
<h2 className="text-black/70 dark:text-white/70 text-3xl font-medium -mt-8">
|
<h2 className="text-black/70 dark:text-white/70 text-3xl font-medium -mt-8">
|
||||||
Research begins here.
|
Research begins here.
|
||||||
</h2>
|
</h2>
|
||||||
<EmptyChatMessageInput
|
<EmptyChatMessageInput />
|
||||||
sendMessage={sendMessage}
|
|
||||||
focusMode={focusMode}
|
|
||||||
setFocusMode={setFocusMode}
|
|
||||||
optimizationMode={optimizationMode}
|
|
||||||
setOptimizationMode={setOptimizationMode}
|
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full gap-4 mt-2 sm:flex-row sm:justify-center">
|
<div className="flex flex-col w-full gap-4 mt-2 sm:flex-row sm:justify-center">
|
||||||
<div className="flex-1 w-full">
|
<div className="flex-1 w-full">
|
||||||
|
@@ -1,34 +1,15 @@
|
|||||||
import { ArrowRight } from 'lucide-react';
|
import { ArrowRight } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import CopilotToggle from './MessageInputActions/Copilot';
|
|
||||||
import Focus from './MessageInputActions/Focus';
|
import Focus from './MessageInputActions/Focus';
|
||||||
import Optimization from './MessageInputActions/Optimization';
|
import Optimization from './MessageInputActions/Optimization';
|
||||||
import Attach from './MessageInputActions/Attach';
|
import Attach from './MessageInputActions/Attach';
|
||||||
import { File } from './ChatWindow';
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
const EmptyChatMessageInput = ({
|
const EmptyChatMessageInput = () => {
|
||||||
sendMessage,
|
const { sendMessage } = useChat();
|
||||||
focusMode,
|
|
||||||
setFocusMode,
|
/* const [copilotEnabled, setCopilotEnabled] = useState(false); */
|
||||||
optimizationMode,
|
|
||||||
setOptimizationMode,
|
|
||||||
fileIds,
|
|
||||||
setFileIds,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
}: {
|
|
||||||
sendMessage: (message: string) => void;
|
|
||||||
focusMode: string;
|
|
||||||
setFocusMode: (mode: string) => void;
|
|
||||||
optimizationMode: string;
|
|
||||||
setOptimizationMode: (mode: string) => void;
|
|
||||||
fileIds: string[];
|
|
||||||
setFileIds: (fileIds: string[]) => void;
|
|
||||||
files: File[];
|
|
||||||
setFiles: (files: File[]) => void;
|
|
||||||
}) => {
|
|
||||||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
@@ -84,20 +65,11 @@ const EmptyChatMessageInput = ({
|
|||||||
/>
|
/>
|
||||||
<div className="flex flex-row items-center justify-between mt-4">
|
<div className="flex flex-row items-center justify-between mt-4">
|
||||||
<div className="flex flex-row items-center space-x-2 lg:space-x-4">
|
<div className="flex flex-row items-center space-x-2 lg:space-x-4">
|
||||||
<Focus focusMode={focusMode} setFocusMode={setFocusMode} />
|
<Focus />
|
||||||
<Attach
|
<Attach showText />
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
showText
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-1 sm:space-x-4">
|
<div className="flex flex-row items-center space-x-1 sm:space-x-4">
|
||||||
<Optimization
|
<Optimization />
|
||||||
optimizationMode={optimizationMode}
|
|
||||||
setOptimizationMode={setOptimizationMode}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
disabled={message.trim().length === 0}
|
disabled={message.trim().length === 0}
|
||||||
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 disabled:bg-[#e0e0dc] dark:disabled:bg-[#ececec21] hover:bg-opacity-85 transition duration-100 rounded-full p-2"
|
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 disabled:bg-[#e0e0dc] dark:disabled:bg-[#ececec21] hover:bg-opacity-85 transition duration-100 rounded-full p-2"
|
||||||
|
@@ -20,6 +20,7 @@ import SearchImages from './SearchImages';
|
|||||||
import SearchVideos from './SearchVideos';
|
import SearchVideos from './SearchVideos';
|
||||||
import { useSpeech } from 'react-text-to-speech';
|
import { useSpeech } from 'react-text-to-speech';
|
||||||
import ThinkBox from './ThinkBox';
|
import ThinkBox from './ThinkBox';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
const ThinkTagProcessor = ({
|
const ThinkTagProcessor = ({
|
||||||
children,
|
children,
|
||||||
@@ -36,22 +37,16 @@ const ThinkTagProcessor = ({
|
|||||||
const MessageBox = ({
|
const MessageBox = ({
|
||||||
message,
|
message,
|
||||||
messageIndex,
|
messageIndex,
|
||||||
history,
|
|
||||||
loading,
|
|
||||||
dividerRef,
|
dividerRef,
|
||||||
isLast,
|
isLast,
|
||||||
rewrite,
|
|
||||||
sendMessage,
|
|
||||||
}: {
|
}: {
|
||||||
message: Message;
|
message: Message;
|
||||||
messageIndex: number;
|
messageIndex: number;
|
||||||
history: Message[];
|
|
||||||
loading: boolean;
|
|
||||||
dividerRef?: MutableRefObject<HTMLDivElement | null>;
|
dividerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
rewrite: (messageId: string) => void;
|
|
||||||
sendMessage: (message: string) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { loading, messages: history, sendMessage, rewrite } = useChat();
|
||||||
|
|
||||||
const [parsedMessage, setParsedMessage] = useState(message.content);
|
const [parsedMessage, setParsedMessage] = useState(message.content);
|
||||||
const [speechMessage, setSpeechMessage] = useState(message.content);
|
const [speechMessage, setSpeechMessage] = useState(message.content);
|
||||||
const [thinkingEnded, setThinkingEnded] = useState(false);
|
const [thinkingEnded, setThinkingEnded] = useState(false);
|
||||||
|
@@ -6,22 +6,11 @@ import Attach from './MessageInputActions/Attach';
|
|||||||
import CopilotToggle from './MessageInputActions/Copilot';
|
import CopilotToggle from './MessageInputActions/Copilot';
|
||||||
import { File } from './ChatWindow';
|
import { File } from './ChatWindow';
|
||||||
import AttachSmall from './MessageInputActions/AttachSmall';
|
import AttachSmall from './MessageInputActions/AttachSmall';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
|
const MessageInput = () => {
|
||||||
|
const { loading, sendMessage } = useChat();
|
||||||
|
|
||||||
const MessageInput = ({
|
|
||||||
sendMessage,
|
|
||||||
loading,
|
|
||||||
fileIds,
|
|
||||||
setFileIds,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
}: {
|
|
||||||
sendMessage: (message: string) => void;
|
|
||||||
loading: boolean;
|
|
||||||
fileIds: string[];
|
|
||||||
setFileIds: (fileIds: string[]) => void;
|
|
||||||
files: File[];
|
|
||||||
setFiles: (files: File[]) => void;
|
|
||||||
}) => {
|
|
||||||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [textareaRows, setTextareaRows] = useState(1);
|
const [textareaRows, setTextareaRows] = useState(1);
|
||||||
@@ -79,14 +68,7 @@ const MessageInput = ({
|
|||||||
mode === 'multi' ? 'flex-col rounded-lg' : 'flex-row rounded-full',
|
mode === 'multi' ? 'flex-col rounded-lg' : 'flex-row rounded-full',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mode === 'single' && (
|
{mode === 'single' && <AttachSmall />}
|
||||||
<AttachSmall
|
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={message}
|
value={message}
|
||||||
@@ -113,12 +95,7 @@ const MessageInput = ({
|
|||||||
)}
|
)}
|
||||||
{mode === 'multi' && (
|
{mode === 'multi' && (
|
||||||
<div className="flex flex-row items-center justify-between w-full pt-2">
|
<div className="flex flex-row items-center justify-between w-full pt-2">
|
||||||
<AttachSmall
|
<AttachSmall />
|
||||||
fileIds={fileIds}
|
|
||||||
setFileIds={setFileIds}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-row items-center space-x-4">
|
<div className="flex flex-row items-center space-x-4">
|
||||||
<CopilotToggle
|
<CopilotToggle
|
||||||
copilotEnabled={copilotEnabled}
|
copilotEnabled={copilotEnabled}
|
||||||
|
@@ -8,20 +8,11 @@ import {
|
|||||||
import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
|
import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
|
||||||
import { Fragment, useRef, useState } from 'react';
|
import { Fragment, useRef, useState } from 'react';
|
||||||
import { File as FileType } from '../ChatWindow';
|
import { File as FileType } from '../ChatWindow';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
|
const Attach = ({ showText }: { showText?: boolean }) => {
|
||||||
|
const { files, setFiles, setFileIds, fileIds } = useChat();
|
||||||
|
|
||||||
const Attach = ({
|
|
||||||
fileIds,
|
|
||||||
setFileIds,
|
|
||||||
showText,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
}: {
|
|
||||||
fileIds: string[];
|
|
||||||
setFileIds: (fileIds: string[]) => void;
|
|
||||||
showText?: boolean;
|
|
||||||
files: FileType[];
|
|
||||||
setFiles: (files: FileType[]) => void;
|
|
||||||
}) => {
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const fileInputRef = useRef<any>();
|
const fileInputRef = useRef<any>();
|
||||||
|
|
||||||
|
@@ -8,18 +8,11 @@ import {
|
|||||||
import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
|
import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
|
||||||
import { Fragment, useRef, useState } from 'react';
|
import { Fragment, useRef, useState } from 'react';
|
||||||
import { File as FileType } from '../ChatWindow';
|
import { File as FileType } from '../ChatWindow';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
|
const AttachSmall = () => {
|
||||||
|
const { files, setFiles, setFileIds, fileIds } = useChat();
|
||||||
|
|
||||||
const AttachSmall = ({
|
|
||||||
fileIds,
|
|
||||||
setFileIds,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
}: {
|
|
||||||
fileIds: string[];
|
|
||||||
setFileIds: (fileIds: string[]) => void;
|
|
||||||
files: FileType[];
|
|
||||||
setFiles: (files: FileType[]) => void;
|
|
||||||
}) => {
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const fileInputRef = useRef<any>();
|
const fileInputRef = useRef<any>();
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import {
|
|||||||
} from '@headlessui/react';
|
} from '@headlessui/react';
|
||||||
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
|
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
const focusModes = [
|
const focusModes = [
|
||||||
{
|
{
|
||||||
@@ -55,13 +56,9 @@ const focusModes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Focus = ({
|
const Focus = () => {
|
||||||
focusMode,
|
const { focusMode, setFocusMode } = useChat();
|
||||||
setFocusMode,
|
|
||||||
}: {
|
|
||||||
focusMode: string;
|
|
||||||
setFocusMode: (mode: string) => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg mt-[6.5px]">
|
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg mt-[6.5px]">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
Transition,
|
Transition,
|
||||||
} from '@headlessui/react';
|
} from '@headlessui/react';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
const OptimizationModes = [
|
const OptimizationModes = [
|
||||||
{
|
{
|
||||||
@@ -34,13 +35,9 @@ const OptimizationModes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Optimization = ({
|
const Optimization = () => {
|
||||||
optimizationMode,
|
const { optimizationMode, setOptimizationMode } = useChat();
|
||||||
setOptimizationMode,
|
|
||||||
}: {
|
|
||||||
optimizationMode: string;
|
|
||||||
setOptimizationMode: (mode: string) => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
|
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
Transition,
|
Transition,
|
||||||
} from '@headlessui/react';
|
} from '@headlessui/react';
|
||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
|
import { useChat } from '@/lib/hooks/useChat';
|
||||||
|
|
||||||
const downloadFile = (filename: string, content: string, type: string) => {
|
const downloadFile = (filename: string, content: string, type: string) => {
|
||||||
const blob = new Blob([content], { type });
|
const blob = new Blob([content], { type });
|
||||||
@@ -118,16 +119,12 @@ const exportAsPDF = (messages: Message[], title: string) => {
|
|||||||
doc.save(`${title || 'chat'}.pdf`);
|
doc.save(`${title || 'chat'}.pdf`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Navbar = ({
|
const Navbar = () => {
|
||||||
chatId,
|
|
||||||
messages,
|
|
||||||
}: {
|
|
||||||
messages: Message[];
|
|
||||||
chatId: string;
|
|
||||||
}) => {
|
|
||||||
const [title, setTitle] = useState<string>('');
|
const [title, setTitle] = useState<string>('');
|
||||||
const [timeAgo, setTimeAgo] = useState<string>('');
|
const [timeAgo, setTimeAgo] = useState<string>('');
|
||||||
|
|
||||||
|
const { messages, chatId } = useChat();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0) {
|
||||||
const newTitle =
|
const newTitle =
|
||||||
@@ -206,7 +203,7 @@ const Navbar = ({
|
|||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Popover>
|
</Popover>
|
||||||
<DeleteChat redirect chatId={chatId} chats={[]} setChats={() => {}} />
|
<DeleteChat redirect chatId={chatId!} chats={[]} setChats={() => {}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user