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
3 changes: 2 additions & 1 deletion codefox-common/src/common-path.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as path from 'path';

import fsExtra from 'fs-extra';
import { cwd } from 'process';
const { existsSync, mkdirSync, promises, writeFileSync } = fsExtra;

// Constants for base directories
const APP_NAME = 'codefox';
// TODO: hack way to get the root directory of the workspace

const WORKSPACE_ROOT = path.resolve(path.join(__dirname, '..', '..', '..'));
const WORKSPACE_ROOT = path.resolve(cwd(), '..');
Comment on lines 9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace working directory hack with a more robust solution.

Using process.cwd() makes the workspace root path dependent on the current working directory, which could lead to inconsistencies. Consider using environment variables or a configuration file to define the workspace root.

-// TODO: hack way to get the root directory of the workspace
-import { cwd } from 'process';
-const WORKSPACE_ROOT = path.resolve(cwd(), '..');
+import { env } from 'process';
+
+const WORKSPACE_ROOT = env.WORKSPACE_ROOT
+  ? path.resolve(env.WORKSPACE_ROOT)
+  : path.resolve(__dirname, '..', '..', '..');
+
+if (!existsSync(WORKSPACE_ROOT)) {
+  throw new Error(`Workspace root not found: ${WORKSPACE_ROOT}`);
+}

Committable suggestion skipped: line range outside the PR's diff.


const ROOT_DIR = path.join(WORKSPACE_ROOT, `.${APP_NAME}`);

Expand Down
5 changes: 4 additions & 1 deletion frontend/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@hookform/resolvers": "^3.9.0",
"@langchain/community": "^0.3.1",
"@langchain/core": "^0.3.3",
"@monaco-editor/react": "^4.6.0",
"@nestjs/common": "^10.4.6",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.4",
Expand All @@ -32,10 +33,12 @@
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@types/dom-speech-recognition": "^0.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"codefox-common": "workspace:*",
"emoji-mart": "^5.6.0",
"framer-motion": "^11.5.6",
"graphql": "^16.9.0",
Expand All @@ -46,6 +49,7 @@
"react": "^18.3.1",
"react-activity-calendar": "^2.7.8",
"react-code-blocks": "^0.1.6",
"react-complex-tree": "^2.4.6",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.9",
"react-hook-form": "^7.53.0",
Expand Down
147 changes: 147 additions & 0 deletions frontend/src/app/(main)/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// app/page.tsx or components/Home.tsx
'use client';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from '@/components/ui/resizable';
import { CodeEngine } from '@/components/code-engine/code-engine';
import { GET_CHAT_HISTORY } from '@/graphql/request';
import { useQuery } from '@apollo/client';
import { toast } from 'sonner';
import { EventEnum } from '@/components/enum';
import { useModels } from '../hooks/useModels';
import { useChatList } from '../hooks/useChatList';
import { useChatStream } from '../hooks/useChatStream';
import EditUsernameForm from '@/components/edit-username-form';
import ChatContent from '@/components/chat/chat';
import { ProjectContext } from '@/components/code-engine/project-context';

export default function Home() {
// Initialize state, refs, and custom hooks
const urlParams = new URLSearchParams(window.location.search);
const [chatId, setChatId] = useState('');
const [messages, setMessages] = useState<any[]>([]);
const [input, setInput] = useState('');
const formRef = useRef<HTMLFormElement>(null);

const { models } = useModels();
const [selectedModel, setSelectedModel] = useState(models[0] || 'gpt-4o');

const { refetchChats } = useChatList();

//TODO: adding project id from .codefox/projects
const [projectId, setProjectId] = useState(
'2025-02-02-dfca4698-6e9b-4aab-9fcb-98e9526e5f21'
);
const [filePath, setFilePath] = useState('frontend/vite.config.ts');

// Apollo query to fetch chat history
useQuery(GET_CHAT_HISTORY, {
variables: { chatId },
onCompleted: (data) => {
if (data?.getChatHistory) {
setMessages(data.getChatHistory);
}
},
onError: () => {
toast.error('Failed to load chat history');
},
});

// Custom hook for handling chat streaming
const { loadingSubmit, handleSubmit, handleInputChange, stop } =
useChatStream({
chatId,
input,
setInput,
setMessages,
selectedModel,
});

// Callback to clear the chat ID
const cleanChatId = () => setChatId('');

// Callback to update chat ID based on URL parameters and refresh the chat list
const updateChatId = useCallback(() => {
const params = new URLSearchParams(window.location.search);
setChatId(params.get('id') || '');
refetchChats();
}, [refetchChats]);

// Callback to switch to the settings view
const updateSetting = () => setChatId(EventEnum.SETTING);

// Effect to initialize chat ID and refresh the chat list based on URL parameters
useEffect(() => {
setChatId(urlParams.get('id') || '');
refetchChats();
}, [urlParams, refetchChats]);

// Effect to add and remove global event listeners
useEffect(() => {
window.addEventListener(EventEnum.CHAT, updateChatId);
window.addEventListener(EventEnum.NEW_CHAT, cleanChatId);
window.addEventListener(EventEnum.SETTING, updateSetting);
return () => {
window.removeEventListener(EventEnum.CHAT, updateChatId);
window.removeEventListener(EventEnum.NEW_CHAT, cleanChatId);
window.removeEventListener(EventEnum.SETTING, updateSetting);
};
}, [updateChatId]);

// Render the settings view if chatId indicates settings mode
if (chatId === EventEnum.SETTING) {
return (
<div className="h-full w-full flex items-center justify-center">
<EditUsernameForm />
</div>
);
}

// Render the main layout
return (
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
<ResizablePanel
defaultSize={50}
minSize={20}
maxSize={80}
className="h-full"
>
<ChatContent
chatId={chatId}
setSelectedModel={setSelectedModel}
messages={messages}
input={input}
handleInputChange={handleInputChange}
handleSubmit={handleSubmit}
loadingSubmit={loadingSubmit}
stop={stop}
formRef={formRef}
setInput={setInput}
setMessages={setMessages}
/>
</ResizablePanel>

<ResizableHandle withHandle className="hidden md:flex" />

{projectId ? (
<ResizablePanel
defaultSize={50}
minSize={20}
maxSize={80}
className="h-full overflow-auto"
>
<ProjectContext.Provider
value={{ projectId, setProjectId, filePath, setFilePath }}
>
<CodeEngine />
</ProjectContext.Provider>
</ResizablePanel>
) : (
<h1>Forgot to input project id</h1>
)}
</ResizablePanelGroup>
);
}
90 changes: 36 additions & 54 deletions frontend/src/app/(main)/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// components/MainLayout.tsx
'use client';

