Merge branch 'cv-main'
This commit is contained in:
@@ -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 (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user