diff --git a/ui/app/library/page.tsx b/ui/app/library/page.tsx index 379596c..76652c5 100644 --- a/ui/app/library/page.tsx +++ b/ui/app/library/page.tsx @@ -1,10 +1,10 @@ 'use client'; import DeleteChat from '@/components/DeleteChat'; -import { cn, formatTimeDifference } from '@/lib/utils'; -import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react'; +import {cn, formatTimeDifference} from '@/lib/utils'; +import {BookOpenText, ClockIcon, Delete, ScanEye} from 'lucide-react'; import Link from 'next/link'; -import { useEffect, useState } from 'react'; +import {useEffect, useState} from 'react'; export interface Chat { id: string; @@ -20,8 +20,8 @@ const Page = () => { useEffect(() => { const fetchChats = async () => { setLoading(true); - - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chats`, { + let userId = localStorage.getItem("userId"); + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chats?userId=` + userId, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -60,19 +60,19 @@ const Page = () => {
- +

Library

-
+
- {chats.length === 0 && ( + {chats && chats.length === 0 && (

No chats found.

)} - {chats.length > 0 && ( + {chats && chats.length > 0 && (
{chats.map((chat, i) => (
{
- +

{formatTimeDifference(new Date(), chat.createdAt)} Ago

diff --git a/ui/app/page.tsx b/ui/app/page.tsx index e18aca9..9666183 100644 --- a/ui/app/page.tsx +++ b/ui/app/page.tsx @@ -3,8 +3,8 @@ import { Metadata } from 'next'; import { Suspense } from 'react'; export const metadata: Metadata = { - title: 'Chat - Perplexica', - description: 'Chat with the internet, chat with Perplexica.', + title: 'MyCounsellor Ai Serach Eengine - Searching Thking with Gemini', + description: 'Chat with the internet, chat with MyCounsellor.', }; const Home = () => { diff --git a/ui/assets/DeepSeekIcon.tsx b/ui/assets/DeepSeekIcon.tsx new file mode 100644 index 0000000..0c761db --- /dev/null +++ b/ui/assets/DeepSeekIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const DeepSeekIcon = (props: React.JSX.IntrinsicAttributes & React.SVGProps) => ( + + + +); + +export default DeepSeekIcon; diff --git a/ui/assets/deepseek.svg b/ui/assets/deepseek.svg new file mode 100644 index 0000000..3dcf249 --- /dev/null +++ b/ui/assets/deepseek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/components/Chat.tsx b/ui/components/Chat.tsx index 81aa32f..a3caf8a 100644 --- a/ui/components/Chat.tsx +++ b/ui/components/Chat.tsx @@ -16,6 +16,8 @@ const Chat = ({ setFileIds, files, setFiles, + copilotEnabled, + setCopilotEnabled, }: { messages: Message[]; sendMessage: (message: string) => void; @@ -26,6 +28,8 @@ const Chat = ({ setFileIds: (fileIds: string[]) => void; files: File[]; setFiles: (files: File[]) => void; + copilotEnabled:boolean + setCopilotEnabled:(mode: boolean) => void; }) => { const [dividerWidth, setDividerWidth] = useState(0); const dividerRef = useRef(null); @@ -93,6 +97,8 @@ const Chat = ({ setFileIds={setFileIds} files={files} setFiles={setFiles} + copilotEnabled={copilotEnabled} + setCopilotEnabled={setCopilotEnabled} />
)} diff --git a/ui/components/ChatWindow.tsx b/ui/components/ChatWindow.tsx index b26573f..27e23b6 100644 --- a/ui/components/ChatWindow.tsx +++ b/ui/components/ChatWindow.tsx @@ -1,17 +1,17 @@ 'use client'; -import { useEffect, useRef, useState } from 'react'; -import { Document } from '@langchain/core/documents'; +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 {toast} from 'sonner'; +import {useSearchParams} from 'next/navigation'; +import {getSuggestions} from '@/lib/actions'; +import {Settings} from 'lucide-react'; import SettingsDialog from './SettingsDialog'; import NextError from 'next/error'; +import {Mcid} from "@/lib/mcid"; export type Message = { messageId: string; @@ -140,7 +140,7 @@ const useSocket = ( if ( Object.keys(chatModelProviders).length > 0 && (((!openAIBaseURL || !openAIPIKey) && - chatModelProvider === 'custom_openai') || + chatModelProvider === 'custom_openai') || !chatModelProviders[chatModelProvider]) ) { const chatModelProvidersKeys = Object.keys(chatModelProviders); @@ -173,7 +173,7 @@ const useSocket = ( Object.keys(chatModelProviders[chatModelProvider]).length > 0 ? chatModelProvider : Object.keys(chatModelProviders)[0] - ], + ], )[0]; localStorage.setItem('chatModel', chatModel); } @@ -352,7 +352,7 @@ const loadMessages = async ( const messages = data.messages.map((msg: any) => { return { ...msg, - ...JSON.parse(msg.metadata), + // ...JSON.parse(msg.metadata), }; }) as Message[]; @@ -366,7 +366,7 @@ const loadMessages = async ( document.title = messages[0].content; - const files = data.chat.files.map((file: any) => { + const files = data.chat.files && data.chat.files.map((file: any) => { return { fileName: file.name, fileExtension: file.name.split('.').pop(), @@ -375,17 +375,17 @@ const loadMessages = async ( }); setFiles(files); - setFileIds(files.map((file: File) => file.fileId)); + setFileIds(files && files.map((file: File) => file.fileId)); setChatHistory(history); setFocusMode(data.chat.focusMode); setIsMessagesLoaded(true); }; -const ChatWindow = ({ id }: { id?: string }) => { +const ChatWindow = ({id}: { id?: string }) => { const searchParams = useSearchParams(); const initialMessage = searchParams.get('q'); - + const [userId, setUserId] = useState(); const [chatId, setChatId] = useState(id); const [newChatCreated, setNewChatCreated] = useState(false); @@ -410,6 +410,7 @@ const ChatWindow = ({ id }: { id?: string }) => { const [focusMode, setFocusMode] = useState('webSearch'); const [optimizationMode, setOptimizationMode] = useState('speed'); + const [copilotEnabled, setCopilotEnabled] = useState(true); const [isMessagesLoaded, setIsMessagesLoaded] = useState(false); @@ -417,6 +418,33 @@ const ChatWindow = ({ id }: { id?: string }) => { const [isSettingsOpen, setIsSettingsOpen] = useState(false); + useEffect(() => { + const initializeUserId = () => { + try { + // 从 localStorage 读取现有用户 ID + const storedUserId = localStorage.getItem('userId'); + + if (storedUserId) { + setUserId(storedUserId); + console.debug('Using existing user ID:', storedUserId); + } else { + const newUserId = new Mcid().generate().toString(); + + localStorage.setItem('userId', newUserId); + setUserId(newUserId); + console.debug('Generated new user ID:', newUserId); + } + } catch (error) { + console.error('Error initializing user ID:', error); + const fallbackId = "1234567890"; + localStorage.setItem('userId', fallbackId); + setUserId(fallbackId); + } + }; + + initializeUserId(); + }, []); + useEffect(() => { if ( chatId && @@ -437,7 +465,7 @@ const ChatWindow = ({ id }: { id?: string }) => { } else if (!chatId) { setNewChatCreated(true); setIsMessagesLoaded(true); - setChatId(crypto.randomBytes(20).toString('hex')); + setChatId(new Mcid().generate().toString()); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -452,6 +480,30 @@ const ChatWindow = ({ id }: { id?: string }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + const savedFocusMode = localStorage.getItem('focusMode'); + if (savedFocusMode) { + setFocusMode(savedFocusMode); + } + }, [setFocusMode]); + + const handleFocusModeChange = (mode: string) => { + localStorage.setItem('focusMode', mode); + setFocusMode(mode); + }; + + useEffect(() => { + const mode = localStorage.getItem('optimizationMode'); + if (mode) { + setOptimizationMode(mode); + } + }, [setOptimizationMode]); + + const handleOptimizationModeChange = (mode: string) => { + localStorage.setItem('optimizationMode', mode); + setOptimizationMode(mode); + }; + const messagesRef = useRef([]); useEffect(() => { @@ -465,7 +517,7 @@ const ChatWindow = ({ id }: { id?: string }) => { } else { setIsReady(false); } - }, [isMessagesLoaded, isWSReady]); + }, [isMessagesLoaded, isWSReady, userId]); const sendMessage = async (message: string, messageId?: string) => { if (loading) return; @@ -481,11 +533,12 @@ const ChatWindow = ({ id }: { id?: string }) => { let recievedMessage = ''; let added = false; - messageId = messageId ?? crypto.randomBytes(7).toString('hex'); + messageId = messageId ?? new Mcid().generate().toString(); ws.send( JSON.stringify({ type: 'message', + userId: userId, message: { messageId: messageId, chatId: chatId!, @@ -493,8 +546,9 @@ const ChatWindow = ({ id }: { id?: string }) => { }, files: fileIds, focusMode: focusMode, + copilotEnabled: copilotEnabled, optimizationMode: optimizationMode, - history: [...chatHistory, ['human', message]], + history: [], }), ); @@ -556,7 +610,7 @@ const ChatWindow = ({ id }: { id?: string }) => { setMessages((prev) => prev.map((message) => { if (message.messageId === data.messageId) { - return { ...message, content: message.content + data.data }; + return {...message, content: message.content + data.data}; } return message; @@ -589,7 +643,7 @@ const ChatWindow = ({ id }: { id?: string }) => { setMessages((prev) => prev.map((msg) => { if (msg.messageId === lastMsg.messageId) { - return { ...msg, suggestions: suggestions }; + return {...msg, suggestions: suggestions}; } return msg; }), @@ -639,19 +693,19 @@ const ChatWindow = ({ id }: { id?: string }) => { Failed to connect to the server. Please try again later.

- +
); } return isReady ? ( notFound ? ( - + ) : (
{messages.length > 0 ? ( <> - + { setFileIds={setFileIds} files={files} setFiles={setFiles} + copilotEnabled={copilotEnabled} + setCopilotEnabled={setCopilotEnabled} /> ) : ( void; focusMode: string; setFocusMode: (mode: string) => void; + copilotEnabled: boolean; + setCopilotEnabled: (enabled: boolean) => void; optimizationMode: string; setOptimizationMode: (mode: string) => void; fileIds: string[]; @@ -36,14 +40,16 @@ const EmptyChat = ({ onClick={() => setIsSettingsOpen(true)} />
-
-

+
+

Research begins here.

void; optimizationMode: string; setOptimizationMode: (mode: string) => void; + copilotEnabled:boolean + setCopilotEnabled:(mode: boolean) => void; fileIds: string[]; setFileIds: (fileIds: string[]) => void; files: File[]; setFiles: (files: File[]) => void; }) => { - const [copilotEnabled, setCopilotEnabled] = useState(false); const [message, setMessage] = useState(''); const inputRef = useRef(null); @@ -85,6 +88,8 @@ const EmptyChatMessageInput = ({
+
+
-
-
- + + {/**/} +
)} @@ -84,12 +101,12 @@ const MessageBox = ({ {message.sources && message.sources.length > 0 && (
- +

Sources

- +
)}
@@ -114,15 +131,16 @@ const MessageBox = ({ {parsedMessage} {loading && isLast ? null : ( -
+
{/* */} - +
- +
@@ -148,10 +166,10 @@ const MessageBox = ({ message.role === 'assistant' && !loading && ( <> -
+
- +

Related

@@ -160,7 +178,7 @@ const MessageBox = ({ className="flex flex-col space-y-3 text-sm" key={i} > -
+
{ sendMessage(suggestion); @@ -183,7 +201,8 @@ const MessageBox = ({ )}
-
+
void; loading: boolean; @@ -21,8 +23,10 @@ const MessageInput = ({ setFileIds: (fileIds: string[]) => void; files: File[]; setFiles: (files: File[]) => void; + copilotEnabled:boolean + setCopilotEnabled:(mode: boolean) => void; + }) => { - const [copilotEnabled, setCopilotEnabled] = useState(false); const [message, setMessage] = useState(''); const [textareaRows, setTextareaRows] = useState(1); const [mode, setMode] = useState<'multi' | 'single'>('single'); diff --git a/ui/components/MessageInputActions/AttachSmall.tsx b/ui/components/MessageInputActions/AttachSmall.tsx index 3514a58..1bce1b9 100644 --- a/ui/components/MessageInputActions/AttachSmall.tsx +++ b/ui/components/MessageInputActions/AttachSmall.tsx @@ -55,7 +55,7 @@ const AttachSmall = ({
- ) : files.length > 0 ? ( + ) : files && files.length > 0 ? ( , }, @@ -29,6 +30,12 @@ const focusModes = [ description: 'Search in published academic papers', icon: , }, + { + key: 'wolframAlphaSearch', + title: 'Wolfram Alpha', + description: 'Computational knowledge engine', + icon: , + }, { key: 'writingAssistant', title: 'Writing', @@ -36,11 +43,24 @@ const focusModes = [ icon: , }, { - key: 'wolframAlphaSearch', - title: 'Wolfram Alpha', - description: 'Computational knowledge engine', - icon: , + key: 'mathAssistant', + title: 'Math', + description: 'Chat without searching the web', + icon: , }, + { + key: 'translator', + title: 'Translator', + description: 'Chat without searching the web', + icon: ( + + ), + }, + { key: 'youtubeSearch', title: 'Youtube', @@ -65,6 +85,14 @@ const focusModes = [ /> ), }, + { + key: 'deepSeek', + title: 'DeepSeek', + description: 'Chat with DeepSeek', + icon: ( + + ), + }, ]; const Focus = ({ @@ -83,7 +111,7 @@ const Focus = ({ {focusMode !== 'webSearch' ? (
{focusModes.find((mode) => mode.key === focusMode)?.icon} -

+

{focusModes.find((mode) => mode.key === focusMode)?.title}

@@ -91,7 +119,7 @@ const Focus = ({ ) : (
-

Focus

+

Focus

)} @@ -105,7 +133,7 @@ const Focus = ({ leaveTo="opacity-0 translate-y-1" > -
+
{focusModes.map((mode, i) => ( setFocusMode(mode.key)} diff --git a/ui/components/MessageInputActions/Optimization.tsx b/ui/components/MessageInputActions/Optimization.tsx index ac8a7b0..465129f 100644 --- a/ui/components/MessageInputActions/Optimization.tsx +++ b/ui/components/MessageInputActions/Optimization.tsx @@ -23,7 +23,7 @@ const OptimizationModes = [ }, { key: 'quality', - title: 'Quality (Soon)', + title: 'Quality', description: 'Get the most thorough and accurate answer', icon: (
{ - OptimizationModes.find((mode) => mode.key === optimizationMode) - ?.icon + OptimizationModes.find((mode) => mode.key === optimizationMode)?.icon }

{ - OptimizationModes.find((mode) => mode.key === optimizationMode) - ?.title + OptimizationModes.find((mode) => mode.key === optimizationMode)?.title }

@@ -76,13 +74,13 @@ const Optimization = ({ setOptimizationMode(mode.key)} key={i} - disabled={mode.key === 'quality'} + disabled={mode.key === 'quality1'} className={cn( 'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-1 duration-200 cursor-pointer transition', optimizationMode === mode.key ? 'bg-light-secondary dark:bg-dark-secondary' : 'hover:bg-light-secondary dark:hover:bg-dark-secondary', - mode.key === 'quality' && 'opacity-50 cursor-not-allowed', + mode.key === 'quality1' && 'opacity-50 cursor-not-allowed', )} >
diff --git a/ui/components/theme/Provider.tsx b/ui/components/theme/Provider.tsx index 43e2714..a02f08d 100644 --- a/ui/components/theme/Provider.tsx +++ b/ui/components/theme/Provider.tsx @@ -7,7 +7,7 @@ const ThemeProviderComponent = ({ children: React.ReactNode; }) => { return ( - + {children} ); diff --git a/ui/lib/mcid.ts b/ui/lib/mcid.ts new file mode 100644 index 0000000..77d7f65 --- /dev/null +++ b/ui/lib/mcid.ts @@ -0,0 +1,36 @@ +// utils/mcid.ts +export class Mcid { + private lastTimestamp = -1; + private sequence = 0; + + constructor(private readonly machineId: number = 0) { + if (machineId < 0 || machineId > 255) { + throw new Error('Machine ID must be between 0 and 255'); + } + } + + generate(): number { + let now = Date.now(); + + if (now < this.lastTimestamp) { + throw new Error('Clock moved backwards'); + } + + if (now === this.lastTimestamp) { + this.sequence = (this.sequence + 1) & 0xf; + if (this.sequence === 0) { + while (now <= this.lastTimestamp) { + now = Date.now(); + } + } + } else { + this.sequence = 0; + } + + this.lastTimestamp = now; + + return ( + (now * 0x1000) + (this.machineId * 16) + this.sequence + ); + } +}