mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
move top icons to sidebar buttons
This commit is contained in:
parent
7dbfcb72f4
commit
efe2a93d8a
2 changed files with 98 additions and 93 deletions
|
|
@ -5,7 +5,7 @@ import { RunEvent, ListRunsResponse } from '@x/shared/src/runs.js';
|
||||||
import type { LanguageModelUsage, ToolUIPart } from 'ai';
|
import type { LanguageModelUsage, ToolUIPart } from 'ai';
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import { CheckIcon, LoaderIcon, PanelLeftIcon, Maximize2, Minimize2, ChevronLeftIcon, ChevronRightIcon, SquarePen, SearchIcon, HistoryIcon, RadioIcon, SquareIcon, Globe } from 'lucide-react';
|
import { CheckIcon, LoaderIcon, PanelLeftIcon, Maximize2, Minimize2, ChevronLeftIcon, ChevronRightIcon, SquarePen, HistoryIcon } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { MarkdownEditor, type MarkdownEditorHandle } from './components/markdown-editor';
|
import { MarkdownEditor, type MarkdownEditorHandle } from './components/markdown-editor';
|
||||||
import { ChatSidebar } from './components/chat-sidebar';
|
import { ChatSidebar } from './components/chat-sidebar';
|
||||||
|
|
@ -454,28 +454,12 @@ function FixedSidebarToggle({
|
||||||
onNavigateForward,
|
onNavigateForward,
|
||||||
canNavigateBack,
|
canNavigateBack,
|
||||||
canNavigateForward,
|
canNavigateForward,
|
||||||
onNewChat,
|
|
||||||
onOpenSearch,
|
|
||||||
meetingState,
|
|
||||||
meetingSummarizing,
|
|
||||||
meetingAvailable,
|
|
||||||
onToggleMeeting,
|
|
||||||
isBrowserOpen,
|
|
||||||
onToggleBrowser,
|
|
||||||
leftInsetPx,
|
leftInsetPx,
|
||||||
}: {
|
}: {
|
||||||
onNavigateBack: () => void
|
onNavigateBack: () => void
|
||||||
onNavigateForward: () => void
|
onNavigateForward: () => void
|
||||||
canNavigateBack: boolean
|
canNavigateBack: boolean
|
||||||
canNavigateForward: boolean
|
canNavigateForward: boolean
|
||||||
onNewChat: () => void
|
|
||||||
onOpenSearch: () => void
|
|
||||||
meetingState: MeetingTranscriptionState
|
|
||||||
meetingSummarizing: boolean
|
|
||||||
meetingAvailable: boolean
|
|
||||||
onToggleMeeting: () => void
|
|
||||||
isBrowserOpen: boolean
|
|
||||||
onToggleBrowser: () => void
|
|
||||||
leftInsetPx: number
|
leftInsetPx: number
|
||||||
}) {
|
}) {
|
||||||
const { toggleSidebar, state } = useSidebar()
|
const { toggleSidebar, state } = useSidebar()
|
||||||
|
|
@ -493,74 +477,6 @@ function FixedSidebarToggle({
|
||||||
>
|
>
|
||||||
<PanelLeftIcon className="size-5" />
|
<PanelLeftIcon className="size-5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onNewChat}
|
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
|
||||||
style={{ marginLeft: TITLEBAR_BUTTON_GAP_PX }}
|
|
||||||
aria-label="New chat"
|
|
||||||
>
|
|
||||||
<SquarePen className="size-5" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onOpenSearch}
|
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
|
||||||
style={{ marginLeft: TITLEBAR_BUTTON_GAP_PX }}
|
|
||||||
aria-label="Search"
|
|
||||||
>
|
|
||||||
<SearchIcon className="size-5" />
|
|
||||||
</button>
|
|
||||||
{meetingAvailable && (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onToggleMeeting}
|
|
||||||
disabled={meetingState === 'connecting' || meetingState === 'stopping' || meetingSummarizing}
|
|
||||||
className={cn(
|
|
||||||
"flex h-8 w-8 items-center justify-center rounded-md transition-colors disabled:pointer-events-none",
|
|
||||||
meetingSummarizing
|
|
||||||
? "text-muted-foreground"
|
|
||||||
: meetingState === 'recording'
|
|
||||||
? "text-red-500 hover:bg-accent"
|
|
||||||
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
||||||
)}
|
|
||||||
style={{ marginLeft: TITLEBAR_BUTTON_GAP_PX }}
|
|
||||||
>
|
|
||||||
{meetingSummarizing || meetingState === 'connecting' ? (
|
|
||||||
<LoaderIcon className="size-4 animate-spin" />
|
|
||||||
) : meetingState === 'recording' ? (
|
|
||||||
<SquareIcon className="size-4 animate-pulse" />
|
|
||||||
) : (
|
|
||||||
<RadioIcon className="size-5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{meetingSummarizing ? 'Generating meeting notes...' : meetingState === 'connecting' ? 'Starting transcription...' : meetingState === 'recording' ? 'Stop meeting notes' : 'Take new meeting notes'}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onToggleBrowser}
|
|
||||||
className={cn(
|
|
||||||
"flex h-8 w-8 items-center justify-center rounded-md transition-colors",
|
|
||||||
isBrowserOpen
|
|
||||||
? "bg-accent text-foreground"
|
|
||||||
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
||||||
)}
|
|
||||||
style={{ marginLeft: TITLEBAR_BUTTON_GAP_PX }}
|
|
||||||
aria-label={isBrowserOpen ? "Close browser" : "Open browser"}
|
|
||||||
>
|
|
||||||
<Globe className="size-5" />
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">{isBrowserOpen ? 'Close browser' : 'Open browser'}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
{/* Back / Forward navigation */}
|
{/* Back / Forward navigation */}
|
||||||
{isCollapsed && (
|
{isCollapsed && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -4149,6 +4065,14 @@ function App() {
|
||||||
}}
|
}}
|
||||||
backgroundTasks={backgroundTasks}
|
backgroundTasks={backgroundTasks}
|
||||||
selectedBackgroundTask={selectedBackgroundTask}
|
selectedBackgroundTask={selectedBackgroundTask}
|
||||||
|
onNewChat={handleNewChatTab}
|
||||||
|
onOpenSearch={() => setIsSearchOpen(true)}
|
||||||
|
meetingState={meetingTranscription.state}
|
||||||
|
meetingSummarizing={meetingSummarizing}
|
||||||
|
meetingAvailable={voiceAvailable}
|
||||||
|
onToggleMeeting={() => { void handleToggleMeeting() }}
|
||||||
|
isBrowserOpen={isBrowserOpen}
|
||||||
|
onToggleBrowser={handleToggleBrowser}
|
||||||
/>
|
/>
|
||||||
<SidebarInset
|
<SidebarInset
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -4651,14 +4575,6 @@ function App() {
|
||||||
onNavigateForward={() => { void navigateForward() }}
|
onNavigateForward={() => { void navigateForward() }}
|
||||||
canNavigateBack={canNavigateBack}
|
canNavigateBack={canNavigateBack}
|
||||||
canNavigateForward={canNavigateForward}
|
canNavigateForward={canNavigateForward}
|
||||||
onNewChat={handleNewChatTab}
|
|
||||||
onOpenSearch={() => setIsSearchOpen(true)}
|
|
||||||
meetingState={meetingTranscription.state}
|
|
||||||
meetingSummarizing={meetingSummarizing}
|
|
||||||
meetingAvailable={voiceAvailable}
|
|
||||||
onToggleMeeting={() => { void handleToggleMeeting() }}
|
|
||||||
isBrowserOpen={isBrowserOpen}
|
|
||||||
onToggleBrowser={handleToggleBrowser}
|
|
||||||
leftInsetPx={isMac ? MACOS_TRAFFIC_LIGHTS_RESERVED_PX : 0}
|
leftInsetPx={isMac ? MACOS_TRAFFIC_LIGHTS_RESERVED_PX : 0}
|
||||||
/>
|
/>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ import {
|
||||||
FilePlus,
|
FilePlus,
|
||||||
Folder,
|
Folder,
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
|
Globe,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
Mic,
|
Mic,
|
||||||
Network,
|
Network,
|
||||||
Pencil,
|
Pencil,
|
||||||
|
Radio,
|
||||||
|
SearchIcon,
|
||||||
|
SquarePen,
|
||||||
Table2,
|
Table2,
|
||||||
Plug,
|
Plug,
|
||||||
LoaderIcon,
|
LoaderIcon,
|
||||||
|
|
@ -90,6 +94,7 @@ import { SettingsDialog } from "@/components/settings-dialog"
|
||||||
import { toast } from "@/lib/toast"
|
import { toast } from "@/lib/toast"
|
||||||
import { useBilling } from "@/hooks/useBilling"
|
import { useBilling } from "@/hooks/useBilling"
|
||||||
import { ServiceEvent } from "@x/shared/src/service-events.js"
|
import { ServiceEvent } from "@x/shared/src/service-events.js"
|
||||||
|
import type { MeetingTranscriptionState } from "@/hooks/useMeetingTranscription"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
|
|
@ -172,6 +177,14 @@ type SidebarContentPanelProps = {
|
||||||
tasksActions?: TasksActions
|
tasksActions?: TasksActions
|
||||||
backgroundTasks?: BackgroundTaskItem[]
|
backgroundTasks?: BackgroundTaskItem[]
|
||||||
selectedBackgroundTask?: string | null
|
selectedBackgroundTask?: string | null
|
||||||
|
onNewChat?: () => void
|
||||||
|
onOpenSearch?: () => void
|
||||||
|
meetingState?: MeetingTranscriptionState
|
||||||
|
meetingSummarizing?: boolean
|
||||||
|
meetingAvailable?: boolean
|
||||||
|
onToggleMeeting?: () => void
|
||||||
|
isBrowserOpen?: boolean
|
||||||
|
onToggleBrowser?: () => void
|
||||||
} & React.ComponentProps<typeof Sidebar>
|
} & React.ComponentProps<typeof Sidebar>
|
||||||
|
|
||||||
const sectionTabs: { id: ActiveSection; label: string }[] = [
|
const sectionTabs: { id: ActiveSection; label: string }[] = [
|
||||||
|
|
@ -395,6 +408,14 @@ export function SidebarContentPanel({
|
||||||
tasksActions,
|
tasksActions,
|
||||||
backgroundTasks = [],
|
backgroundTasks = [],
|
||||||
selectedBackgroundTask,
|
selectedBackgroundTask,
|
||||||
|
onNewChat,
|
||||||
|
onOpenSearch,
|
||||||
|
meetingState = 'idle',
|
||||||
|
meetingSummarizing = false,
|
||||||
|
meetingAvailable = false,
|
||||||
|
onToggleMeeting,
|
||||||
|
isBrowserOpen = false,
|
||||||
|
onToggleBrowser,
|
||||||
...props
|
...props
|
||||||
}: SidebarContentPanelProps) {
|
}: SidebarContentPanelProps) {
|
||||||
const { activeSection, setActiveSection } = useSidebarSection()
|
const { activeSection, setActiveSection } = useSidebarSection()
|
||||||
|
|
@ -488,6 +509,74 @@ export function SidebarContentPanel({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Quick action buttons */}
|
||||||
|
<div className="titlebar-no-drag flex flex-col gap-0.5 px-2 pb-1">
|
||||||
|
{onNewChat && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onNewChat}
|
||||||
|
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<SquarePen className="size-4" />
|
||||||
|
<span>New chat</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{onOpenSearch && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onOpenSearch}
|
||||||
|
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<SearchIcon className="size-4" />
|
||||||
|
<span>Search</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{meetingAvailable && onToggleMeeting && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onToggleMeeting}
|
||||||
|
disabled={meetingState === 'connecting' || meetingState === 'stopping' || meetingSummarizing}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors disabled:pointer-events-none",
|
||||||
|
meetingState === 'recording'
|
||||||
|
? "text-red-500 hover:bg-sidebar-accent"
|
||||||
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{meetingSummarizing || meetingState === 'connecting' ? (
|
||||||
|
<LoaderIcon className="size-4 animate-spin" />
|
||||||
|
) : meetingState === 'recording' ? (
|
||||||
|
<Square className="size-4 animate-pulse" />
|
||||||
|
) : (
|
||||||
|
<Radio className="size-4" />
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{meetingSummarizing
|
||||||
|
? 'Generating notes…'
|
||||||
|
: meetingState === 'connecting'
|
||||||
|
? 'Starting…'
|
||||||
|
: meetingState === 'recording'
|
||||||
|
? 'Stop recording'
|
||||||
|
: 'Take meeting notes'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{onToggleBrowser && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onToggleBrowser}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||||
|
isBrowserOpen
|
||||||
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||||
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Globe className="size-4" />
|
||||||
|
<span>Run browser task</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
{activeSection === "knowledge" && (
|
{activeSection === "knowledge" && (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue