mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
iteration
This commit is contained in:
@@ -428,7 +428,7 @@ export default function ChatInput({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col relative h-auto rounded-xl border mx-6 mb-4 transition-colors ${
|
||||
className={`flex flex-col relative h-auto rounded-xl border mx-6 mb-6 transition-colors ${
|
||||
isFocused
|
||||
? 'border-borderProminent hover:border-borderProminent'
|
||||
: 'border-borderSubtle hover:border-borderStandard'
|
||||
@@ -464,14 +464,14 @@ export default function ChatInput({
|
||||
</Button>
|
||||
|
||||
{isLoading ? (
|
||||
<Button type="button" onClick={onStop} shape="round" size="sm" variant="secondary">
|
||||
<Button type="button" onClick={onStop} shape="round" size="sm" variant="ghost">
|
||||
<Stop />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
variant="default"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
// disabled={!displayValue.trim() && pastedImages.length === 0}
|
||||
>
|
||||
|
||||
@@ -50,8 +50,14 @@ import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
} from './ui/sidebar';
|
||||
import MoreMenu from './more_menu/MoreMenu';
|
||||
import BottomMenu from './bottom_menu/BottomMenu';
|
||||
import { SessionInsights } from './sessions/SessionsInsights';
|
||||
import { useSidebar } from './ui/sidebar';
|
||||
import { Button } from './ui/button';
|
||||
import { Gear, Idea } from './icons';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
|
||||
import { Bot, Folder, Save, Send } from 'lucide-react';
|
||||
import { ChatSmart } from './icons';
|
||||
|
||||
// Context for sharing current model info
|
||||
const CurrentModelContext = createContext<{ model: string; mode: string } | null>(null);
|
||||
@@ -137,9 +143,10 @@ function ChatContentWithSidebar({
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
}) {
|
||||
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
||||
const { open: isSidebarOpen } = useSidebar();
|
||||
|
||||
// Calculate padding based on sidebar state and macOS
|
||||
const headerPadding = safeIsMacOS ? 'pl-24' : 'pl-12';
|
||||
const headerPadding = !isSidebarOpen ? (safeIsMacOS ? 'pl-20' : 'pl-12') : 'pl-4';
|
||||
|
||||
const [hasMessages, setHasMessages] = useState(false);
|
||||
const [lastInteractionTime, setLastInteractionTime] = useState<number>(Date.now());
|
||||
@@ -612,23 +619,136 @@ function ChatContentWithSidebar({
|
||||
</Sidebar>
|
||||
<SidebarInset>
|
||||
<div className="flex flex-col min-w-0 h-[calc(100dvh-40px)] shadow-default bg-background-default mt-2 mr-2 mb-2 rounded-2xl animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<div className="fixed w-[16rem] left-0 top-0 z-10">
|
||||
<div
|
||||
className={`flex w-full py-4 ${headerPadding} pr-5 z-50 transition-[padding] duration-200 ease-in-out`}
|
||||
>
|
||||
<SidebarTrigger />
|
||||
{/* <MoreMenu
|
||||
setView={setView || (() => {})}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen || (() => {})}
|
||||
/> */}
|
||||
<div
|
||||
className={`h-12 flex items-center justify-between fade-in duration-300 transition-all border-b ${!isSidebarOpen ? 'border-border-default' : 'border-transparent'}`}
|
||||
>
|
||||
<div className={`flex items-center ${headerPadding}`}>
|
||||
<SidebarTrigger className="no-drag" />
|
||||
</div>
|
||||
<div className="flex items-center pr-4">
|
||||
{setIsGoosehintsModalOpen && (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setIsGoosehintsModalOpen(true)}
|
||||
className="px-3"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
shape="round"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Idea className="w-4 h-4" />
|
||||
{/* Configure .goosehints */}
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Customize instructions</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{recipeConfig ? (
|
||||
<>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.createChatWindow(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
recipeConfig as Recipe,
|
||||
'recipeEditor'
|
||||
);
|
||||
}}
|
||||
className="px-3"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Send className="w-4 h-4" />
|
||||
View recipe
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>View the recipe you're using</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
// onClick={handleSaveRecipeClick}
|
||||
className="px-3"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
shape="round"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Save className="w-4 h-4" />
|
||||
Save recipe
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Save this recipe for reuse</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.logInfo('Make Agent button clicked');
|
||||
window.dispatchEvent(new CustomEvent('make-agent-from-chat'));
|
||||
}}
|
||||
className="px-3"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
shape="round"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Bot className="w-4 h-4" />
|
||||
{/* Make Agent from this session */}
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Make a custom agent you can share or reuse</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* <Button
|
||||
onClick={async () => {
|
||||
await remove('GOOSE_PROVIDER', false);
|
||||
await remove('GOOSE_MODEL', false);
|
||||
setView?.('welcome');
|
||||
}}
|
||||
className="px-3 hover:shadow-default hover:bg-background-default transition-all duration-200 text-red-400 hover:text-red-300"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
>
|
||||
<Refresh />
|
||||
</Button> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex flex-col min-w-0 flex-1 overflow-y-scroll relative pl-6 pr-4 pb-16 pt-10"
|
||||
className="flex flex-col min-w-0 flex-1 overflow-y-scroll relative pl-6 pr-4 pb-16 pt-6"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
{/* Session Insights at the top */}
|
||||
{messages.length === 0 && (
|
||||
<div className="mb-8 animate-[fadein_500ms_ease-in_forwards]">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recipeConfig?.title && messages.length > 0 && (
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
@@ -644,13 +764,9 @@ function ChatContentWithSidebar({
|
||||
/>
|
||||
)}
|
||||
{messages.length === 0 ? (
|
||||
<Splash
|
||||
append={append}
|
||||
activities={
|
||||
Array.isArray(recipeConfig?.activities) ? recipeConfig!.activities : null
|
||||
}
|
||||
title={recipeConfig?.title}
|
||||
/>
|
||||
<>
|
||||
<SearchView>{/* Empty search view when no messages */}</SearchView>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SearchView>
|
||||
@@ -741,6 +857,20 @@ function ChatContentWithSidebar({
|
||||
</div>
|
||||
<div className="relative z-10 animate-[fadein_400ms_ease-in_forwards]">
|
||||
{isLoading && <LoadingGoose />}
|
||||
|
||||
{/* Splash component at the bottom when no messages
|
||||
{messages.length === 0 && (
|
||||
<div className="mb-4">
|
||||
<Splash
|
||||
append={append}
|
||||
activities={
|
||||
Array.isArray(recipeConfig?.activities) ? recipeConfig!.activities : null
|
||||
}
|
||||
title={recipeConfig?.title}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
<ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Folder, FileText, Clock, Save } from 'lucide-react';
|
||||
import { Folder, FileText, Clock, MessageCircle } from 'lucide-react';
|
||||
import { SidebarContent, SidebarFooter } from '../ui/sidebar';
|
||||
import { Button } from '../ui/button';
|
||||
import { ChatSmart, Idea, Time, Send, Refresh, Gear } from '../icons';
|
||||
import { Separator } from '../ui/separator';
|
||||
import { ChatSmart, Time, Gear, LinkedIn, Youtube, Discord } from '../icons';
|
||||
import { ViewOptions, View } from '../../App';
|
||||
import { useConfig } from '../ConfigContext';
|
||||
import { Recipe } from '../../recipe';
|
||||
import { saveRecipe, generateRecipeFilename } from '../../recipe/recipeStorage';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip';
|
||||
import ThemeSelector from './ThemeSelector';
|
||||
import GooseLogo from '../GooseLogo';
|
||||
|
||||
interface SidebarProps {
|
||||
onSelectSession: (sessionId: string) => void;
|
||||
@@ -96,14 +96,64 @@ const AppSidebar: React.FC<SidebarProps> = ({ setIsGoosehintsModalOpen, setView
|
||||
return (
|
||||
<>
|
||||
<SidebarContent>
|
||||
{/* Action Buttons */}
|
||||
<div className="px-1 pt-14 py-0 space-y-2 relative">
|
||||
{/* <SidebarHeader>
|
||||
<div className="flex items-center gap-2 pt-12 pb-4">
|
||||
<GooseLogo size="small" />
|
||||
<span className="text-base">codename goose</span>
|
||||
</div>
|
||||
</SidebarHeader> */}
|
||||
|
||||
{/* Menu */}
|
||||
<div className="px-1 py-0 pt-14 space-y-2 relative">
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.createChatWindow(
|
||||
undefined,
|
||||
window.appConfig.get('GOOSE_WORKING_DIR') as string | undefined
|
||||
);
|
||||
}}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<ChatSmart className="w-4 h-4" />
|
||||
<span className="text-sm">New session</span>
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Start a new session in the current directory</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.directoryChooser();
|
||||
}}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Folder className="w-4 h-4" />
|
||||
<span className="text-sm">Open directory</span>
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Start a new session in a different directory</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{setView && (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setView('sessions')}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
@@ -123,7 +173,7 @@ const AppSidebar: React.FC<SidebarProps> = ({ setIsGoosehintsModalOpen, setView
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setView('schedules')}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
@@ -138,106 +188,17 @@ const AppSidebar: React.FC<SidebarProps> = ({ setIsGoosehintsModalOpen, setView
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{setIsGoosehintsModalOpen && (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setIsGoosehintsModalOpen(true)}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Idea className="w-4 h-4" />
|
||||
Configure .goosehints
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Customize instructions</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{recipeConfig ? (
|
||||
<>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.createChatWindow(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
recipeConfig as Recipe,
|
||||
'recipeEditor'
|
||||
);
|
||||
}}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Send className="w-4 h-4" />
|
||||
View recipe
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>View the recipe you're using</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={handleSaveRecipeClick}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Save className="w-4 h-4" />
|
||||
Save recipe
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Save this recipe for reuse</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.logInfo('Make Agent button clicked');
|
||||
window.dispatchEvent(new CustomEvent('make-agent-from-chat'));
|
||||
}}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<Send className="w-4 h-4" />
|
||||
Make Agent from this session
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Make a custom agent you can share or reuse</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{setView && (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setView('recipes')}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
<FileText className="w-4 h-4" />
|
||||
Recipe Library
|
||||
Recipe library
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -247,12 +208,12 @@ const AppSidebar: React.FC<SidebarProps> = ({ setIsGoosehintsModalOpen, setView
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* {setView && (
|
||||
{setView && (
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<Button
|
||||
onClick={() => setView('settings')}
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-neutral-200 transition-all duration-200"
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="flex gap-2 items-center text-text-default">
|
||||
@@ -265,77 +226,69 @@ const AppSidebar: React.FC<SidebarProps> = ({ setIsGoosehintsModalOpen, setView
|
||||
<p>View all settings and options</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Theme Selector */}
|
||||
<div className="mt-4">
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
<div className="mt-4">{/* <ThemeSelector /> */}</div>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.createChatWindow(
|
||||
undefined,
|
||||
window.appConfig.get('GOOSE_WORKING_DIR') as string | undefined
|
||||
);
|
||||
}}
|
||||
className="px-3 hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
>
|
||||
<ChatSmart className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Start a new session in the current directory</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="flex items-center gap-2">
|
||||
<GooseLogo size="small" />
|
||||
<span className="text-base">codename goose</span>
|
||||
</div>
|
||||
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electron.directoryChooser();
|
||||
}}
|
||||
className="px-3 hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
>
|
||||
<Folder className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Start a new session in a different directory</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="pb-4">
|
||||
<div className="flex gap-2">
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href="https://discord.gg/pvQ8S2e5"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center h-8 w-8 p-0 rounded-full hover:bg-neutral-200 transition-all duration-200"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Join our Discord</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/* <Button
|
||||
onClick={async () => {
|
||||
await remove('GOOSE_PROVIDER', false);
|
||||
await remove('GOOSE_MODEL', false);
|
||||
setView?.('welcome');
|
||||
}}
|
||||
className="px-3 hover:shadow-default hover:bg-background-default transition-all duration-200 text-red-400 hover:text-red-300"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
>
|
||||
<Refresh />
|
||||
</Button> */}
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href="https://www.linkedin.com/company/block-opensource"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center h-8 w-8 p-0 rounded-full hover:bg-neutral-200 transition-all duration-200"
|
||||
>
|
||||
<LinkedIn className="w-4 h-4" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Follow us on LinkedIn</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
onClick={() => setView?.('settings')}
|
||||
className="px-3 hover:shadow-default hover:bg-background-default transition-all duration-200"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
>
|
||||
<Gear />
|
||||
</Button>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href="https://www.youtube.com/@blockopensource"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center h-8 w-8 p-0 rounded-full hover:bg-neutral-200 transition-all duration-200"
|
||||
>
|
||||
<Youtube className="w-4 h-4" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Watch on YouTube</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarFooter>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useTextAnimator } from '../hooks/use-text-animator';
|
||||
import { Card } from './ui/card';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { SessionInsights } from './sessions/SessionsInsights';
|
||||
|
||||
// Register GSAP plugins
|
||||
gsap.registerPlugin();
|
||||
@@ -81,9 +80,9 @@ export default function Splash({ append, activities, title }: SplashProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-col">
|
||||
{title && (
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<div className="flex items-center px-4 py-2 mb-4">
|
||||
<span className="w-2 h-2 rounded-full bg-blockTeal mr-2" />
|
||||
<span className="text-sm">
|
||||
<span className="text-text-muted">Agent</span>{' '}
|
||||
@@ -91,45 +90,33 @@ export default function Splash({ append, activities, title }: SplashProps) {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="h-full flex flex-col pb-12">
|
||||
{/* <div className="relative text-textStandard mb-12">
|
||||
<div className="w-min animate-[flyin_2s_var(--spring-easing)_forwards]">
|
||||
<GooseLogo />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex flex-col mb-10 mt-5">
|
||||
<h1 className="text-text-prominent text-4xl font-light">
|
||||
<span>{greeting.prefix}</span>
|
||||
</h1>
|
||||
<div className="text-text-muted text-4xl font-light inline">{greeting.message}</div>
|
||||
{/* Compact greeting section */}
|
||||
<div className="flex flex-col px-6 mb-0">
|
||||
<h1 className="text-text-prominent text-4xl font-light mb-2">
|
||||
<span>{greeting.prefix}</span>
|
||||
{/* <span className="text-text-muted">{greeting.message}</span> */}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{messagePill && (
|
||||
<div className="mb-4 p-3 rounded-lg border animate-[fadein_500ms_ease-in_forwards]">
|
||||
{messagePill.replace(/^message:/i, '').trim()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col">
|
||||
{messagePill && (
|
||||
<div className="mb-6 p-4 rounded-lg border animate-[fadein_500ms_ease-in_forwards]">
|
||||
{messagePill.replace(/^message:/i, '').trim()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-4 animate-[fadein_500ms_ease-in_forwards]">
|
||||
{remainingPills.map((content, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
onClick={() => append(content)}
|
||||
title={content.length > 100 ? content : undefined}
|
||||
className="cursor-pointer px-6 w-[256px]"
|
||||
>
|
||||
{content.length > 100 ? content.slice(0, 100) + '...' : content}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="animate-[fadein_500ms_ease-in_forwards]">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 animate-[fadein_500ms_ease-in_forwards]">
|
||||
{remainingPills.map((content, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
onClick={() => append(content)}
|
||||
title={content.length > 100 ? content : undefined}
|
||||
className="cursor-pointer px-4 py-2 w-[200px]"
|
||||
>
|
||||
{content.length > 100 ? content.slice(0, 100) + '...' : content}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useConfig } from '../ConfigContext';
|
||||
import { useModelAndProvider } from '../ModelAndProviderContext';
|
||||
import { Message } from '../../types/message';
|
||||
import { ManualSummarizeButton } from '../context_management/ManualSummaryButton';
|
||||
import { DirSwitcher } from './DirSwitcher';
|
||||
|
||||
const TOKEN_LIMIT_DEFAULT = 128000; // fallback for custom models that the backend doesn't know about
|
||||
const TOKEN_WARNING_THRESHOLD = 0.8; // warning shows at 80% of the token limit
|
||||
@@ -202,30 +203,36 @@ export default function BottomMenu({
|
||||
}, [isModelMenuOpen]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full justify-end items-center pb-2 pr-3 transition-colors relative text-xs align-middle animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
{/* Tool and Token count */}
|
||||
{<BottomMenuAlertPopover alerts={alerts} />}
|
||||
<div className="flex w-full justify-between items-center pb-2 pr-3 transition-colors relative text-xs align-middle animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<div>
|
||||
<DirSwitcher hasMessages={messages.length > 0} />
|
||||
</div>
|
||||
|
||||
{/* Model Selector Dropdown */}
|
||||
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
||||
<div className="flex items-center">
|
||||
{/* Tool and Token count */}
|
||||
{<BottomMenuAlertPopover alerts={alerts} />}
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
{/* Model Selector Dropdown */}
|
||||
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
||||
|
||||
{/* Goose Mode Selector Dropdown */}
|
||||
<BottomMenuModeSelection setView={setView} />
|
||||
{/* Separator */}
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
|
||||
{/* Summarize Context Button - ADD THIS */}
|
||||
{messages.length > 0 && (
|
||||
<>
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
<ManualSummarizeButton
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* Goose Mode Selector Dropdown */}
|
||||
<BottomMenuModeSelection setView={setView} />
|
||||
|
||||
{/* Summarize Context Button - ADD THIS */}
|
||||
{messages.length > 0 && (
|
||||
<>
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
<ManualSummarizeButton
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Document } from '../icons';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip';
|
||||
|
||||
interface DirSwitcherProps {
|
||||
hasMessages?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DirSwitcher: React.FC<DirSwitcherProps> = ({
|
||||
hasMessages = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||
|
||||
const handleDirectoryChange = async () => {
|
||||
if (hasMessages) {
|
||||
window.electron.directoryChooser();
|
||||
} else {
|
||||
window.electron.directoryChooser(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip open={isTooltipOpen} onOpenChange={setIsTooltipOpen}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
className={`z-[100] hover:cursor-pointer text-text-default/70 hover:text-text-default text-xs flex items-center transition-colors pl-1 [&>svg]:size-4 ${className}`}
|
||||
onClick={handleDirectoryChange}
|
||||
>
|
||||
<Document className="mr-1" />
|
||||
<div className="max-w-[200px] truncate [direction:rtl]">
|
||||
{String(window.appConfig.get('GOOSE_WORKING_DIR'))}
|
||||
</div>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-96 overflow-auto scrollbar-thin" side="top">
|
||||
{window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function Discord({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function LinkedIn({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function Youtube({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -9,9 +9,11 @@ import ChevronUp from './ChevronUp';
|
||||
import { ChevronRight } from './ChevronRight';
|
||||
import Close from './Close';
|
||||
import Copy from './Copy';
|
||||
import Discord from './Discord';
|
||||
import Document from './Document';
|
||||
import Edit from './Edit';
|
||||
import Idea from './Idea';
|
||||
import LinkedIn from './LinkedIn';
|
||||
import More from './More';
|
||||
import Refresh from './Refresh';
|
||||
import SensitiveHidden from './SensitiveHidden';
|
||||
@@ -20,6 +22,7 @@ import Send from './Send';
|
||||
import Settings from './Settings';
|
||||
import Time from './Time';
|
||||
import { Gear } from './Gear';
|
||||
import Youtube from './Youtube';
|
||||
|
||||
export {
|
||||
ArrowDown,
|
||||
@@ -33,10 +36,12 @@ export {
|
||||
ChevronUp,
|
||||
Close,
|
||||
Copy,
|
||||
Discord,
|
||||
Document,
|
||||
Edit,
|
||||
Idea,
|
||||
Gear,
|
||||
LinkedIn,
|
||||
More,
|
||||
Refresh,
|
||||
SensitiveHidden,
|
||||
@@ -44,4 +49,5 @@ export {
|
||||
Send,
|
||||
Settings,
|
||||
Time,
|
||||
Youtube,
|
||||
};
|
||||
|
||||
@@ -11,6 +11,36 @@ interface SessionInsights {
|
||||
totalTokens: number;
|
||||
}
|
||||
|
||||
// Greeting component
|
||||
function GreetingCard() {
|
||||
const [selectedGreeting, setSelectedGreeting] = useState<{
|
||||
prefix: string;
|
||||
} | null>(null);
|
||||
|
||||
// Select a random greeting on component mount
|
||||
useEffect(() => {
|
||||
const prefixes = ['Hello.', 'Welcome.', 'Greetings.', 'Welcome back.', 'Hello there.'];
|
||||
const randomPrefixIndex = Math.floor(Math.random() * prefixes.length);
|
||||
|
||||
setSelectedGreeting({
|
||||
prefix: prefixes[randomPrefixIndex],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const greeting = selectedGreeting || { prefix: 'Hello.', message: ' How can I help you today?' };
|
||||
|
||||
return (
|
||||
<Card className="col-span-2 border-none animate-in fade-in slide-in-from-bottom-8 duration-500 bg-background-accent">
|
||||
<CardContent className="flex flex-col justify-end h-full py-4">
|
||||
<h1 className="text-text-on-accent text-4xl font-light">
|
||||
<span>{greeting.prefix}</span>
|
||||
</h1>
|
||||
{/* <p className="text-text-on-accent font-light text-lg">{greeting.message}</p> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function SessionInsights() {
|
||||
const [insights, setInsights] = useState<SessionInsights | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -55,45 +85,49 @@ export function SessionInsights() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{/* Total Sessions Card */}
|
||||
<Card className="w-full animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end" ref={totalSessionsRef}>
|
||||
{insights?.totalSessions}
|
||||
</p>
|
||||
<CardDescription>Total sessions</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<>
|
||||
<div className="grid grid-cols-4 gap-4 mb-4 mt-2">
|
||||
<GreetingCard />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{/* Total Sessions Card */}
|
||||
<Card className="w-full animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end" ref={totalSessionsRef}>
|
||||
{insights?.totalSessions}
|
||||
</p>
|
||||
<CardDescription>Total sessions</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Average Duration Card */}
|
||||
<Card className="w-full animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
<span ref={avgDurationRef}>{insights?.avgSessionDuration?.toFixed(1)}m</span>
|
||||
</p>
|
||||
<CardDescription>Avg. duration</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Average Duration Card */}
|
||||
<Card className="w-full animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end">
|
||||
<span ref={avgDurationRef}>{insights?.avgSessionDuration?.toFixed(1)}m</span>
|
||||
</p>
|
||||
<CardDescription>Avg. duration</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Total Tokens Card */}
|
||||
<Card className="w-full col-span-2 animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end" ref={totalTokensRef}>
|
||||
{insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''}
|
||||
</p>
|
||||
<CardDescription>Total tokens (millions)</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Total Tokens Card */}
|
||||
<Card className="w-full col-span-2 animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent className="flex flex-col justify-end h-full">
|
||||
<div className="flex flex-col justify-end">
|
||||
<p className="text-4xl font-mono font-light flex items-end" ref={totalTokensRef}>
|
||||
{insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''}
|
||||
</p>
|
||||
<CardDescription>Total tokens (millions)</CardDescription>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Activity Heatmap Card */}
|
||||
{/* <Card className="w-full col-span-4">
|
||||
{/* Activity Heatmap Card */}
|
||||
{/* <Card className="w-full col-span-4">
|
||||
<CardContent>
|
||||
<CardDescription className="mb-4">
|
||||
<span className="text-lg text-text-default">Activity Heatmap</span>
|
||||
@@ -102,25 +136,26 @@ export function SessionInsights() {
|
||||
</CardContent>
|
||||
</Card> */}
|
||||
|
||||
{/* Most Active Directories Card */}
|
||||
<Card className="w-full col-span-4 animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent>
|
||||
<CardDescription className="mb-4">
|
||||
<span className="text-lg text-text-default">Active directories</span>
|
||||
</CardDescription>
|
||||
<div className="space-y-2 ">
|
||||
{insights.mostActiveDirs.map(([dir, count], index) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FolderOpen className="h-4 w-4 text-text-muted" />
|
||||
<span className="truncate max-w-[200px]">{dir}</span>
|
||||
{/* Most Active Directories Card */}
|
||||
<Card className="w-full col-span-4 animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<CardContent>
|
||||
<CardDescription className="mb-4">
|
||||
<span className="text-lg text-text-default">Active directories</span>
|
||||
</CardDescription>
|
||||
<div className="space-y-2 ">
|
||||
{insights.mostActiveDirs.map(([dir, count], index) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FolderOpen className="h-4 w-4 text-text-muted" />
|
||||
<span className="truncate max-w-[400px] rtl">{dir}</span>
|
||||
</div>
|
||||
<span className="text-text-default font-mono font-light">{count} sessions</span>
|
||||
</div>
|
||||
<span className="text-text-default font-mono font-light">{count} sessions</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+11
-12
@@ -11,6 +11,7 @@ import {
|
||||
Tray,
|
||||
App,
|
||||
globalShortcut,
|
||||
shell,
|
||||
} from 'electron';
|
||||
import type { OpenDialogReturnValue } from 'electron';
|
||||
import { Buffer } from 'node:buffer';
|
||||
@@ -528,12 +529,18 @@ const createChat = async (
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
// Open all links in external browser
|
||||
if (url.startsWith('http:') || url.startsWith('https:')) {
|
||||
electron.shell.openExternal(url);
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
}
|
||||
return { action: 'allow' };
|
||||
});
|
||||
|
||||
// Handle new-window events (alternative approach for external links)
|
||||
mainWindow.webContents.on('new-window', (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
// Load the index.html of the app.
|
||||
let queryParams = '';
|
||||
if (query) {
|
||||
@@ -1756,18 +1763,10 @@ app.whenReady().then(async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// On macOS, use the 'open' command with Chrome
|
||||
if (process.platform === 'darwin') {
|
||||
spawn('open', ['-a', 'Google Chrome', url]);
|
||||
} else if (process.platform === 'win32') {
|
||||
// On Windows, start is built-in command of cmd.exe
|
||||
spawn('cmd.exe', ['/c', 'start', '', 'chrome', url]);
|
||||
} else {
|
||||
// On Linux, use xdg-open with chrome
|
||||
spawn('xdg-open', [url]);
|
||||
}
|
||||
// Use shell.openExternal to open URL in user's default browser
|
||||
shell.openExternal(url);
|
||||
} catch (error) {
|
||||
console.error('Error opening URL in Chrome:', error);
|
||||
console.error('Error opening URL in browser:', error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
--color-black: #000000;
|
||||
|
||||
--color-neutral-100: #e8eaf2;
|
||||
--color-neutral-200: #ccd2dd;
|
||||
--color-neutral-200: #d4dae2;
|
||||
--color-neutral-300: #a1a7b0;
|
||||
--color-neutral-400: #6e747e;
|
||||
--color-neutral-500: #32353b;
|
||||
|
||||
Reference in New Issue
Block a user