Merge branch 'cv-main'

This commit is contained in:
xintaofei
2026-04-23 11:26:02 +08:00
2 changed files with 111 additions and 137 deletions

View File

@@ -23,12 +23,6 @@ import {
CommandItem, CommandItem,
CommandList, CommandList,
} from "@/components/ui/command" } from "@/components/ui/command"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
interface ConversationContextBarProps { interface ConversationContextBarProps {
@@ -65,53 +59,51 @@ export const ConversationContextBar = memo(function ConversationContextBar({
branches.get(ownFolder.id) ?? ownFolder.git_branch ?? null branches.get(ownFolder.id) ?? ownFolder.git_branch ?? null
return ( return (
<TooltipProvider> <div className="flex shrink-0 items-center gap-1.5 px-2 pt-2 text-xs text-muted-foreground">
<div className="flex shrink-0 items-center gap-1.5 px-2 pt-2 text-xs text-muted-foreground"> <FolderPicker
<FolderPicker folders={folders}
folders={folders} currentFolderId={ownFolder.id}
currentFolderId={ownFolder.id} currentFolderName={ownFolder.name}
currentFolderName={ownFolder.name} editable={isNewConversation}
editable={isNewConversation} onSelect={async (folderId) => {
onSelect={async (folderId) => { const target = folders.find((f) => f.id === folderId)
const target = folders.find((f) => f.id === folderId) if (!target) return
if (!target) return try {
try { setTabFolder(ownTab.id, target.id, target.path)
setTabFolder(ownTab.id, target.id, target.path) toast.success(t("toasts.folderChanged", { name: target.name }))
toast.success(t("toasts.folderChanged", { name: target.name })) } catch (err) {
} catch (err) { console.error(
console.error( "[ConversationContextBar] switch folder failed:",
"[ConversationContextBar] switch folder failed:", err
err )
) toast.error(t("toasts.openFolderFailed"))
toast.error(t("toasts.openFolderFailed")) }
} }}
}} labelEmpty={t("noFolders")}
labelEmpty={t("noFolders")} labelSearch={t("searchFolder")}
labelSearch={t("searchFolder")} />
/>
<BranchPicker <BranchPicker
folderId={ownFolder.id} folderId={ownFolder.id}
folderPath={ownFolder.path} folderPath={ownFolder.path}
currentBranch={currentBranch} currentBranch={currentBranch}
onCheckout={async (branchName) => { onCheckout={async (branchName) => {
const taskId = `checkout-${ownFolder.id}-${Date.now()}` const taskId = `checkout-${ownFolder.id}-${Date.now()}`
addTask(taskId, tBd("tasks.checkoutTo", { branchName })) addTask(taskId, tBd("tasks.checkoutTo", { branchName }))
updateTask(taskId, { status: "running" }) updateTask(taskId, { status: "running" })
try { try {
await gitCheckout(ownFolder.path, branchName) await gitCheckout(ownFolder.path, branchName)
setBranch(ownFolder.id, branchName) setBranch(ownFolder.id, branchName)
await refreshFolder(ownFolder.id) await refreshFolder(ownFolder.id)
updateTask(taskId, { status: "completed" }) updateTask(taskId, { status: "completed" })
} catch (err) { } catch (err) {
const msg = err instanceof Error ? err.message : String(err) const msg = err instanceof Error ? err.message : String(err)
updateTask(taskId, { status: "failed", error: msg }) updateTask(taskId, { status: "failed", error: msg })
toast.error(msg) toast.error(msg)
} }
}} }}
/> />
</div> </div>
</TooltipProvider>
) )
}) })
@@ -146,6 +138,7 @@ const FolderPicker = memo(function FolderPicker({
<Button <Button
variant="outline" variant="outline"
size="xs" size="xs"
title={currentFolderName}
className={cn( className={cn(
"min-w-0 bg-transparent", "min-w-0 bg-transparent",
!editable && "cursor-default opacity-60 hover:bg-transparent" !editable && "cursor-default opacity-60 hover:bg-transparent"
@@ -158,12 +151,7 @@ const FolderPicker = memo(function FolderPicker({
) )
if (!editable) { if (!editable) {
return ( return trigger
<Tooltip>
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
<TooltipContent side="bottom">{currentFolderName}</TooltipContent>
</Tooltip>
)
} }
return ( return (

View File

@@ -24,12 +24,6 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { useIsMobile } from "@/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile"
import { import {
loadShowCompleted, loadShowCompleted,
@@ -81,80 +75,72 @@ export function Sidebar() {
return ( return (
<aside className="flex h-full min-h-0 flex-col overflow-hidden bg-sidebar text-sidebar-foreground select-none"> <aside className="flex h-full min-h-0 flex-col overflow-hidden bg-sidebar text-sidebar-foreground select-none">
<TooltipProvider> <div className="flex h-10 shrink-0 items-center justify-between gap-2 border-b border-border pl-4 pr-2">
<div className="flex h-10 shrink-0 items-center justify-between gap-2 border-b border-border pl-4 pr-2"> <div className="flex min-w-0 items-baseline gap-[0.375rem]">
<div className="flex min-w-0 items-baseline gap-[0.375rem]"> <h2 className="truncate text-[0.875rem] font-bold tracking-[-0.00625rem] text-sidebar-foreground">
<h2 className="truncate text-[0.875rem] font-bold tracking-[-0.00625rem] text-sidebar-foreground"> {t("title")}
{t("title")} </h2>
</h2>
</div>
<div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={() => listRef.current?.scrollToActive()}
title={t("locateActiveConversation")}
>
<Crosshair className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={handleToggleExpandAll}
title={
allExpanded ? t("collapseAllGroups") : t("expandAllGroups")
}
>
{allExpanded ? (
<ChevronsDownUp className="h-3.5 w-3.5" />
) : (
<ChevronsUpDown className="h-3.5 w-3.5" />
)}
</Button>
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
>
<EllipsisVertical className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">
{t("moreOptions")}
</TooltipContent>
</Tooltip>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
checked={showCompleted}
onCheckedChange={handleSetShowCompleted}
>
{t("showCompleted")}
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>{t("sortBy")}</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={sortMode}
onValueChange={handleSetSortMode}
>
<DropdownMenuRadioItem value="created">
{t("sortByCreatedAt")}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="updated">
{t("sortByUpdatedAt")}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</TooltipProvider> <div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={() => listRef.current?.scrollToActive()}
title={t("locateActiveConversation")}
>
<Crosshair className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={handleToggleExpandAll}
title={
allExpanded ? t("collapseAllGroups") : t("expandAllGroups")
}
>
{allExpanded ? (
<ChevronsDownUp className="h-3.5 w-3.5" />
) : (
<ChevronsUpDown className="h-3.5 w-3.5" />
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
title={t("moreOptions")}
>
<EllipsisVertical className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
checked={showCompleted}
onCheckedChange={handleSetShowCompleted}
>
{t("showCompleted")}
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>{t("sortBy")}</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={sortMode}
onValueChange={handleSetSortMode}
>
<DropdownMenuRadioItem value="created">
{t("sortByCreatedAt")}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="updated">
{t("sortByUpdatedAt")}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* On mobile, clicking a conversation card auto-closes the Sheet */} {/* On mobile, clicking a conversation card auto-closes the Sheet */}
<div <div