feat: styling changes for viewing sessions (#1974)

This commit is contained in:
Salman Mohammed
2025-04-01 12:51:34 -04:00
committed by GitHub
parent 2e5d879cb9
commit 759d6368e5
5 changed files with 135 additions and 85 deletions

View File

@@ -1,9 +1,19 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Clock, MessageSquare, Folder, Share, Copy, Check, LoaderCircle } from 'lucide-react'; import {
Calendar,
MessageSquareText,
Folder,
Share2,
Sparkles,
Copy,
Check,
Target,
LoaderCircle,
} from 'lucide-react';
import { type SessionDetails } from '../../sessions'; import { type SessionDetails } from '../../sessions';
import { SessionHeaderCard, SessionMessages } from './SessionViewComponents'; import { SessionHeaderCard, SessionMessages, formatDate } from './SessionViewComponents';
import { createSharedSession } from '../../sharedSessions'; import { createSharedSession } from '../../sharedSessions';
import { Modal, ModalContent, ModalHeader, ModalTitle, ModalFooter } from '../ui/modal'; import { Modal, ModalContent } from '../ui/modal';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -107,27 +117,30 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
<SessionHeaderCard onBack={onBack}> <SessionHeaderCard onBack={onBack}>
{/* Session info row */} {/* Session info row */}
<div className="ml-8"> <div className="ml-8">
<h1 className="text-lg font-bold text-textStandard"> <h1 className="text-lg text-textStandardInverse">
{session.metadata.description || session.session_id} {session.metadata.description || session.session_id}
</h1> </h1>
<div className="flex items-center text-sm text-textSubtle mt-2 space-x-4"> <div className="flex items-center text-sm text-textSubtle mt-1 space-x-5">
<span className="flex items-center"> <span className="flex items-center">
<Clock className="w-4 h-4 mr-1" /> <Calendar className="w-4 h-4 mr-1" />
{new Date(session.messages[0]?.created * 1000).toLocaleString()} {formatDate(session.messages[0]?.created)}
</span> </span>
<span className="flex items-center">
<MessageSquareText className="w-4 h-4 mr-1" />
{session.metadata.message_count}
</span>
{session.metadata.total_tokens !== null && (
<span className="flex items-center">
<Target className="w-4 h-4 mr-1" />
{session.metadata.total_tokens.toLocaleString()}
</span>
)}
</div>
<div className="flex items-center text-sm text-textSubtle space-x-5">
<span className="flex items-center"> <span className="flex items-center">
<Folder className="w-4 h-4 mr-1" /> <Folder className="w-4 h-4 mr-1" />
{session.metadata.working_dir} {session.metadata.working_dir}
</span> </span>
<span className="flex items-center">
<MessageSquare className="w-4 h-4 mr-1" />
{session.metadata.message_count} messages
</span>
{session.metadata.total_tokens !== null && (
<span className="flex items-center">
{session.metadata.total_tokens.toLocaleString()} tokens
</span>
)}
</div> </div>
</div> </div>
@@ -135,30 +148,30 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
<button <button
onClick={handleShare} onClick={handleShare}
disabled={!canShare || isSharing} disabled={!canShare || isSharing}
className={`flex items-center text-textStandard px-3 py-1 border rounded-md ${ className={`flex items-center text-textStandardInverse px-2 py-1 ${
canShare canShare
? 'border-primary hover:text-primary hover:font-bold hover:scale-105 transition-all duration-150' ? 'hover:font-bold hover:scale-110 transition-all duration-150'
: 'border-gray-300 cursor-not-allowed opacity-50' : 'cursor-not-allowed opacity-50'
}`} }`}
> >
{isSharing ? ( {isSharing ? (
<> <>
<LoaderCircle className="w-5 h-5 animate-spin mr-2" /> <LoaderCircle className="w-7 h-7 animate-spin mr-2" />
<span>Sharing...</span> <span>Sharing...</span>
</> </>
) : ( ) : (
<> <>
<Share className="w-5 h-5" /> <Share2 className="w-7 h-7" />
</> </>
)} )}
</button> </button>
<span <button
onClick={onResume} onClick={onResume}
className="text-md cursor-pointer text-textStandard hover:font-bold hover:scale-105 transition-all duration-150" className="flex items-center text-textStandardInverse px-2 py-1 hover:font-bold hover:scale-110 transition-all duration-150"
> >
Resume Session <Sparkles className="w-7 h-7" />
</span> </button>
</div> </div>
</SessionHeaderCard> </SessionHeaderCard>
@@ -171,18 +184,31 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
{/* Share Link Modal */} {/* Share Link Modal */}
<Modal open={isShareModalOpen} onOpenChange={setIsShareModalOpen}> <Modal open={isShareModalOpen} onOpenChange={setIsShareModalOpen}>
<ModalContent className="sm:max-w-md dark:bg-black"> <ModalContent className="sm:max-w-md p-0 bg-bgApp dark:bg-bgApp dark:border-borderSubtle">
<ModalHeader> {/* Share Icon */}
<ModalTitle className="text-textStandard">Share Session</ModalTitle> <div className="flex justify-center mt-4">
</ModalHeader> <Share2 className="w-6 h-6 text-textStandard" />
<div className="flex flex-col gap-2 mt-2"> </div>
<div className="flex items-center gap-2">
<div className="flex-1 p-2 rounded-md overflow-x-auto"> {/* Centered Title */}
<code className="text-sm text-textStandard">{shareLink}</code> <div className="mt-2 px-6 text-center">
</div> <h2 className="text-lg font-semibold text-textStandard">Share Session (beta)</h2>
</div>
{/* Description & Link */}
<div className="px-6 flex flex-col gap-4 mt-2">
<p className="text-sm text-center text-textSubtle">
Share this session link to give others a read only view of your goose chat.
</p>
<div className="relative rounded-lg border border-borderSubtle px-3 py-2 flex items-center bg-gray-100 dark:bg-gray-600">
<code className="text-sm text-textStandard dark:text-textStandardInverse overflow-x-hidden break-all pr-8 w-full">
{shareLink}
</code>
<Button <Button
size="sm" size="icon"
className="flex-shrink-0" variant="ghost"
className="absolute right-2 top-1/2 -translate-y-1/2"
onClick={handleCopyLink} onClick={handleCopyLink}
disabled={isCopied} disabled={isCopied}
> >
@@ -190,22 +216,19 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
<span className="sr-only">Copy</span> <span className="sr-only">Copy</span>
</Button> </Button>
</div> </div>
<p className="text-sm text-textSubtle">
Share this link with others to give them access to this session.
<br />
They will need to have Goose installed and session sharing configured.
</p>
</div> </div>
<ModalFooter className="sm:justify-start">
{/* Footer */}
<div>
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
onClick={() => setIsShareModalOpen(false)} onClick={() => setIsShareModalOpen(false)}
className="hover:text-textStandard border border-borderSubtle text-textStandard hover:bg-bgSubtle" className="w-full h-[60px] border-t rounded-b-lg dark:border-gray-600 text-lg text-textStandard hover:bg-gray-100 hover:dark:bg-gray-600"
> >
Close Cancel
</Button> </Button>
</ModalFooter> </div>
</ModalContent> </ModalContent>
</Modal> </Modal>
</div> </div>

