diff --git a/backend/src/chat/chat.model.ts b/backend/src/chat/chat.model.ts index 086dcdbf..78da6062 100644 --- a/backend/src/chat/chat.model.ts +++ b/backend/src/chat/chat.model.ts @@ -31,7 +31,7 @@ export class Chat extends SystemBaseModel { transformer: { to: (messages: Message[]) => messages, from: (value: any) => { - return value.map((message: any) => ({ + return value?.map((message: any) => ({ ...message, createdAt: message.createdAt ? new Date(message.createdAt) : null, updatedAt: message.updatedAt ? new Date(message.updatedAt) : null, diff --git a/frontend/package.json b/frontend/package.json index 85845aa0..d594a50a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "next": "^14.2.13", "next-themes": "^0.3.0", "react": "^18.3.1", + "react-activity-calendar": "^2.7.8", "react-code-blocks": "^0.1.6", "react-dom": "^18.3.1", "react-dropzone": "^14.2.9", diff --git a/frontend/src/app/(main)/MainLayout.tsx b/frontend/src/app/(main)/MainLayout.tsx index c69fefff..4bdac043 100644 --- a/frontend/src/app/(main)/MainLayout.tsx +++ b/frontend/src/app/(main)/MainLayout.tsx @@ -63,11 +63,11 @@ export default function MainLayout({ maxSize={isMobile ? 10 : 16} onCollapse={() => { console.log(`setting collapse to T`); - setIsCollapsed(true); + // setIsCollapsed(true); }} onExpand={() => { console.log(`setting collapse to F`); - setIsCollapsed(false); + // setIsCollapsed(false); }} className={cn( 'transition-all duration-300 ease-in-out', diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx index 5ee87ebf..09708d9d 100644 --- a/frontend/src/app/(main)/page.tsx +++ b/frontend/src/app/(main)/page.tsx @@ -1,6 +1,12 @@ 'use client'; -import { useEffect, useRef, useState } from 'react'; +import { + SetStateAction, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import { Message } from '@/components/types'; import { useModels } from '../hooks/useModels'; import ChatContent from '@/components/chat/chat'; @@ -10,9 +16,11 @@ import { useQuery } from '@apollo/client'; import { toast } from 'sonner'; import { useChatList } from '../hooks/useChatList'; import { EventEnum } from '@/components/enum'; +import DetailSettings from '@/components/detail-settings'; +import EditUsernameForm from '@/components/edit-username-form'; export default function Home() { - const urlParams = new URLSearchParams(window.location.search); + let urlParams = new URLSearchParams(window.location.search); const [chatId, setChatId] = useState(''); // Core message states const [messages, setMessages] = useState([]); @@ -44,17 +52,29 @@ export default function Home() { }, }); - const updateChatId = () => { + const cleanChatId = () => { setChatId(''); }; + const updateChatId = useCallback(() => { + urlParams = new URLSearchParams(window.location.search); + setChatId(urlParams.get('id') || ''); + refetchChats(); + }, []); + const updateSetting = () => { + setChatId(EventEnum.SETTING); + }; useEffect(() => { - window.addEventListener(EventEnum.NEW_CHAT, updateChatId); + window.addEventListener(EventEnum.CHAT, updateChatId); + window.addEventListener(EventEnum.NEW_CHAT, cleanChatId); + window.addEventListener(EventEnum.SETTING, updateSetting); return () => { - window.removeEventListener(EventEnum.NEW_CHAT, updateChatId); + window.removeEventListener(EventEnum.CHAT, updateChatId); + window.removeEventListener(EventEnum.NEW_CHAT, cleanChatId); + window.removeEventListener(EventEnum.SETTING, updateSetting); }; - }, []); + }, [updateChatId]); const { loadingSubmit, handleSubmit, handleInputChange, stop } = useChatStream({ @@ -66,18 +86,24 @@ export default function Home() { }); return ( - + <> + {chatId === EventEnum.SETTING.toString() ? ( + + ) : ( + + )} + ); } diff --git a/frontend/src/components/chat/chat-list.tsx b/frontend/src/components/chat/chat-list.tsx index 98cb01ca..e8a14988 100644 --- a/frontend/src/components/chat/chat-list.tsx +++ b/frontend/src/components/chat/chat-list.tsx @@ -3,7 +3,7 @@ import React, { useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; import { cn } from '@/lib/utils'; -import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'; +import { Avatar, AvatarFallback, AvatarImage, SmallAvatar } from '../ui/avatar'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import CodeDisplayBlock from '../code-display-block'; @@ -155,7 +155,7 @@ export default function ChatList({ )} - + {user.username?.substring(0, 2).toUpperCase()} - + ) : (
- + - + {renderMessageContent(message.content)} diff --git a/frontend/src/components/edit-username-form.tsx b/frontend/src/components/edit-username-form.tsx index d4bd7733..03b5cfc0 100644 --- a/frontend/src/components/edit-username-form.tsx +++ b/frontend/src/components/edit-username-form.tsx @@ -1,22 +1,52 @@ 'use client'; -import { set, z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { Button } from '@/components/ui/button'; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; -import React, { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import React, { useEffect, useMemo, useState } from 'react'; import { ModeToggle } from './mode-toggle'; import { toast } from 'sonner'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import Image from 'next/image'; +import { ActivityCalendar } from 'react-activity-calendar'; +import { TeamSelector } from './team-selector'; + +const data = [ + { + date: '2024-01-01', + count: 2, + level: 0, + }, + { + date: '2024-06-23', + count: 2, + level: 1, + }, + { + date: '2024-08-02', + count: 16, + level: 4, + }, + { + date: '2024-11-29', + count: 11, + level: 3, + }, + { + date: '2024-12-29', + count: 11, + level: 0, + }, +]; const formSchema = z.object({ username: z.string().min(2, { @@ -24,13 +54,14 @@ const formSchema = z.object({ }), }); -interface EditUsernameFormProps { - setOpen: React.Dispatch>; -} - -export default function EditUsernameForm({ setOpen }: EditUsernameFormProps) { +export default function EditUsernameForm() { const [name, setName] = useState(''); + const avatarFallback = useMemo(() => { + if (!name) return 'US'; + return name.substring(0, 2).toUpperCase(); + }, [name]); + useEffect(() => { setName(localStorage.getItem('ollama_user') || 'Anonymous'); }, []); @@ -53,36 +84,88 @@ export default function EditUsernameForm({ setOpen }: EditUsernameFormProps) { form.setValue('username', e.currentTarget.value); setName(e.currentTarget.value); }; - return ( -
-
- Theme - +
+

User Settings

+
+
+ +
+ {/* Profile Picture Section */} +
+

Profile Picture

+
+

You look good today!

+ + + {avatarFallback} + +
+
+
+ + {/* Username and Description Section */} +
+

Profile Information

+ + + ( + +
+
+ Username +

+ Select your interface color scheme. +

+
+
+ {' '} + + handleChange(e)} + /> + + +
+
+
+ )} + /> + + +
+
+ + {/* Interface Theme Section */} +
+

Interface Theme

+
+

+ Select your interface color scheme. +

+ +
+
+ +
+
+

Default Team

+
+

+ New projects and deployments from your personal scope will be + created in the codesfox team. +

+ +
+
-
- ( - - Name - -
- handleChange(e)} - /> - -
-
- -
- )} - /> - - +
); } diff --git a/frontend/src/components/enum.ts b/frontend/src/components/enum.ts index 83656be3..f59e6629 100644 --- a/frontend/src/components/enum.ts +++ b/frontend/src/components/enum.ts @@ -1,3 +1,5 @@ export enum EventEnum { NEW_CHAT = 'newchat', // event name 'newchat', help to monitor the change of url + SETTING = 'setting', + CHAT = 'chat', } diff --git a/frontend/src/components/mode-toggle.tsx b/frontend/src/components/mode-toggle.tsx index e5c19b31..ef165b4b 100644 --- a/frontend/src/components/mode-toggle.tsx +++ b/frontend/src/components/mode-toggle.tsx @@ -18,7 +18,7 @@ export function ModeToggle() { return ( - + + + setTheme('light')}> + Light mode + + setTheme('dark')}> + Dark mode + + + + ); +} diff --git a/frontend/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx index 1346957c..120c9ad8 100644 --- a/frontend/src/components/ui/avatar.tsx +++ b/frontend/src/components/ui/avatar.tsx @@ -12,7 +12,7 @@ const Avatar = React.forwardRef< , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SmallAvatar.displayName = AvatarPrimitive.Root.displayName; + const AvatarImage = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -47,4 +62,4 @@ const AvatarFallback = React.forwardRef< )); AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; -export { Avatar, AvatarImage, AvatarFallback }; +export { Avatar, AvatarImage, AvatarFallback, SmallAvatar }; diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 34bd2f08..3cef44af 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -21,7 +21,8 @@ const buttonVariants = cva( link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: 'h-9', + default: 'h-9 px-4 py-2', + setting: 'h-9', sm: 'h-8 rounded-md px-3 text-xs', lg: 'h-10 rounded-md px-8', icon: 'h-9 w-9', diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx index c661c54e..c5809162 100644 --- a/frontend/src/components/ui/form.tsx +++ b/frontend/src/components/ui/form.tsx @@ -78,7 +78,7 @@ const FormItem = React.forwardRef< return ( -
+
); }); diff --git a/frontend/src/components/user-settings.tsx b/frontend/src/components/user-settings.tsx index a6179a66..6e580d9b 100644 --- a/frontend/src/components/user-settings.tsx +++ b/frontend/src/components/user-settings.tsx @@ -8,21 +8,17 @@ import { } from '@/components/ui/dropdown-menu'; import PullModel from './pull-model'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + AvatarFallback, + AvatarImage, + SmallAvatar, +} from '@/components/ui/avatar'; import { GearIcon } from '@radix-ui/react-icons'; import { Button } from '@/components/ui/button'; import { useAuth } from '@/app/hooks/useAuth'; import { useRouter } from 'next/navigation'; import { useMemo, useState, memo } from 'react'; -import { - Dialog, - DialogTrigger, - DialogContent, - DialogTitle, -} from '@radix-ui/react-dialog'; -import EditUsernameForm from './edit-username-form'; -import { DialogHeader } from './ui/dialog'; -import DetailSettings from './detail-settings'; +import { EventEnum } from './enum'; interface UserSettingsProps { isSimple: boolean; @@ -53,15 +49,16 @@ export const UserSettings = ({ isSimple }: UserSettingsProps) => { const avatarButton = useMemo(() => { return ( ); @@ -72,10 +69,17 @@ export const UserSettings = ({ isSimple }: UserSettingsProps) => { {avatarButton} e.preventDefault()}> - - - e.preventDefault()}> - +
{ + window.history.replaceState({}, '', '/?id=setting'); + const event = new Event(EventEnum.SETTING); + window.dispatchEvent(event); + }} + > + + Settings +
=16.0.0} deprecated: This package is no longer supported. + requiresBuild: true dependencies: delegates: 1.0.0 readable-stream: 3.6.2 @@ -9977,7 +9981,7 @@ packages: resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} dependencies: follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.0 + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -11565,6 +11569,10 @@ packages: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} dev: true + /date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dev: false + /dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} dev: false @@ -13344,6 +13352,7 @@ packages: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + requiresBuild: true dependencies: aproba: 2.0.0 color-support: 1.1.3 @@ -19314,6 +19323,15 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-activity-calendar@2.7.8(react@18.3.1): + resolution: {integrity: sha512-lj9IIMrRAoMsXSf6wWo7AcMNXie61Y5EuNApm6rVdJswngyw8LS2Ja50yHrGBFu9GaL1HMutGIoSbr5ifEi9xw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + dependencies: + date-fns: 4.1.0 + react: 18.3.1 + dev: false + /react-code-blocks@0.1.6(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-ENNuxG07yO+OuX1ChRje3ieefPRz6yrIpHmebQlaFQgzcAHbUfVeTINpOpoI9bSRSObeYo/OdHsporeToZ7fcg==} engines: {node: '>=16'}