mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
feat: session manger (#867)
* feat: init session manger * feat: persist selected app to localStorage - Save app selection when switching providers - Restore last selected app on page load * feat: persist current view to localStorage - Save view selection when switching tabs - Restore last selected view on page load * styles: update ui * feat: Improve macOS Terminal activation and refactor Kitty launch to use command with user's default shell. * fix: session view * feat: toc * feat: Implement FlexSearch for improved session search functionality. * feat: Redesign session manager search and filter UI for a more compact and dynamic experience. * refactor: modularize session manager by extracting components and utility functions into dedicated files. * feat: Enhance session terminal launching with support for iTerm2, Ghostty, WezTerm, and Alacritty, including UI and custom configuration options. * feat: Conditionally render terminal selection and resume session buttons only on macOS.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { List, X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface TocItem {
|
||||
index: number;
|
||||
preview: string;
|
||||
ts?: number;
|
||||
}
|
||||
|
||||
interface SessionTocSidebarProps {
|
||||
items: TocItem[];
|
||||
onItemClick: (index: number) => void;
|
||||
}
|
||||
|
||||
export function SessionTocSidebar({
|
||||
items,
|
||||
onItemClick,
|
||||
}: SessionTocSidebarProps) {
|
||||
if (items.length <= 2) return null;
|
||||
|
||||
return (
|
||||
<div className="w-64 border-l shrink-0 hidden xl:block">
|
||||
<div className="p-3 border-b">
|
||||
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
<List className="size-3.5" />
|
||||
<span>对话目录</span>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea className="h-[calc(100%-40px)]">
|
||||
<div className="p-2 space-y-0.5">
|
||||
{items.map((item, tocIndex) => (
|
||||
<button
|
||||
key={item.index}
|
||||
type="button"
|
||||
onClick={() => onItemClick(item.index)}
|
||||
className={cn(
|
||||
"w-full text-left px-2 py-1.5 rounded text-xs transition-colors",
|
||||
"hover:bg-muted/80 text-muted-foreground hover:text-foreground",
|
||||
"flex items-start gap-2"
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0 w-4 h-4 rounded-full bg-primary/10 text-primary text-[10px] flex items-center justify-center font-medium">
|
||||
{tocIndex + 1}
|
||||
</span>
|
||||
<span className="line-clamp-2 leading-snug">{item.preview}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SessionTocDialogProps {
|
||||
items: TocItem[];
|
||||
onItemClick: (index: number) => void;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function SessionTocDialog({
|
||||
items,
|
||||
onItemClick,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: SessionTocDialogProps) {
|
||||
if (items.length <= 2) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="fixed bottom-20 right-4 xl:hidden size-10 rounded-full shadow-lg z-30"
|
||||
>
|
||||
<List className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="max-w-md max-h-[70vh] flex flex-col p-0 gap-0"
|
||||
zIndex="alert"
|
||||
onInteractOutside={() => onOpenChange(false)}
|
||||
onEscapeKeyDown={() => onOpenChange(false)}
|
||||
>
|
||||
<DialogHeader className="px-4 py-3 relative border-b">
|
||||
<DialogTitle className="flex items-center gap-2 text-base font-semibold">
|
||||
<List className="size-4 text-primary" />
|
||||
对话目录
|
||||
</DialogTitle>
|
||||
<DialogClose
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1.5 hover:bg-muted transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||
aria-label="关闭"
|
||||
>
|
||||
<X className="size-4 text-muted-foreground" />
|
||||
</DialogClose>
|
||||
</DialogHeader>
|
||||
<div className="overflow-y-auto max-h-[calc(70vh-80px)]">
|
||||
<div className="p-3 pb-4 space-y-1">
|
||||
{items.map((item, tocIndex) => (
|
||||
<button
|
||||
key={item.index}
|
||||
type="button"
|
||||
onClick={() => onItemClick(item.index)}
|
||||
className={cn(
|
||||
"w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all",
|
||||
"hover:bg-primary/10 text-foreground",
|
||||
"flex items-start gap-3",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary focus:ring-inset"
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0 w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs flex items-center justify-center font-semibold">
|
||||
{tocIndex + 1}
|
||||
</span>
|
||||
<span className="line-clamp-2 leading-relaxed pt-0.5">
|
||||
{item.preview}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user