View File

@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ViewConfig } from '../../App'; import { ViewConfig } from '../../App';
import { import {
MessageSquare, MessageSquareText,
Target,
LoaderCircle, LoaderCircle,
AlertCircle, AlertCircle,
Calendar, Calendar,
@@ -46,7 +47,7 @@ const SessionListView: React.FC<SessionListViewProps> = ({ setView, onSelectSess
// Format date to be more readable // Format date to be more readable
// eg. "10:39 PM, Feb 28, 2025" // eg. "10:39 PM, Feb 28, 2025"
const formatDate = (dateString: string) => { const formatDateString = (dateString: string) => {
try { try {
const date = new Date(dateString); const date = new Date(dateString);
const time = new Intl.DateTimeFormat('en-US', { const time = new Intl.DateTimeFormat('en-US', {
@@ -114,7 +115,7 @@ const SessionListView: React.FC<SessionListViewProps> = ({ setView, onSelectSess
<div className="flex gap-3"> <div className="flex gap-3">
<div className="flex items-center text-textSubtle text-sm"> <div className="flex items-center text-textSubtle text-sm">
<Calendar className="w-3 h-3 mr-1 flex-shrink-0" /> <Calendar className="w-3 h-3 mr-1 flex-shrink-0" />
<span className="truncate">{formatDate(session.modified)}</span> <span className="truncate">{formatDateString(session.modified)}</span>
</div> </div>
<div className="flex items-center text-textSubtle text-sm"> <div className="flex items-center text-textSubtle text-sm">
<Folder className="w-3 h-3 mr-1 flex-shrink-0" /> <Folder className="w-3 h-3 mr-1 flex-shrink-0" />
@@ -130,12 +131,13 @@ const SessionListView: React.FC<SessionListViewProps> = ({ setView, onSelectSess
</div> </div>
<div className="flex items-center mt-1 space-x-3 text-sm text-textSubtle"> <div className="flex items-center mt-1 space-x-3 text-sm text-textSubtle">
<div className="flex items-center"> <div className="flex items-center">
<MessageSquare className="w-3 h-3 mr-1" /> <MessageSquareText className="w-3 h-3 mr-1" />
<span>{session.metadata.message_count}</span> <span>{session.metadata.message_count}</span>
</div> </div>
{session.metadata.total_tokens !== null && ( {session.metadata.total_tokens !== null && (
<div className="flex items-center"> <div className="flex items-center">
<span>{session.metadata.total_tokens.toLocaleString()} tokens</span> <Target className="w-3 h-3 mr-1" />
<span>{session.metadata.total_tokens.toLocaleString()}</span>
</div> </div>
)} )}
</div> </div>
@@ -148,7 +150,7 @@ const SessionListView: React.FC<SessionListViewProps> = ({ setView, onSelectSess
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center justify-center h-full text-textSubtle"> <div className="flex flex-col items-center justify-center h-full text-textSubtle">
<MessageSquare className="h-12 w-12 mb-4" /> <MessageSquareText className="h-12 w-12 mb-4" />
<p className="text-lg mb-2">No chat sessions found</p> <p className="text-lg mb-2">No chat sessions found</p>
<p className="text-sm">Your chat history will appear here</p> <p className="text-sm">Your chat history will appear here</p>
</div> </div>

View File

@@ -9,6 +9,31 @@ import ToolCallWithResponse from '../ToolCallWithResponse';
import { ToolRequestMessageContent, ToolResponseMessageContent } from '../../types/message'; import { ToolRequestMessageContent, ToolResponseMessageContent } from '../../types/message';
import { type Message } from '../../types/message'; import { type Message } from '../../types/message';
/**
* Format a timestamp into a human-readable date string
*/
export const formatDate = (timestamp: number) => {
const date = new Date(timestamp * 1000);
const getOrdinal = (n: number) => {
const s = ['th', 'st', 'nd', 'rd'];
const v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
};
const hours = date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true,
});
const month = date.toLocaleString('en-US', { month: 'short' });
const day = getOrdinal(date.getDate());
const year = date.getFullYear();
return `${hours}, ${month} ${day}, ${year}`;
};
/** /**
* Get tool responses map from messages * Get tool responses map from messages
*/ */
@@ -50,8 +75,13 @@ export interface SessionHeaderCardProps {
*/ */
export const SessionHeaderCard: React.FC<SessionHeaderCardProps> = ({ onBack, children }) => { export const SessionHeaderCard: React.FC<SessionHeaderCardProps> = ({ onBack, children }) => {
return ( return (
<Card className="px-8 pt-6 pb-4 bg-bgSecondary flex items-center"> <Card className="rounded-none px-8 pt-6 pb-4 bg-bgAppInverse text-textProminentInverse flex items-center">
<BackButton showText={false} onClick={onBack} className="text-textStandard" /> <BackButton
showText={false}
onClick={onBack}
iconSize="w-7 h-7"
className="text-textProminentInverse hover:text-textProminentInverse"
/>
{children} {children}
</Card> </Card>
); );

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Clock, Globe, MessageSquare, Folder } from 'lucide-react'; import { Calendar, MessageSquareText, Folder, Target } from 'lucide-react';
import { type SharedSessionDetails } from '../../sharedSessions'; import { type SharedSessionDetails } from '../../sharedSessions';
import { SessionHeaderCard, SessionMessages } from './SessionViewComponents'; import { SessionHeaderCard, SessionMessages, formatDate } from './SessionViewComponents';
interface SharedSessionViewProps { interface SharedSessionViewProps {
session: SharedSessionDetails | null; session: SharedSessionDetails | null;
@@ -20,44 +20,37 @@ const SharedSessionView: React.FC<SharedSessionViewProps> = ({
}) => { }) => {
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <div className="relative flex items-center h-[36px] w-full"></div>
{/* Top Row - back, info (fixed) */} {/* Top Row - back, info (fixed) */}
<SessionHeaderCard onBack={onBack}> <SessionHeaderCard onBack={onBack}>
{/* Session info row */} {/* Session info row */}
<div className="ml-8"> <div className="ml-8">
<h1 className="text-lg font-bold text-textStandard"> <h1 className="text-lg text-textStandardInverse">
{session ? session.description : 'Shared Session'} {session ? session.description : 'Shared Session'}
</h1> </h1>
{session && ( <div className="flex items-center text-sm text-textSubtle mt-1 space-x-5">
<div className="flex items-center text-sm text-textSubtle mt-2 space-x-4"> <span className="flex items-center">
<Calendar className="w-4 h-4 mr-1" />
{formatDate(session.messages[0]?.created)}
</span>
<span className="flex items-center">
<MessageSquareText className="w-4 h-4 mr-1" />
{session.message_count}
</span>
{session.total_tokens !== null && (
<span className="flex items-center"> <span className="flex items-center">
<Clock className="w-4 h-4 mr-1" /> <Target className="w-4 h-4 mr-1" />
{new Date(session.messages[0]?.created * 1000).toLocaleString()} {session.total_tokens.toLocaleString()}
</span> </span>
{/* <span className="flex items-center"> )}
<Globe className="w-4 h-4 mr-1" /> </div>
{session.base_url} <div className="flex items-center text-sm text-textSubtle space-x-5">
</span> */} <span className="flex items-center">
<span className="flex items-center"> <Folder className="w-4 h-4 mr-1" />
<MessageSquare className="w-4 h-4 mr-1" /> {session.working_dir}
{session.message_count} messages </span>
</span> </div>
{session.total_tokens !== null && (
<span className="flex items-center">
{session.total_tokens.toLocaleString()} tokens
</span>
)}
</div>
)}
{session && (
<div className="flex items-center text-sm text-textSubtle mt-1">
<span className="flex items-center">
<Folder className="w-4 h-4 mr-1" />
{session.working_dir}
</span>
</div>
)}
</div> </div>
</SessionHeaderCard> </SessionHeaderCard>

View File

@@ -5,6 +5,7 @@ interface BackButtonProps {
onClick?: () => void; // Mark onClick as optional onClick?: () => void; // Mark onClick as optional
className?: string; className?: string;
textSize?: 'sm' | 'base' | 'md' | 'lg'; textSize?: 'sm' | 'base' | 'md' | 'lg';
iconSize?: 'w-3 h-3' | 'w-4 h-4' | 'w-5 h-5' | 'w-6 h-6' | 'w-7 h-7';
showText?: boolean; // Add new prop showText?: boolean; // Add new prop
} }
@@ -12,6 +13,7 @@ const BackButton: React.FC<BackButtonProps> = ({
onClick, onClick,
className = '', className = '',
textSize = 'sm', textSize = 'sm',
iconSize = 'w-3 h-3',
showText = true, showText = true,
}) => { }) => {
const handleExit = () => { const handleExit = () => {
@@ -29,7 +31,7 @@ const BackButton: React.FC<BackButtonProps> = ({
onClick={handleExit} onClick={handleExit}
className={`flex items-center text-${textSize} text-textSubtle group hover:text-textStandard ${className}`} className={`flex items-center text-${textSize} text-textSubtle group hover:text-textStandard ${className}`}
> >
<Back className="w-3 h-3 group-hover:-translate-x-1 transition-all mr-1" /> <Back className={`${iconSize} group-hover:-translate-x-1 transition-all mr-1`} />
{showText && <span>Exit</span>} {showText && <span>Exit</span>}
</button> </button>
); );