import React, { useEffect, useState } from 'react';
import { cn } from '@/lib/utils';
import { usePathname } from 'next/navigation';
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from '@/components/ui/resizable';
import { SidebarProvider } from '@/components/ui/sidebar';
import { ChatSideBar } from '@/components/sidebar';
Comment on lines 4 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused imports.

The following imports are not used in the code:

  • cn from '@/lib/utils'
  • ResizableHandle from '@/components/ui/resizable'

Apply this diff to clean up imports:

-import { cn } from '@/lib/utils';
import {
-  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from '@/components/ui/resizable';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { cn } from '@/lib/utils';
import { usePathname } from 'next/navigation';
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from '@/components/ui/resizable';
import { SidebarProvider } from '@/components/ui/sidebar';
import { ChatSideBar } from '@/components/sidebar';
import {
ResizablePanel,
ResizablePanelGroup,
} from '@/components/ui/resizable';
import { SidebarProvider } from '@/components/ui/sidebar';
import { ChatSideBar } from '@/components/sidebar';
🧰 Tools
🪛 GitHub Actions: autofix.ci

[warning] 4-4: 'cn' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars


[warning] 6-6: 'ResizableHandle' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars

import { useChatList } from '../hooks/useChatList';
import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
import CustomSidebar from '@/components/sidebar';
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';

export default function MainLayout({
children,
Expand All @@ -15,8 +18,8 @@ export default function MainLayout({
}) {
const [isCollapsed, setIsCollapsed] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const defaultLayout = [30, 160];
const navCollapsedSize = 10;
const defaultLayout = [25, 75]; // [sidebar, main]
const navCollapsedSize = 5;
const {
chats,
loading,
Expand All @@ -36,63 +39,42 @@ export default function MainLayout({
};
checkScreenWidth();
window.addEventListener('resize', checkScreenWidth);
return () => {
window.removeEventListener('resize', checkScreenWidth);
};
return () => window.removeEventListener('resize', checkScreenWidth);
}, []);

console.log(`${isCollapsed}, ${isMobile}`);

return (
<main className="flex h-[calc(100dvh)] flex-col items-center">
<ResizablePanelGroup
direction="horizontal"
autoSaveId="main-layout"
onLayout={(sizes: number[]) => {
document.cookie = `react-resizable-panels:layout=${JSON.stringify(
sizes
)}; path=/; max-age=604800`;
const sidebarSize = sizes[0];
const isNowCollapsed = sidebarSize < 10;
setIsCollapsed(isNowCollapsed);

if (isNowCollapsed && sizes.length > 1) {
const newSizes = [navCollapsedSize, 100 - navCollapsedSize];
document.cookie = `react-resizable-panels:layout=${JSON.stringify(newSizes)}; path=/; max-age=604800`;
return newSizes;
}

document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}; path=/; max-age=604800`;
return sizes;
}}
className="h-screen items-stretch"
className="h-screen items-stretch w-full"
>
<SidebarProvider>
<ResizablePanel
defaultSize={defaultLayout[0]}
collapsedSize={navCollapsedSize}
collapsible={true}
minSize={isMobile ? 4 : 12}
maxSize={isMobile ? 10 : 16}
onCollapse={() => {
console.log(`setting collapse to T`);
// setIsCollapsed(true);
}}
onExpand={() => {
console.log(`setting collapse to F`);
// setIsCollapsed(false);
}}
className={cn(
'transition-all duration-300 ease-in-out',
isCollapsed ? 'min-w-[50px] md:min-w-[70px]' : 'md:min-w-[200px]'
)}
>
{loading ? (
<div className="flex justify-center items-center">Loading...</div>
) : error ? (
<div className="flex justify-center items-center text-red-500">
Error: {error.message}
</div>
) : (
<CustomSidebar
isCollapsed={isCollapsed}
isMobile={isMobile}
chatListUpdated={chatListUpdated}
setChatListUpdated={setChatListUpdated}
chats={chats}
loading={loading}
error={error}
onRefetch={refetchChats}
/>
)}
</ResizablePanel>
<ChatSideBar
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
isMobile={isMobile}
chatListUpdated={chatListUpdated}
setChatListUpdated={setChatListUpdated}
chats={chats}
loading={loading}
error={error}
onRefetch={refetchChats}
/>

<ResizablePanel
className="h-full w-full flex justify-center"
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Inter } from 'next/font/google';
import MainLayout from './MainLayout';
import { SidebarProvider } from '@/components/ui/sidebar';

import { getProjectPath, getProjectsDir, getRootDir } from 'codefox-common';
import { FileReader } from '@/utils/file-reader';
const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
Expand All @@ -17,10 +19,10 @@ export const viewport: Viewport = {
userScalable: false,
};

export default function Layout({
export default async function Layout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
}) {
return <MainLayout>{children}</MainLayout>;
}
Loading
Loading