mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Merge branch 'zane/ui-update' of github.com:block/goose into zane/ui-update
* 'zane/ui-update' of github.com:block/goose: Design pass (#3366)
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
diff --git a/ui/desktop/src/components/sessions/SessionsInsights.tsx b/ui/desktop/src/components/sessions/SessionsInsights.tsx
|
||||
index 1234567..abcdefg 100644
|
||||
--- a/ui/desktop/src/components/sessions/SessionsInsights.tsx
|
||||
+++ b/ui/desktop/src/components/sessions/SessionsInsights.tsx
|
||||
@@ -90,7 +90,7 @@ export function SessionInsights() {
|
||||
}
|
||||
|
||||
return (
|
||||
- <div className="bg-background-muted ml-8 mr-8">
|
||||
+ <div className="bg-background-muted flex flex-col h-full">
|
||||
+ {/* Header container with rounded bottom */}
|
||||
+ <div className="bg-background-default rounded-b-2xl mb-0.5">
|
||||
- <div className="px-8 pb-12 pt-19 bg-background-default space-y-4">
|
||||
+ <div className="px-8 pb-12 pt-19 space-y-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.25, x: -5, y: 5, rotate: -20 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0, y: 0, rotate: 0 }}
|
||||
@@ -102,8 +102,9 @@ export function SessionInsights() {
|
||||
</motion.div>
|
||||
<Greeting />
|
||||
</div>
|
||||
+ </div>
|
||||
|
||||
- <div className="grid gap-[2px] pl-[2px] pr-[2px] mt-0.5">
|
||||
+ {/* Stats containers - full bleed with 2px gaps */}
|
||||
+ <div className="flex flex-col flex-1 space-y-0.5">
|
||||
- {/* Top row with three equal columns */}
|
||||
- <div className="grid grid-cols-3 gap-[2px]">
|
||||
+ <div className="grid grid-cols-3 gap-0.5">
|
||||
{/* Total Sessions Card */}
|
||||
- <Card className="w-full py-6 px-4 border-none rounded-tl-none rounded-bl-none">
|
||||
+ <Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
- <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
+ <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.totalSessions}
|
||||
@@ -115,8 +116,8 @@ export function SessionInsights() {
|
||||
</Card>
|
||||
|
||||
{/* Average Duration Card */}
|
||||
- <Card className="w-full py-6 px-4 border-none">
|
||||
+ <Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
- <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
+ <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.avgSessionDuration?.toFixed(1)}m
|
||||
@@ -128,8 +129,8 @@ export function SessionInsights() {
|
||||
</Card>
|
||||
|
||||
{/* Total Tokens Card */}
|
||||
- <Card className="w-full py-6 px-4 border-none rounded-tr-none rounded-br-none">
|
||||
+ <Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
- <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
+ <CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''}
|
||||
@@ -141,70 +142,52 @@ export function SessionInsights() {
|
||||
</div>
|
||||
|
||||
- {/* Bottom row with two equal columns */}
|
||||
- <div className="grid grid-cols-1 gap-[2px]">
|
||||
- {/* Recent Projects Card */}
|
||||
- {/*<Card className="w-full py-6 px-4 border-none rounded-tl-none rounded-bl-none">*/}
|
||||
- {/* <CardContent className="animate-in fade-in duration-500 px-4">*/}
|
||||
- {/* <div className="flex justify-between items-center mb-2 px-2">*/}
|
||||
- {/* <CardDescription className="mb-0">*/}
|
||||
- {/* <span className="text-lg text-text-default">Recent projects</span>*/}
|
||||
- {/* </CardDescription>*/}
|
||||
- {/* <Button*/}
|
||||
- {/* variant="ghost"*/}
|
||||
- {/* size="sm"*/}
|
||||
- {/* className="text-xs text-text-muted flex items-center gap-1 !px-0 hover:bg-transparent hover:underline hover:text-text-default"*/}
|
||||
- {/* onClick={navigateToProjects}*/}
|
||||
- {/* >*/}
|
||||
- {/* See all*/}
|
||||
- {/* </Button>*/}
|
||||
- {/* </div>*/}
|
||||
- {/* <div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">*/}
|
||||
- {/* <AnimatePresence>*/}
|
||||
- {/* {recentProjects.length > 0 ? (*/}
|
||||
- {/* recentProjects.map((project, index) => (*/}
|
||||
- {/* <motion.div*/}
|
||||
- {/* key={project.id}*/}
|
||||
- {/* className="flex items-center justify-between text-sm py-1 px-2 rounded-md hover:bg-background-muted cursor-pointer transition-colors"*/}
|
||||
- {/* onClick={() => handleProjectClick(project.id)}*/}
|
||||
- {/* role="button"*/}
|
||||
- {/* tabIndex={0}*/}
|
||||
- {/* initial={{ opacity: 0, y: 5 }}*/}
|
||||
- {/* animate={{ opacity: 1, y: 0 }}*/}
|
||||
- {/* transition={{ duration: 0.3, delay: index * 0.1 }}*/}
|
||||
- {/* onKeyDown={(e) => {*/}
|
||||
- {/* if (e.key === 'Enter' || e.key === ' ') {*/}
|
||||
- {/* handleProjectClick(project.id);*/}
|
||||
- {/* }*/}
|
||||
- {/* }}*/}
|
||||
- {/* >*/}
|
||||
- {/* <div className="flex items-center space-x-2">*/}
|
||||
- {/* <Folder className="h-4 w-4 text-text-muted" />*/}
|
||||
- {/* <span className="truncate max-w-[200px]">{project.name}</span>*/}
|
||||
- {/* </div>*/}
|
||||
- {/* <span className="text-text-muted font-mono font-light">*/}
|
||||
- {/* {formatDateOnly(project.updatedAt)}*/}
|
||||
- {/* </span>*/}
|
||||
- {/* </motion.div>*/}
|
||||
- {/* ))*/}
|
||||
- {/* ) : (*/}
|
||||
- {/* <div className="text-text-muted text-sm py-2 px-2">*/}
|
||||
- {/* No recent projects found.*/}
|
||||
- {/* </div>*/}
|
||||
- {/* )}*/}
|
||||
- {/* </AnimatePresence>*/}
|
||||
- {/* </div>*/}
|
||||
- {/* </CardContent>*/}
|
||||
- {/*</Card>*/}
|
||||
+ {/* Projects and Chats row */}
|
||||
+ <div className="grid grid-cols-2 gap-0.5">
|
||||
+ {/* Recent Projects Card */}
|
||||
+ <Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
+ <CardContent className="animate-in fade-in duration-500 p-0">
|
||||
+ <div className="flex justify-between items-center mb-4">
|
||||
+ <CardDescription className="mb-0">
|
||||
+ <span className="text-lg text-text-default">Recent projects</span>
|
||||
+ </CardDescription>
|
||||
+ <Button
|
||||
+ variant="ghost"
|
||||
+ size="sm"
|
||||
+ className="text-xs text-text-muted flex items-center gap-1 !px-0 hover:bg-transparent hover:underline hover:text-text-default"
|
||||
+ onClick={() => {/* navigateToProjects */}}
|
||||
+ >
|
||||
+ See all
|
||||
+ </Button>
|
||||
+ </div>
|
||||
+ <div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">
|
||||
+ <div className="text-text-muted text-sm py-2">
|
||||
+ No recent projects found.
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </CardContent>
|
||||
+ </Card>
|
||||
|
||||
{/* Recent Chats Card */}
|
||||
- <Card className="w-full py-6 px-4 border-none rounded-none">
|
||||
+ <Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
- <CardContent className="animate-in fade-in duration-500 px-4">
|
||||
+ <CardContent className="animate-in fade-in duration-500 p-0">
|
||||
- <div className="flex justify-between items-center mb-2 px-2">
|
||||
+ <div className="flex justify-between items-center mb-4">
|
||||
<CardDescription className="mb-0">
|
||||
<span className="text-lg text-text-default">Recent chats</span>
|
||||
</CardDescription>
|
||||
@@ -242,11 +225,13 @@ export function SessionInsights() {
|
||||
) : (
|
||||
- <div className="text-text-muted text-sm py-2">
|
||||
+ <div className="text-text-muted text-sm py-2">
|
||||
No recent chat sessions found.
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
- <div className="h-full bg-background-default"></div>
|
||||
+
|
||||
+ {/* Filler container - extends to fill remaining space */}
|
||||
+ <div className="bg-background-default rounded-2xl flex-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
diff --git a/ui/desktop/src/components/hub.tsx b/ui/desktop/src/components/hub.tsx
|
||||
index 1234567..abcdefg 100644
|
||||
--- a/ui/desktop/src/components/hub.tsx
|
||||
+++ b/ui/desktop/src/components/hub.tsx
|
||||
@@ -65,7 +65,7 @@ export default function Hub({
|
||||
|
||||
return (
|
||||
<ChatContextManagerProvider>
|
||||
- <div className="flex flex-col h-full bg-background-default">
|
||||
+ <div className="flex flex-col h-full bg-background-muted">
|
||||
- <div className="flex-1 flex flex-col">
|
||||
+ <div className="flex-1 flex flex-col mb-0.5">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
|
||||
- <ChatInput
|
||||
+ <ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={false}
|
||||
onStop={() => {}}
|
||||
|
||||
diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx
|
||||
index 1234567..abcdefg 100644
|
||||
--- a/ui/desktop/src/components/ChatInput.tsx
|
||||
+++ b/ui/desktop/src/components/ChatInput.tsx
|
||||
@@ -658,7 +658,7 @@ export default function ChatInput({
|
||||
|
||||
return (
|
||||
<div
|
||||
- className={`flex flex-col relative h-auto border-t-2 border-background-muted p-4 transition-colors ${
|
||||
+ className={`flex flex-col relative h-auto p-4 transition-colors ${
|
||||
disableAnimation ? '' : 'animate-in fade-in duration-500'
|
||||
} ${
|
||||
isFocused
|
||||
? 'border-borderProminent hover:border-borderProminent'
|
||||
: 'border-borderSubtle hover:border-borderStandard'
|
||||
- } bg-background-default z-10 rounded-t-lg`}
|
||||
+ } bg-background-default z-10 rounded-t-2xl`}
|
||||
data-drop-zone="true"
|
||||
onDrop={handleLocalDrop}
|
||||
onDragOver={handleLocalDragOver}
|
||||
@@ -307,101 +307,108 @@ function BaseChatContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MainPanelLayout {...customMainLayoutProps}>
|
||||
<div className="bg-background-muted h-full flex flex-col min-h-0">
|
||||
<MainPanelLayout removeTopPadding={true} {...customMainLayoutProps}>
|
||||
{/* Loader when generating recipe */}
|
||||
{isGeneratingRecipe && <LayingEggLoader />}
|
||||
|
||||
{/* Custom header */}
|
||||
{renderHeader && renderHeader()}
|
||||
|
||||
{/* Recipe agent header - fixed at top when recipe is active */}
|
||||
{recipeConfig?.title && (
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
profileInfo={
|
||||
recipeConfig.profile
|
||||
? `${recipeConfig.profile} - ${recipeConfig.mcps || 12} MCPs`
|
||||
: undefined
|
||||
}
|
||||
onChangeProfile={() => {
|
||||
console.log('Change profile clicked');
|
||||
}}
|
||||
showBorder={messages.length > 0}
|
||||
/>
|
||||
)}
|
||||
{/* Recipe agent header - inside the messages container when present */}
|
||||
<div className="flex flex-col flex-1 mb-0.5 min-h-0">
|
||||
<ScrollArea
|
||||
ref={scrollRef}
|
||||
className={`flex-1 bg-background-default rounded-b-2xl min-h-0 ${contentClassName}`}
|
||||
autoScroll
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
data-drop-zone="true"
|
||||
paddingX={6}
|
||||
paddingY={0}
|
||||
>
|
||||
{/* Recipe agent header - now inside the messages container */}
|
||||
{recipeConfig?.title && (
|
||||
<div className="px-0 -mx-6">
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
profileInfo={
|
||||
recipeConfig.profile
|
||||
? `${recipeConfig.profile} - ${recipeConfig.mcps || 12} MCPs`
|
||||
: undefined
|
||||
}
|
||||
onChangeProfile={() => {
|
||||
console.log('Change profile clicked');
|
||||
}}
|
||||
showBorder={messages.length > 0}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ScrollArea
|
||||
ref={scrollRef}
|
||||
className={`flex-1 ${contentClassName}`}
|
||||
autoScroll
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
data-drop-zone="true"
|
||||
paddingX={6}
|
||||
paddingY={0}
|
||||
>
|
||||
{/* Custom content before messages */}
|
||||
{renderBeforeMessages && renderBeforeMessages()}
|
||||
{/* Custom content before messages */}
|
||||
{renderBeforeMessages && renderBeforeMessages()}
|
||||
|
||||
{/* Messages or Splash */}
|
||||
{messages.length === 0 ? (
|
||||
/* Show Splash when no messages and we have a recipe config */
|
||||
recipeConfig && (
|
||||
<Splash
|
||||
append={(text: string) => append(text)}
|
||||
activities={Array.isArray(recipeConfig.activities) ? recipeConfig.activities : null}
|
||||
title={recipeConfig.title}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{disableSearch ? (
|
||||
// Render messages without SearchView wrapper when search is disabled
|
||||
renderMessages()
|
||||
) : (
|
||||
// Render messages with SearchView wrapper when search is enabled
|
||||
<SearchView>{renderMessages()}</SearchView>
|
||||
)}
|
||||
{/* Messages or Splash */}
|
||||
{messages.length === 0 ? (
|
||||
/* Show Splash when no messages and we have a recipe config */
|
||||
recipeConfig && (
|
||||
<Splash
|
||||
append={(text: string) => append(text)}
|
||||
activities={Array.isArray(recipeConfig.activities) ? recipeConfig.activities : null}
|
||||
title={recipeConfig.title}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{disableSearch ? (
|
||||
// Render messages without SearchView wrapper when search is disabled
|
||||
renderMessages()
|
||||
) : (
|
||||
// Render messages with SearchView wrapper when search is enabled
|
||||
<SearchView>{renderMessages()}</SearchView>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex flex-col items-center justify-center p-4">
|
||||
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
|
||||
{error.message || 'Honk! Goose experienced an error while responding'}
|
||||
{error && (
|
||||
<div className="flex flex-col items-center justify-center p-4">
|
||||
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
|
||||
{error.message || 'Honk! Goose experienced an error while responding'}
|
||||
</div>
|
||||
<div
|
||||
className="px-3 py-2 mt-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
|
||||
onClick={async () => {
|
||||
// Find the last user message
|
||||
const lastUserMessage = messages.reduceRight(
|
||||
(found, m) => found || (m.role === 'user' ? m : null),
|
||||
null as Message | null
|
||||
);
|
||||
if (lastUserMessage) {
|
||||
append(lastUserMessage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Retry Last Message
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="px-3 py-2 mt-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
|
||||
onClick={async () => {
|
||||
// Find the last user message
|
||||
const lastUserMessage = messages.reduceRight(
|
||||
(found, m) => found || (m.role === 'user' ? m : null),
|
||||
null as Message | null
|
||||
);
|
||||
if (lastUserMessage) {
|
||||
append(lastUserMessage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Retry Last Message
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="block h-8" />
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
<div className="block h-8" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Custom content after messages */}
|
||||
{renderAfterMessages && renderAfterMessages()}
|
||||
</ScrollArea>
|
||||
{/* Loading indicator at bottom of messages container */}
|
||||
{isLoading && (
|
||||
<div className="px-0 -mx-6">
|
||||
<LoadingGoose message={isLoadingSummary ? 'summarizing conversation…' : undefined} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Custom content after messages */}
|
||||
{renderAfterMessages && renderAfterMessages()}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`relative z-10 ${disableAnimation ? '' : 'animate-[fadein_400ms_ease-in_forwards]'}`}
|
||||
>
|
||||
<div className="px-6">
|
||||
{isLoading && (
|
||||
<LoadingGoose message={isLoadingSummary ? 'summarizing conversation…' : undefined} />
|
||||
)}
|
||||
</div>
|
||||
<ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -826,13 +826,13 @@ export default function ChatInput({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col relative h-auto border-t-2 border-background-muted p-4 transition-colors ${
|
||||
className={`flex flex-col relative h-auto p-4 transition-colors ${
|
||||
disableAnimation ? '' : 'animate-in fade-in duration-500'
|
||||
} ${
|
||||
isFocused
|
||||
? 'border-borderProminent hover:border-borderProminent'
|
||||
: 'border-borderSubtle hover:border-borderStandard'
|
||||
} bg-background-default z-10`}
|
||||
} bg-background-default z-10 rounded-t-2xl`}
|
||||
data-drop-zone="true"
|
||||
onDrop={handleLocalDrop}
|
||||
onDragOver={handleLocalDragOver}
|
||||
|
||||
@@ -2,11 +2,12 @@ import React from 'react';
|
||||
|
||||
export const MainPanelLayout: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
removeTopPadding?: boolean;
|
||||
}> = ({ children, removeTopPadding = false }) => {
|
||||
return (
|
||||
<div className={`h-dvh`}>
|
||||
{/* Padding top matches the app toolbar drag area height */}
|
||||
<div className={`flex flex-col bg-background-default flex-1 min-w-0 h-full pt-[32px]`}>
|
||||
{/* Padding top matches the app toolbar drag area height - can be removed for full bleed */}
|
||||
<div className={`flex flex-col bg-background-muted flex-1 min-w-0 h-full min-h-0 ${removeTopPadding ? '' : 'pt-[32px]'}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,8 +86,8 @@ export default function Hub({
|
||||
|
||||
return (
|
||||
<ChatContextManagerProvider>
|
||||
<div className="flex flex-col h-full bg-background-default">
|
||||
<div className="flex-1 flex flex-col">
|
||||
<div className="flex flex-col h-full bg-background-muted">
|
||||
<div className="flex-1 flex flex-col mb-0.5">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -106,25 +106,29 @@ export function SessionInsights() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-background-muted ml-8 mr-8">
|
||||
<div className="px-8 pb-12 pt-19 bg-background-default space-y-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.25, x: -5, y: 5, rotate: -20 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0, y: 0, rotate: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
className="origin-bottom-left"
|
||||
>
|
||||
<Goose className="size-8" />
|
||||
</motion.div>
|
||||
<Greeting />
|
||||
<div className="bg-background-muted flex flex-col h-full">
|
||||
{/* Header container with rounded bottom */}
|
||||
<div className="bg-background-default rounded-b-2xl mb-0.5">
|
||||
<div className="px-8 pb-12 pt-19 space-y-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.25, x: -5, y: 5, rotate: -20 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0, y: 0, rotate: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
className="origin-bottom-left"
|
||||
>
|
||||
<Goose className="size-8" />
|
||||
</motion.div>
|
||||
<Greeting />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-[2px] pl-[2px] pr-[2px] mt-0.5">
|
||||
{/* Stats containers - full bleed with 2px gaps */}
|
||||
<div className="flex flex-col flex-1 space-y-0.5">
|
||||
{/* Top row with three equal columns */}
|
||||
<div className="grid grid-cols-3 gap-[2px]">
|
||||
<div className="grid grid-cols-3 gap-0.5">
|
||||
{/* Total Sessions Card */}
|
||||
<Card className="w-full py-6 px-4 border-none rounded-tl-none rounded-bl-none">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
<Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.totalSessions}
|
||||
@@ -135,8 +139,8 @@ export function SessionInsights() {
|
||||
</Card>
|
||||
|
||||
{/* Average Duration Card */}
|
||||
<Card className="w-full py-6 px-4 border-none">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
<Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.avgSessionDuration?.toFixed(1)}m
|
||||
@@ -147,8 +151,8 @@ export function SessionInsights() {
|
||||
</Card>
|
||||
|
||||
{/* Total Tokens Card */}
|
||||
<Card className="w-full py-6 px-4 border-none rounded-tr-none rounded-br-none">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full px-4">
|
||||
<Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
<CardContent className="animate-in fade-in duration-500 flex flex-col justify-end h-full p-0">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
{insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''}
|
||||
@@ -159,66 +163,36 @@ export function SessionInsights() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Bottom row with two equal columns */}
|
||||
<div className="grid grid-cols-1 gap-[2px]">
|
||||
{/* Projects and Chats row */}
|
||||
<div className="grid grid-cols-2 gap-0.5">
|
||||
{/* Recent Projects Card */}
|
||||
{/*<Card className="w-full py-6 px-4 border-none rounded-tl-none rounded-bl-none">*/}
|
||||
{/* <CardContent className="animate-in fade-in duration-500 px-4">*/}
|
||||
{/* <div className="flex justify-between items-center mb-2 px-2">*/}
|
||||
{/* <CardDescription className="mb-0">*/}
|
||||
{/* <span className="text-lg text-text-default">Recent projects</span>*/}
|
||||
{/* </CardDescription>*/}
|
||||
{/* <Button*/}
|
||||
{/* variant="ghost"*/}
|
||||
{/* size="sm"*/}
|
||||
{/* className="text-xs text-text-muted flex items-center gap-1 !px-0 hover:bg-transparent hover:underline hover:text-text-default"*/}
|
||||
{/* onClick={navigateToProjects}*/}
|
||||
{/* >*/}
|
||||
{/* See all*/}
|
||||
{/* </Button>*/}
|
||||
{/* </div>*/}
|
||||
{/* <div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">*/}
|
||||
{/* <AnimatePresence>*/}
|
||||
{/* {recentProjects.length > 0 ? (*/}
|
||||
{/* recentProjects.map((project, index) => (*/}
|
||||
{/* <motion.div*/}
|
||||
{/* key={project.id}*/}
|
||||
{/* className="flex items-center justify-between text-sm py-1 px-2 rounded-md hover:bg-background-muted cursor-pointer transition-colors"*/}
|
||||
{/* onClick={() => handleProjectClick(project.id)}*/}
|
||||
{/* role="button"*/}
|
||||
{/* tabIndex={0}*/}
|
||||
{/* initial={{ opacity: 0, y: 5 }}*/}
|
||||
{/* animate={{ opacity: 1, y: 0 }}*/}
|
||||
{/* transition={{ duration: 0.3, delay: index * 0.1 }}*/}
|
||||
{/* onKeyDown={(e) => {*/}
|
||||
{/* if (e.key === 'Enter' || e.key === ' ') {*/}
|
||||
{/* handleProjectClick(project.id);*/}
|
||||
{/* }*/}
|
||||
{/* }}*/}
|
||||
{/* >*/}
|
||||
{/* <div className="flex items-center space-x-2">*/}
|
||||
{/* <Folder className="h-4 w-4 text-text-muted" />*/}
|
||||
{/* <span className="truncate max-w-[200px]">{project.name}</span>*/}
|
||||
{/* </div>*/}
|
||||
{/* <span className="text-text-muted font-mono font-light">*/}
|
||||
{/* {formatDateOnly(project.updatedAt)}*/}
|
||||
{/* </span>*/}
|
||||
{/* </motion.div>*/}
|
||||
{/* ))*/}
|
||||
{/* ) : (*/}
|
||||
{/* <div className="text-text-muted text-sm py-2 px-2">*/}
|
||||
{/* No recent projects found.*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* </AnimatePresence>*/}
|
||||
{/* </div>*/}
|
||||
{/* </CardContent>*/}
|
||||
{/*</Card>*/}
|
||||
<Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
<CardContent className="animate-in fade-in duration-500 p-0">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<CardDescription className="mb-0">
|
||||
<span className="text-lg text-text-default">Recent projects</span>
|
||||
</CardDescription>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs text-text-muted flex items-center gap-1 !px-0 hover:bg-transparent hover:underline hover:text-text-default"
|
||||
onClick={() => {/* navigateToProjects */}}
|
||||
>
|
||||
See all
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">
|
||||
<div className="text-text-muted text-sm py-2">
|
||||
No recent projects found.
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Chats Card */}
|
||||
<Card className="w-full py-6 px-4 border-none rounded-none">
|
||||
<CardContent className="animate-in fade-in duration-500 px-4">
|
||||
<div className="flex justify-between items-center mb-2 px-2">
|
||||
<Card className="w-full py-6 px-6 border-none rounded-2xl bg-background-default">
|
||||
<CardContent className="animate-in fade-in duration-500 p-0">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<CardDescription className="mb-0">
|
||||
<span className="text-lg text-text-default">Recent chats</span>
|
||||
</CardDescription>
|
||||
@@ -271,7 +245,9 @@ export function SessionInsights() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="h-full bg-background-default"></div>
|
||||
|
||||
{/* Filler container - extends to fill remaining space */}
|
||||
<div className="bg-background-default rounded-2xl flex-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user