Merge branch 'hackathon/copilot' of github.com:Significant-Gravitas/AutoGPT into hackathon/copilot

This commit is contained in:
Swifty
2025-12-16 15:23:59 +01:00
39 changed files with 504 additions and 111 deletions

View File

@@ -1,4 +1,4 @@
.PHONY: start-core stop-core logs-core format lint migrate run-backend run-frontend load-store-agents
.PHONY: start-core stop-core logs-core format lint migrate run-backend stop-backend run-frontend load-store-agents
# Run just Supabase + Redis + RabbitMQ
start-core:
@@ -34,7 +34,14 @@ migrate:
cd backend && poetry run prisma migrate deploy
cd backend && poetry run prisma generate
run-backend:
stop-backend:
@echo "Stopping backend processes..."
@cd backend && poetry run cli stop 2>/dev/null || true
@echo "Killing any processes using backend ports..."
@lsof -ti:8001,8002,8003,8004,8005,8006,8007 | xargs kill -9 2>/dev/null || true
@echo "Backend stopped"
run-backend: stop-backend
cd backend && poetry run app
run-frontend:
@@ -55,7 +62,8 @@ help:
@echo " logs-core - Tail the logs for core services"
@echo " format - Format & lint backend (Python) and frontend (TypeScript) code"
@echo " migrate - Run backend database migrations"
@echo " run-backend - Run the backend FastAPI server"
@echo " stop-backend - Stop any running backend processes"
@echo " run-backend - Run the backend FastAPI server (stops existing processes first)"
@echo " run-frontend - Run the frontend Next.js development server"
@echo " test-data - Run the test data creator"
@echo " load-store-agents - Load store agents from agents/ folder into test database"

View File

