Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/chat/chat.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/(main)/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
64 changes: 45 additions & 19 deletions frontend/src/app/(main)/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<Message[]>([]);
Expand Down Expand Up @@ -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({
Expand All @@ -66,18 +86,24 @@ export default function Home() {
});

return (
<ChatContent
chatId={chatId}
setSelectedModel={setSelectedModel}
messages={messages}
input={input}
handleInputChange={handleInputChange}
handleSubmit={handleSubmit}
loadingSubmit={loadingSubmit}
stop={stop}
formRef={formRef}
setInput={setInput}
setMessages={setMessages}
/>
<>
{chatId === EventEnum.SETTING.toString() ? (
<EditUsernameForm />
) : (
<ChatContent
chatId={chatId}
setSelectedModel={setSelectedModel}
messages={messages}
input={input}
handleInputChange={handleInputChange}
handleSubmit={handleSubmit}
loadingSubmit={loadingSubmit}
stop={stop}
formRef={formRef}
setInput={setInput}
setMessages={setMessages}
/>
)}
</>
);
}
10 changes: 5 additions & 5 deletions frontend/src/components/chat/chat-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -155,7 +155,7 @@ export default function ChatList({
</>
)}
</div>
<Avatar className="flex justify-start items-center overflow-hidden">
<SmallAvatar className="flex justify-start items-center overflow-hidden">
<AvatarImage
src="/"
alt="user"
Expand All @@ -164,17 +164,17 @@ export default function ChatList({
<AvatarFallback>
{user.username?.substring(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
</SmallAvatar>
</div>
) : (
<div className="flex items-end gap-2">
<Avatar className="flex justify-start items-center">
<SmallAvatar className="flex justify-start items-center">
<AvatarImage
src="/codefox.svg"
alt="AI"
className="h-full w-full object-contain dark:invert"
/>
</Avatar>
</SmallAvatar>
<span className="bg-accent p-3 rounded-md max-w-xs sm:max-w-2xl overflow-x-auto">
{renderMessageContent(message.content)}
</span>
Expand Down
163 changes: 123 additions & 40 deletions frontend/src/components/edit-username-form.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,67 @@
'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, {
message: 'Name must be at least 2 characters.',
}),
});

interface EditUsernameFormProps {
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

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');
}, []);
Expand All @@ -53,36 +84,88 @@ export default function EditUsernameForm({ setOpen }: EditUsernameFormProps) {
form.setValue('username', e.currentTarget.value);
setName(e.currentTarget.value);
};

return (
<Form {...form}>
<div className="w-full flex flex-col gap-4 pt-8">
<FormLabel>Theme</FormLabel>
<ModeToggle />
<div className="w-[60%] pt-10">
<h1 className="text-3xl font-semibold mb-8">User Settings</h1>
<div className="space-y-8">
<div className="w-[100%] flex justify-center">
<ActivityCalendar data={data} blockSize={12} blockMargin={5} />
</div>
{/* Profile Picture Section */}
<div className="space-y-2">
<h2 className="text-lg font-medium">Profile Picture</h2>
<div className="flex items-center justify-between">
<p className="text-muted-foreground">You look good today!</p>
<Avatar className="flex items-center justify-center">
<AvatarImage src="" alt="User" />
<AvatarFallback>{avatarFallback}</AvatarFallback>
</Avatar>
</div>
</div>
<div className="bg-border h-px" />

{/* Username and Description Section */}
<div className="space-y-2">
<h2 className="text-lg font-medium">Profile Information</h2>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-4">
<div className="flex-1">
<FormLabel>Username</FormLabel>
<p className="text-muted-foreground">
Select your interface color scheme.
</p>
</div>
<div className="w-[200px]">
{' '}
<FormControl>
<Input
{...field}
type="text"
value={name}
className="w-full rounded-[10px]"
onChange={(e) => handleChange(e)}
/>
</FormControl>
<FormMessage />
</div>
</div>
</FormItem>
)}
/>
</form>
</Form>
</div>
<div className="h-px bg-border" />

{/* Interface Theme Section */}
<div className="space-y-2">
<h2 className="text-lg font-medium">Interface Theme</h2>
<div className="flex items-center justify-between">
<p className="text-muted-foreground">
Select your interface color scheme.
</p>
<ModeToggle />
</div>
</div>

<div className="h-px bg-border" />
<div className="space-y-2">
<h2 className="text-lg font-medium">Default Team</h2>
<div className="flex items-center justify-between">
<p className="w-[50%] text-muted-foreground">
New projects and deployments from your personal scope will be
created in the codesfox team.
</p>
<TeamSelector />
</div>
</div>
</div>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<div className="md:flex gap-4">
<Input
{...field}
type="text"
value={name}
onChange={(e) => handleChange(e)}
/>
<Button type="submit">Change name</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
);
}
2 changes: 2 additions & 0 deletions frontend/src/components/enum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export enum EventEnum {
NEW_CHAT = 'newchat', // event name 'newchat', help to monitor the change of url
SETTING = 'setting',
CHAT = 'chat',
}
2 changes: 1 addition & 1 deletion frontend/src/components/mode-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function ModeToggle() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="justify-start">
<Button variant="outline" className="justify-start rounded-[10px]">
{theme === 'light' && (
<div className="flex justify-between w-full scale-100 dark:scale-0">
<p>Light mode</p>
Expand Down
Loading
Loading