@@ -1,10 +1,10 @@
import { Alert, AlertDescription } from "@/components/molecules/Alert/Alert";
import { Text } from "@/components/atoms/Text/Text";
import Link from "next/link";
import { useGetV2GetLibraryAgentByGraphId } from "@/app/api/__generated__/endpoints/library/library";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { useQueryStates, parseAsString } from "nuqs";
import { isValidUUID } from "@/app/(platform)/chat/helpers";
import { Text } from "@/components/atoms/Text/Text";
import { isValidUUID } from "@/components/contextual/Chat/helpers";
import { Alert, AlertDescription } from "@/components/molecules/Alert/Alert";
import Link from "next/link";
import { parseAsString, useQueryStates } from "nuqs";
export const WebhookDisclaimer = ({ nodeId }: { nodeId: string }) => {
const [{ flowID }] = useQueryStates({

View File

@@ -1,16 +1,24 @@
"use client";
import { useChatPage } from "./useChatPage";
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatErrorState } from "./components/ChatErrorState/ChatErrorState";
import { ChatLoadingState } from "./components/ChatLoadingState/ChatLoadingState";
import { useGetFlag, Flag } from "@/services/feature-flags/use-get-flag";
import { useRouter } from "next/navigation";
import { Button } from "@/components/__legacy__/ui/button";
import { scrollbarStyles } from "@/components/styles/scrollbars";
import { cn } from "@/lib/utils";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { X } from "@phosphor-icons/react";
import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react";
import { Drawer } from "vaul";
import { ChatContainer } from "@/components/contextual/Chat/components/ChatContainer/ChatContainer";
import { ChatErrorState } from "@/components/contextual/Chat/components/ChatErrorState/ChatErrorState";
import { ChatLoadingState } from "@/components/contextual/Chat/components/ChatLoadingState/ChatLoadingState";
import { useChatPage } from "./useChatPage";
export default function ChatPage() {
const isChatEnabled = useGetFlag(Flag.CHAT);
const router = useRouter();
const pathname = usePathname();
const isOpen = pathname === "/chat";
const {
messages,
isLoading,
@@ -28,56 +36,88 @@ export default function ChatPage() {
}
}, [isChatEnabled, router]);
function handleOpenChange(open: boolean) {
if (!open) {
router.replace("/marketplace");
}
}
if (isChatEnabled === null || isChatEnabled === false) {
return null;
}
return (
<div className="flex h-full flex-col">
{/* Header */}
<header className="border-b border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-900">
<div className="container mx-auto flex items-center justify-between">
<h1 className="text-xl font-semibold">Chat</h1>
{sessionId && (
<div className="flex items-center gap-4">
<span className="text-sm text-zinc-600 dark:text-zinc-400">
Session: {sessionId.slice(0, 8)}...
</span>
<button
onClick={clearSession}
className="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
New Chat
</button>
</div>
<Drawer.Root
open={isOpen}
onOpenChange={handleOpenChange}
direction="right"
modal={false}
>
<Drawer.Portal>
<Drawer.Content
className={cn(
"fixed right-0 top-0 z-50 flex h-full w-1/2 flex-col border-l border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900",
scrollbarStyles,
)}
</div>
</header>
>
{/* Header */}
<header className="shrink-0 border-b border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-900">
<div className="flex items-center justify-between">
<Drawer.Title className="text-xl font-semibold">
Chat
</Drawer.Title>
<div className="flex items-center gap-4">
{sessionId && (
<>
<span className="text-sm text-zinc-600 dark:text-zinc-400">
Session: {sessionId.slice(0, 8)}...
</span>
<button
onClick={clearSession}
className="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
New Chat
</button>
</>
)}
<Button
variant="link"
aria-label="Close"
onClick={() => handleOpenChange(false)}
className="!focus-visible:ring-0 p-0"
>
<X width="1.5rem" />
</Button>
</div>
</div>
</header>
{/* Main Content */}
<main className="container mx-auto flex flex-1 flex-col overflow-hidden">
{/* Loading State - show when explicitly loading/creating OR when we don't have a session yet and no error */}
{(isLoading || isCreating || (!sessionId && !error)) && (
<ChatLoadingState
message={isCreating ? "Creating session..." : "Loading..."}
/>
)}
{/* Main Content */}
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
{/* Loading State - show when explicitly loading/creating OR when we don't have a session yet and no error */}
{(isLoading || isCreating || (!sessionId && !error)) && (
<ChatLoadingState
message={isCreating ? "Creating session..." : "Loading..."}
/>
)}
{/* Error State */}
{error && !isLoading && (
<ChatErrorState error={error} onRetry={createSession} />
)}
{/* Error State */}
{error && !isLoading && (
<ChatErrorState error={error} onRetry={createSession} />
)}
{/* Session Content */}
{sessionId && !isLoading && !error && (
<ChatContainer
sessionId={sessionId}
initialMessages={messages}
onRefreshSession={refreshSession}
className="flex-1"
/>
)}
</main>
</div>
{/* Session Content */}
{sessionId && !isLoading && !error && (
<ChatContainer
sessionId={sessionId}
initialMessages={messages}
onRefreshSession={refreshSession}
className="flex-1"
/>
)}
</main>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}

View File

@@ -1,11 +1,11 @@
"use client";
import { useEffect, useRef } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "sonner";
import { useChatSession } from "@/app/(platform)/chat/useChatSession";
import { useChatSession } from "@/components/contextual/Chat/useChatSession";
import { useChatStream } from "@/components/contextual/Chat/useChatStream";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useChatStream } from "@/app/(platform)/chat/useChatStream";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useRef } from "react";
import { toast } from "sonner";
export function useChatPage() {
const router = useRouter();

View File

@@ -1,6 +1,7 @@
import { ChatDrawer } from "@/components/contextual/Chat/ChatDrawer";
import { Navbar } from "@/components/layout/Navbar/Navbar";
import { AdminImpersonationBanner } from "./admin/components/AdminImpersonationBanner";
import { ReactNode } from "react";
import { AdminImpersonationBanner } from "./admin/components/AdminImpersonationBanner";
export default function PlatformLayout({ children }: { children: ReactNode }) {
return (
@@ -8,6 +9,7 @@ export default function PlatformLayout({ children }: { children: ReactNode }) {
<Navbar />
<AdminImpersonationBanner />
<section className="flex-1">{children}</section>
<ChatDrawer />
</main>
);
}

View File

@@ -0,0 +1,118 @@
"use client";
import { Button } from "@/components/__legacy__/ui/button";
import { scrollbarStyles } from "@/components/styles/scrollbars";
import { cn } from "@/lib/utils";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { X } from "@phosphor-icons/react";
import { useEffect } from "react";
import { Drawer } from "vaul";
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatErrorState } from "./components/ChatErrorState/ChatErrorState";
import { ChatLoadingState } from "./components/ChatLoadingState/ChatLoadingState";
import { useChat } from "./useChat";
import { useChatDrawer } from "./useChatDrawer";
export function ChatDrawer() {
const isChatEnabled = useGetFlag(Flag.CHAT);
const { isOpen, close } = useChatDrawer();
const {
messages,
isLoading,
isCreating,
error,
sessionId,
createSession,
clearSession,
refreshSession,
} = useChat();
useEffect(() => {
if (isChatEnabled === false && isOpen) {
close();
}
}, [isChatEnabled, isOpen, close]);
if (isChatEnabled === null || isChatEnabled === false) {
return null;
}
return (
<Drawer.Root
open={isOpen}
onOpenChange={(open) => {
if (!open) {
close();
}
}}
direction="right"
modal={false}
>
<Drawer.Portal>
<Drawer.Content
className={cn(
"fixed right-0 top-0 z-50 flex h-full w-1/2 flex-col border-l border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900",
scrollbarStyles,
)}
>
{/* Header */}
<header className="shrink-0 border-b border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-900">
<div className="flex items-center justify-between">
<Drawer.Title className="text-xl font-semibold">
Chat
</Drawer.Title>
<div className="flex items-center gap-4">
{sessionId && (
<>
<span className="text-sm text-zinc-600 dark:text-zinc-400">
Session: {sessionId.slice(0, 8)}...
</span>
<button
onClick={clearSession}
className="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
New Chat
</button>
</>
)}
<Button
variant="link"
aria-label="Close"
onClick={close}
className="!focus-visible:ring-0 p-0"
>
<X width="1.5rem" />
</Button>
</div>
</div>
</header>
{/* Main Content */}
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
{/* Loading State - show when explicitly loading/creating OR when we don't have a session yet and no error */}
{(isLoading || isCreating || (!sessionId && !error)) && (
<ChatLoadingState
message={isCreating ? "Creating session..." : "Loading..."}
/>
)}
{/* Error State */}
{error && !isLoading && (
<ChatErrorState error={error} onRetry={createSession} />
)}
{/* Session Content */}
{sessionId && !isLoading && !error && (
<ChatContainer
sessionId={sessionId}
initialMessages={messages}
onRefreshSession={refreshSession}
className="flex-1"
/>
)}
</main>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}

View File

@@ -1,9 +1,9 @@
import { cn } from "@/lib/utils";
import { ChatInput } from "@/app/(platform)/chat/components/ChatInput/ChatInput";
import { MessageList } from "@/app/(platform)/chat/components/MessageList/MessageList";
import { QuickActionsWelcome } from "@/app/(platform)/chat/components/QuickActionsWelcome/QuickActionsWelcome";
import { useChatContainer } from "./useChatContainer";
import type { SessionDetailResponse } from "@/app/api/__generated__/models/sessionDetailResponse";
import { cn } from "@/lib/utils";
import { ChatInput } from "../ChatInput/ChatInput";
import { MessageList } from "../MessageList/MessageList";
import { QuickActionsWelcome } from "../QuickActionsWelcome/QuickActionsWelcome";
import { useChatContainer } from "./useChatContainer";
export interface ChatContainerProps {
sessionId: string | null;

View File

@@ -1,14 +1,14 @@
import type { StreamChunk } from "@/components/contextual/Chat/useChatStream";
import { toast } from "sonner";
import type { StreamChunk } from "@/app/(platform)/chat/useChatStream";
import type { HandlerDependencies } from "./useChatContainer.handlers";
import {
handleError,
handleLoginNeeded,
handleStreamEnd,
handleTextChunk,
handleTextEnded,
handleToolCallStart,
handleToolResponse,
handleLoginNeeded,
handleStreamEnd,
handleError,
} from "./useChatContainer.handlers";
export function createStreamEventDispatcher(

View File

@@ -1,5 +1,5 @@
import type { ChatMessageData } from "@/app/(platform)/chat/components/ChatMessage/useChatMessage";
import type { ToolResult } from "@/types/chat";
import type { ChatMessageData } from "../ChatMessage/useChatMessage";
export function createUserMessage(content: string): ChatMessageData {
return {

View File

@@ -1,7 +1,7 @@
import type { Dispatch, SetStateAction, MutableRefObject } from "react";
import type { StreamChunk } from "@/app/(platform)/chat/useChatStream";
import type { ChatMessageData } from "@/app/(platform)/chat/components/ChatMessage/useChatMessage";
import { parseToolResponse, extractCredentialsNeeded } from "./helpers";
import type { StreamChunk } from "@/components/contextual/Chat/useChatStream";
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
import type { ChatMessageData } from "../ChatMessage/useChatMessage";
import { extractCredentialsNeeded, parseToolResponse } from "./helpers";
export interface HandlerDependencies {
setHasTextChunks: Dispatch<SetStateAction<boolean>>;

View File

@@ -1,16 +1,16 @@
import { useState, useCallback, useRef, useMemo } from "react";
import { toast } from "sonner";
import { useChatStream } from "@/app/(platform)/chat/useChatStream";
import type { SessionDetailResponse } from "@/app/api/__generated__/models/sessionDetailResponse";
import type { ChatMessageData } from "@/app/(platform)/chat/components/ChatMessage/useChatMessage";
import { useChatStream } from "@/components/contextual/Chat/useChatStream";
import { useCallback, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import type { ChatMessageData } from "../ChatMessage/useChatMessage";
import { createStreamEventDispatcher } from "./createStreamEventDispatcher";
import {
parseToolResponse,
isValidMessage,
isToolCallArray,
createUserMessage,
filterAuthMessages,
isToolCallArray,
isValidMessage,
parseToolResponse,
} from "./helpers";
import { createStreamEventDispatcher } from "./createStreamEventDispatcher";
interface UseChatContainerArgs {
sessionId: string | null;

View File

@@ -1,17 +1,17 @@
"use client";
import { cn } from "@/lib/utils";
import { RobotIcon, UserIcon, CheckCircleIcon } from "@phosphor-icons/react";
import { useCallback } from "react";
import { MessageBubble } from "@/app/(platform)/chat/components/MessageBubble/MessageBubble";
import { MarkdownContent } from "@/app/(platform)/chat/components/MarkdownContent/MarkdownContent";
import { ToolCallMessage } from "@/app/(platform)/chat/components/ToolCallMessage/ToolCallMessage";
import { ToolResponseMessage } from "@/app/(platform)/chat/components/ToolResponseMessage/ToolResponseMessage";
import { AuthPromptWidget } from "@/app/(platform)/chat/components/AuthPromptWidget/AuthPromptWidget";
import { ChatCredentialsSetup } from "@/app/(platform)/chat/components/ChatCredentialsSetup/ChatCredentialsSetup";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { cn } from "@/lib/utils";
import { CheckCircleIcon, RobotIcon, UserIcon } from "@phosphor-icons/react";
import { useCallback } from "react";
import { getToolActionPhrase } from "../../helpers";
import { AuthPromptWidget } from "../AuthPromptWidget/AuthPromptWidget";
import { ChatCredentialsSetup } from "../ChatCredentialsSetup/ChatCredentialsSetup";
import { MarkdownContent } from "../MarkdownContent/MarkdownContent";
import { MessageBubble } from "../MessageBubble/MessageBubble";
import { ToolCallMessage } from "../ToolCallMessage/ToolCallMessage";
import { ToolResponseMessage } from "../ToolResponseMessage/ToolResponseMessage";
import { useChatMessage, type ChatMessageData } from "./useChatMessage";
import { getToolActionPhrase } from "@/app/(platform)/chat/helpers";
export interface ChatMessageProps {
message: ChatMessageData;
className?: string;
@@ -113,7 +113,6 @@ export function ChatMessage({
message={message.message}
sessionId={message.sessionId}
agentInfo={message.agentInfo}
returnUrl="/chat"
/>
</div>
);

View File

@@ -1,7 +1,7 @@
import { cn } from "@/lib/utils";
import { Robot } from "@phosphor-icons/react";
import { MessageBubble } from "@/app/(platform)/chat/components/MessageBubble/MessageBubble";
import { MarkdownContent } from "@/app/(platform)/chat/components/MarkdownContent/MarkdownContent";
import { MarkdownContent } from "../MarkdownContent/MarkdownContent";
import { MessageBubble } from "../MessageBubble/MessageBubble";
import { useStreamingMessage } from "./useStreamingMessage";
export interface StreamingMessageProps {

View File

@@ -1,7 +1,6 @@
import React from "react";
import { WrenchIcon } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import { getToolActionPhrase } from "@/app/(platform)/chat/helpers";
import { WrenchIcon } from "@phosphor-icons/react";
import { getToolActionPhrase } from "../../helpers";
export interface ToolCallMessageProps {
toolName: string;

View File

@@ -1,7 +1,6 @@
import React from "react";
import { WrenchIcon } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import { getToolActionPhrase } from "@/app/(platform)/chat/helpers";
import { WrenchIcon } from "@phosphor-icons/react";
import { getToolActionPhrase } from "../../helpers";
export interface ToolResponseMessageProps {
toolName: string;

View File

@@ -0,0 +1,73 @@
/**
* Maps internal tool names to user-friendly display names with emojis.
* @deprecated Use getToolActionPhrase or getToolCompletionPhrase for status messages
*
* @param toolName - The internal tool name from the backend
* @returns A user-friendly display name with an emoji prefix
*/
export function getToolDisplayName(toolName: string): string {
const toolDisplayNames: Record<string, string> = {
find_agent: "🔍 Search Marketplace",
get_agent_details: "📋 Get Agent Details",
check_credentials: "🔑 Check Credentials",
setup_agent: "⚙️ Setup Agent",
run_agent: "▶️ Run Agent",
get_required_setup_info: "📝 Get Setup Requirements",
};
return toolDisplayNames[toolName] || toolName;
}
/**
* Maps internal tool names to human-friendly action phrases (present continuous).
* Used for tool call messages to indicate what action is currently happening.
*
* @param toolName - The internal tool name from the backend
* @returns A human-friendly action phrase in present continuous tense
*/
export function getToolActionPhrase(toolName: string): string {
const toolActionPhrases: Record<string, string> = {
find_agent: "Looking for agents in the marketplace",
agent_carousel: "Looking for agents in the marketplace",
get_agent_details: "Learning about the agent",
check_credentials: "Checking your credentials",
setup_agent: "Setting up the agent",
execution_started: "Running the agent",
run_agent: "Running the agent",
get_required_setup_info: "Getting setup requirements",
schedule_agent: "Scheduling the agent to run",
};
// Return mapped phrase or generate human-friendly fallback
return toolActionPhrases[toolName] || toolName;
}
/**
* Maps internal tool names to human-friendly completion phrases (past tense).
* Used for tool response messages to indicate what action was completed.
*
* @param toolName - The internal tool name from the backend
* @returns A human-friendly completion phrase in past tense
*/
export function getToolCompletionPhrase(toolName: string): string {
const toolCompletionPhrases: Record<string, string> = {
find_agent: "Finished searching the marketplace",
get_agent_details: "Got agent details",
check_credentials: "Checked credentials",
setup_agent: "Agent setup complete",
run_agent: "Agent execution started",
get_required_setup_info: "Got setup requirements",
};
// Return mapped phrase or generate human-friendly fallback
return (
toolCompletionPhrases[toolName] ||
`Finished ${toolName.replace(/_/g, " ").replace("...", "")}`
);
}
/** Validate UUID v4 format */
export function isValidUUID(value: string): boolean {
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(value);
}

View File

@@ -0,0 +1,117 @@
"use client";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useEffect, useRef } from "react";
import { toast } from "sonner";
import { useChatSession } from "./useChatSession";
import { useChatStream } from "./useChatStream";
export function useChat() {
const hasCreatedSessionRef = useRef(false);
const hasClaimedSessionRef = useRef(false);
const { user } = useSupabase();
const { sendMessage: sendStreamMessage } = useChatStream();
const {
session,
sessionId: sessionIdFromHook,
messages,
isLoading,
isCreating,
error,
createSession,
refreshSession,
claimSession,
clearSession: clearSessionBase,
} = useChatSession({
urlSessionId: null,
autoCreate: false,
});
useEffect(
function autoCreateSession() {
if (!hasCreatedSessionRef.current && !isCreating && !sessionIdFromHook) {
hasCreatedSessionRef.current = true;
createSession().catch((_err) => {
hasCreatedSessionRef.current = false;
});
}
},
[isCreating, sessionIdFromHook, createSession],
);
useEffect(
function autoClaimSession() {
if (
session &&
!session.user_id &&
user &&
!hasClaimedSessionRef.current &&
!isLoading &&
sessionIdFromHook
) {
hasClaimedSessionRef.current = true;
claimSession(sessionIdFromHook)
.then(() => {
sendStreamMessage(
sessionIdFromHook,
"User has successfully logged in.",
() => {},
false,
).catch(() => {});
})
.catch(() => {
hasClaimedSessionRef.current = false;
});
}
},
[
session,
user,
isLoading,
sessionIdFromHook,
claimSession,
sendStreamMessage,
],
);
useEffect(function monitorNetworkStatus() {
function handleOnline() {
toast.success("Connection restored", {
description: "You're back online",
});
}
function handleOffline() {
toast.error("You're offline", {
description: "Check your internet connection",
});
}
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
function clearSession() {
clearSessionBase();
hasCreatedSessionRef.current = false;
hasClaimedSessionRef.current = false;
}
return {
session,
messages,
isLoading,
isCreating,
error,
createSession,
refreshSession,
clearSession,
sessionId: sessionIdFromHook,
};
}

View File

@@ -0,0 +1,17 @@
"use client";
import { create } from "zustand";
interface ChatDrawerState {
isOpen: boolean;
open: () => void;
close: () => void;
toggle: () => void;
}
export const useChatDrawer = create<ChatDrawerState>((set) => ({
isOpen: false,
open: () => set({ isOpen: true }),
close: () => set({ isOpen: false }),
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}));

View File

@@ -1,16 +1,16 @@
import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
usePostV2CreateSession,
getGetV2GetSessionQueryKey,
postV2CreateSession,
useGetV2GetSession,
usePatchV2SessionAssignUser,
getGetV2GetSessionQueryKey,
usePostV2CreateSession,
} from "@/app/api/__generated__/endpoints/chat/chat";
import type { SessionDetailResponse } from "@/app/api/__generated__/models/sessionDetailResponse";
import { storage, Key } from "@/services/storage/local-storage";
import { isValidUUID } from "@/app/(platform)/chat/helpers";
import { Key, storage } from "@/services/storage/local-storage";
import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { isValidUUID } from "./helpers";
interface UseChatSessionArgs {
urlSessionId?: string | null;

View File

@@ -1,6 +1,7 @@
"use client";
import { IconLaptop } from "@/components/__legacy__/ui/icons";
import { useChatDrawer } from "@/components/contextual/Chat/useChatDrawer";
import { cn } from "@/lib/utils";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import {
@@ -24,9 +25,22 @@ export function NavbarLink({ name, href }: Props) {
const pathname = usePathname();
const isActive = pathname.includes(href);
const chat_enabled = useGetFlag(Flag.CHAT);
const { open: openChatDrawer } = useChatDrawer();
const isChat = href === "/chat";
function handleClick(e: React.MouseEvent) {
if (isChat && chat_enabled) {
e.preventDefault();
openChatDrawer();
}
}
return (
<Link href={href} data-testid={`navbar-link-${name.toLowerCase()}`}>
<Link
href={href}
onClick={handleClick}
data-testid={`navbar-link-${name.toLowerCase()}`}
>
<div
className={cn(
"flex items-center justify-start gap-1 p-1 md:p-2",

View File

@@ -2,6 +2,7 @@
import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
import { IconAutoGPTLogo, IconType } from "@/components/__legacy__/ui/icons";
import { useChatDrawer } from "@/components/contextual/Chat/useChatDrawer";
import { PreviewBanner } from "@/components/layout/Navbar/components/PreviewBanner/PreviewBanner";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
@@ -40,6 +41,8 @@ export function NavbarView({ isLoggedIn, previewBranchName }: NavbarViewProps) {
const { isUserLoading } = useSupabase();
const isLoadingProfile = isProfileLoading || isUserLoading;
const { open: openChatDrawer } = useChatDrawer();
const linksWithChat = useMemo(() => {
const chatLink = { name: "Chat", href: "/chat" };
return isChatEnabled ? [...loggedInLinks, chatLink] : loggedInLinks;
@@ -128,6 +131,10 @@ export function NavbarView({ isLoggedIn, previewBranchName }: NavbarViewProps) {
: IconType.LayoutDashboard,
text: link.name,
href: link.href,
onClick:
link.name === "Chat" && isChatEnabled
? openChatDrawer
: undefined,
})),
},
...dynamicMenuItems,