Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {isViewable} from "@agenta/chat/assets"
import type {StagedUpload as UploadFile} from "@agenta/chat/model"
import {type MountFile} from "@agenta/entities/session"

import {type DriveFileSource, DriveFileSourceContext} from "@/oss/components/Drives/driveFileSource"
import {FilesDrawer} from "@/oss/components/Drives/FilesDrawer"
import {type SessionDriveData} from "@/oss/components/Drives/useSessionDrive"
import {type DriveFileSource, DriveFileSourceContext} from "@agenta/entity-ui/drive"
import {FilesDrawer} from "@agenta/entity-ui/drive"
import {type SessionDriveData} from "@agenta/entities/drive"

/**
* Previews the composer's attachments in the shared Files drawer — the same tree + viewer used for
Expand Down
10 changes: 5 additions & 5 deletions web/oss/src/components/Drives/chatFileRefs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import {
import {atom, useAtomValue} from "jotai"
import {atomFamily} from "jotai/utils"

import {agentMountQueryFamily} from "./agentDrive"
import {DriveFileInlineRef} from "./DriveFileCard"
import {useDriveArtifactId, useDriveSessionId} from "./driveSessionContext"
import {cleanPath} from "./driveTree"
import {AGENT_FILES_DIR} from "./useSessionDrive"
import {agentMountQueryFamily} from "@agenta/entities/drive"
import {DriveFileInlineRef} from "@agenta/entity-ui/drive"
import {useDriveArtifactId, useDriveSessionId} from "@agenta/entity-ui/drive"
import {cleanPath} from "@agenta/entities/drive"
import {AGENT_FILES_DIR} from "@agenta/entities/drive"

/** A span that could NAME a file; strip a leading `./` and require a path-ish shape: a slash, or a
* letter-led trailing extension (`.ts`, `.tar.gz`). A bare `/[./]/` matched any dotted token —
Expand Down
26 changes: 26 additions & 0 deletions web/oss/src/components/Drives/useChatScopeSessionId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {isSessionFresh} from "@agenta/chat/state"
import {useAtomValue} from "jotai"

import {useChatScopeKey} from "@/oss/components/AgentChatSlice/state/scope"
import {
activeSessionIdAtomFamily,
sessionsListAtomFamily,
} from "@/oss/components/AgentChatSlice/state/sessions"

/**
* This app's answer to "which conversation's drive is the config panel showing?" — resolved the
* same way the chat resolves it: a stale active id (closed tab) falls back to the first open tab,
* and a brand-new never-run tab resolves to none so the queries stay held off.
*
* Lives here, not in the shared drive code: open chat TABS are a desktop concept. A
* session-scoped surface passes its own id straight through instead.
*/
export const useChatScopeSessionId = (): string => {
const scope = useChatScopeKey()
const sessions = useAtomValue(sessionsListAtomFamily(scope))
const rawActiveId = useAtomValue(activeSessionIdAtomFamily(scope))
const resolved = sessions.some((s) => s.id === rawActiveId)
? rawActiveId
: (sessions[0]?.id ?? "")
return resolved && !isSessionFresh(resolved) ? resolved : ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {useAtomValue} from "jotai"
import dynamic from "next/dynamic"

import {timeAgo} from "@/oss/components/AgentChatSlice/state/sessions"
import {agentMountQueryFamily} from "@/oss/components/Drives/agentDrive"
import {AGENT_FILES_DIR, useSessionDrive} from "@/oss/components/Drives/useSessionDrive"
import {AGENT_FILES_DIR, agentMountQueryFamily, useSessionDrive} from "@agenta/entities/drive"

// The whole drive explorer, pulled in only once the drawer is actually opened.
const FilesDrawer = dynamic(
() => import("@/oss/components/Drives/FilesDrawer").then((mod) => mod.FilesDrawer),
() => import("@agenta/entity-ui/drive").then((mod) => mod.FilesDrawer),
{ssr: false},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
* fetch). Uses the same elevated dark surface as the build-mode Inspector so the right dock reads
* as one panel across modes. A file row opens Quick Look; "View all files" opens the Files drawer.
*/
import {isSessionFresh} from "@agenta/chat/state"
import {listArrowKeyDown} from "@agenta/entities/drive"
import {FILE_ITEM_VARIANTS, FILE_SPRING} from "@agenta/entities/drive"
import {relativeTime} from "@agenta/entities/drive"
import {type DroppedFile} from "@agenta/entities/drive"
import {isRecentlyChanged, useRecentChangeClock} from "@agenta/entities/drive"
import {type FileDropProps, useStageDrop} from "@agenta/entities/drive"
import {driveHasMixedOrigins, useSessionDriveSummary} from "@agenta/entities/drive"
import {isSessionFresh} from "@agenta/entities/session"
import {EnhancedButton as Button} from "@agenta/ui/components/presentational"
import {Tag} from "@agenta/ui/components/presentational"
import {SimpleTooltip as Tooltip} from "@agenta/ui/ui"
import {CircleNotch, FolderOpen, FolderSimple, Sidebar} from "@phosphor-icons/react"
import {Button, Tag, Tooltip, Typography} from "antd"
import {useAtom, useSetAtom} from "jotai"
import {atomWithStorage} from "jotai/utils"
import {AnimatePresence, MotionConfig, motion} from "motion/react"
Expand All @@ -20,17 +29,20 @@ import {
SKELETON_ROW_COUNT,
} from "./DriveFileRow"
import {DriveItemContextMenu, useCopyDrivePath, useDriveItemDownload} from "./DriveItemContextMenu"
import {listArrowKeyDown} from "./driveKeyboard"
import {FILE_ITEM_VARIANTS, FILE_SPRING} from "./driveMotion"
import {useDriveArtifactId} from "./driveSessionContext"
import {relativeTime} from "./driveTree"
import {type DroppedFile} from "./dropEntries"
import {driveQuickLookAtomFamily} from "./quickLook"
import {isRecentlyChanged, useRecentChangeClock} from "./recentChange"
import {type FileDropProps, useStageDrop} from "./useDriveDrop"
import {driveHasMixedOrigins, useSessionDriveSummary} from "./useSessionDrive"

const {Text} = Typography
/** antd `Typography.Text` stand-in: the only prop this module used is `type="secondary"`. */
const Text = ({
type: _type,
className,
children,
...rest
}: React.HTMLAttributes<HTMLSpanElement> & {type?: "secondary"}) => (
<span className={`text-colorTextSecondary ${className ?? ""}`} {...rest}>
{children}
</span>
)

// Shared elevated surface with the Inspector (build-spec §elevation) — the right dock reads the
// same in chat (this rail) and build (Inspector). Theme-aware tokens, same as the Inspector panel.
Expand Down Expand Up @@ -112,7 +124,7 @@ export function ContextRail({
style={{width: STRIP_WIDTH, borderColor: BORDER}}
{...stageDropProps}
>
<Tooltip title="Show files" placement="left">
<Tooltip title="Show files" side="left">
<span className="flex h-7 w-7 items-center justify-center rounded text-colorTextSecondary transition-colors group-hover:text-colorText">
<Sidebar size={15} />
</span>
Expand All @@ -126,7 +138,7 @@ export function ContextRail({
? "Some files couldn’t be loaded — open to retry"
: `${drive.fileCount}${drive.fileCountCapped ? "+" : ""} file${drive.fileCount === 1 && !drive.fileCountCapped ? "" : "s"} in this conversation`
}
placement="left"
side="left"
>
<DriveWarningBadge
show={drive.partialErrored}
Expand All @@ -146,7 +158,7 @@ export function ContextRail({
</Tooltip>
) : null}
{busy ? (
<Tooltip title="Agent is running" placement="left">
<Tooltip title="Agent is running" side="left">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full rounded-full bg-colorInfo opacity-60 motion-safe:animate-ping" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-colorInfo" />
Expand Down Expand Up @@ -197,10 +209,11 @@ const ExpandedRail = ({
<div className="flex items-center gap-1.5 px-3 pt-3">
<span className="text-xs font-medium">Files</span>
{drive.fileCount > 0 ? (
<Tag bordered className="m-0 !px-1.5 !text-[12px] font-normal leading-[16px]">
{drive.fileCount}
{drive.fileCountCapped ? "+" : ""}
</Tag>
<Tag
size="small"
className="m-0 !px-1.5 !text-[12px] font-normal leading-[16px]"
label={`${drive.fileCount}${drive.fileCountCapped ? "+" : ""}`}
/>
) : null}
<div className="ml-auto flex items-center">
{/* A mount failure badges the open-drawer folder and swaps its tooltip to the retry
Expand All @@ -211,7 +224,7 @@ const ExpandedRail = ({
? "Some files couldn’t be loaded — open to retry"
: "Open the Files drawer"
}
placement="bottom"
side="bottom"
>
<Button
type="text"
Expand All @@ -224,7 +237,7 @@ const ExpandedRail = ({
aria-label="Open the files drawer"
/>
</Tooltip>
<Tooltip title="Collapse panel" placement="bottom">
<Tooltip title="Collapse panel" side="bottom">
<Button
type="text"
icon={<Sidebar size={13} />}
Expand Down Expand Up @@ -263,7 +276,7 @@ const ExpandedRail = ({
aria-busy={showSkeleton || undefined}
>
{phase === "error" ? (
<Text type="secondary" className="px-1 pb-1 !text-xs">
<Text type="secondary" className="px-1 pb-1 !text-[11px]">
Couldn&rsquo;t load files.{" "}
{drive.retry ? (
<DriveRetryButton
Expand All @@ -275,11 +288,11 @@ const ExpandedRail = ({
) : phase === "no-changes" ? (
// Files exist but none changed in THIS conversation (recents = its
// record log).
<Text type="secondary" className="px-1 pb-1 !text-xs">
<Text type="secondary" className="px-1 pb-1 !text-[11px]">
No changes yet — open “View all files” to browse.
</Text>
) : phase === "empty" ? (
<Text type="secondary" className="px-1 pb-1 !text-xs">
<Text type="secondary" className="px-1 pb-1 !text-[11px]">
No files yet.
</Text>
) : (
Expand All @@ -295,7 +308,7 @@ const ExpandedRail = ({
(_, i) => (
<motion.div
key={`__sk-${i}`}
layout
layout="position"
variants={FILE_ITEM_VARIANTS}
initial="initial"
animate="animate"
Expand Down Expand Up @@ -324,7 +337,7 @@ const ExpandedRail = ({
return (
<motion.div
key={file.path}
layout
layout="position"
variants={FILE_ITEM_VARIANTS}
initial="initial"
animate="animate"
Expand Down Expand Up @@ -407,7 +420,7 @@ const ExpandedRail = ({
</MotionConfig>
{!showSkeleton &&
(drive.reconciling || drive.isFetching) ? (
<div className="flex items-center gap-1.5 px-1 pt-0.5 text-xs text-colorTextTertiary">
<div className="flex items-center gap-1.5 px-1 pt-0.5 text-[11px] text-colorTextTertiary">
<CircleNotch size={11} className="animate-spin" />
<span>Loading more…</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {type Mount} from "@agenta/entities/session"
import {driveRootLabel} from "@agenta/entities/drive"
import {House} from "@phosphor-icons/react"

/** Breadcrumb root label. Mount slugs are the RESERVED form (`__ag__<uuid5>__cwd`) — surface
* only the human tail ("cwd"), never the uuid (spec: raw ids stay out of labels). */
export const driveRootLabel = (mount: Mount | null): string =>
mount?.slug?.split("__").filter(Boolean).pop() ?? "cwd"

/** Clickable path breadcrumb: each folder segment (and the home root) navigates via `onNavigate`
* (a folder path, "" = root). The last segment is the current file/folder (plain). Scrolls
* horizontally rather than truncating, so every part stays reachable. */
Expand Down Expand Up @@ -64,3 +59,5 @@ export const DriveBreadcrumb = ({
</div>
)
}

export {driveRootLabel}
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,38 @@
*/
import {type ReactNode, useCallback, useRef, useState} from "react"

import {useRepoInfo} from "@agenta/entities/drive"
import {looksLikeFilePath} from "@agenta/entities/drive"
import {type DriveId, type DriveScope} from "@agenta/entities/drive"
import {type DroppedFile} from "@agenta/entities/drive"
import {useDriveFilters} from "@agenta/entities/drive"
import {useDriveSelection} from "@agenta/entities/drive"
import {useDriveTreeKeyboard} from "@agenta/entities/drive"
import {useDriveTreePane} from "@agenta/entities/drive"
import {useDriveTreeReveal} from "@agenta/entities/drive"
import {useDriveTreeViewport} from "@agenta/entities/drive"
import {useDriveUploads} from "@agenta/entities/drive"
import {driveHasMixedOrigins, type SessionDriveData} from "@agenta/entities/drive"
import {useTreeGroupScroll} from "@agenta/entities/drive"
import {type MountFile} from "@agenta/entities/session"
import {projectIdAtom} from "@agenta/shared/state"
import {useAtomValue} from "jotai"

import {projectIdAtom} from "@/oss/state/project"

import {driveRootLabel} from "./DriveBreadcrumb"
import {DriveExplorerSkeleton} from "./DriveExplorerSkeleton"
import {DriveEmptyState, DriveErrorState} from "./DriveExplorerStates"
import {DriveFilePreview} from "./DriveFilePreview"
import {DriveHeader} from "./DriveHeader"
import {useCopyDrivePath, useCopyText, useDriveItemDownload} from "./DriveItemContextMenu"
import {useRepoInfo} from "./driveRepo"
import {DriveToolbar} from "./DriveToolbar"
import {DriveTreeList} from "./DriveTreeList"
import {DriveTreePane} from "./DriveTreePane"
import {looksLikeFilePath} from "./driveTreeView"
import {type DriveId, type DriveScope} from "./driveTypes"
import {type DroppedFile} from "./dropEntries"
import {FolderView} from "./FolderView"
import {useDriveDownloadAll} from "./useDriveDownloadAll"
import {useDriveFilters} from "./useDriveFilters"
import {useDriveSelection} from "./useDriveSelection"
import {useDriveTreeData} from "./useDriveTreeData"
import {useDriveTreeKeyboard} from "./useDriveTreeKeyboard"
import {useDriveTreePane} from "./useDriveTreePane"
import {useDriveTreeReveal} from "./useDriveTreeReveal"
import {useDriveTreeViewport} from "./useDriveTreeViewport"
import {useDriveUploads} from "./useDriveUploads"
import {driveHasMixedOrigins, type SessionDriveData} from "./useSessionDrive"
import {useTreeGroupScroll} from "./useTreeGroupScroll"
import {useUploadReveal} from "./useUploadReveal"

export type {DriveId, DriveScope} from "./driveTypes"
export type {DriveId, DriveScope} from "@agenta/entities/drive"

/**
* The browsing body — loading/empty/error states + the two-pane search/tree/preview. Owns its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,50 @@ const ContentPaneSkeleton = ({mode}: {mode: "grid" | "preview"}) =>
</div>
)

/** The header + toolbar bands, matching DriveHeader/DriveToolbar's own geometry (both are
* `h-auto shrink-0 … border-b … px-3 py-2`), so the chrome does not appear out of nowhere when
* the real explorer mounts. */
const ChromeSkeleton = () => (
<>
<div className="flex shrink-0 items-center gap-2 border-0 border-b border-solid border-colorBorderSecondary px-3 py-2">
<div className={`h-6 w-6 shrink-0 ${bar}`} />
<div className={`h-3.5 w-40 ${bar}`} />
<div className="ml-auto flex items-center gap-2">
<div className={`h-6 w-6 ${bar}`} />
<div className={`h-6 w-6 ${bar}`} />
</div>
</div>
<div className="flex shrink-0 items-center gap-2 border-0 border-b border-solid border-colorBorderSecondary px-3 py-2">
<div className={`h-6 w-6 shrink-0 ${bar}`} />
<div className={`h-6 w-[220px] max-w-[45%] ${bar}`} />
<div className={`ml-auto h-6 w-28 ${bar}`} />
</div>
</>
)

/**
* Drawer-body placeholder: the tree pane (unless hidden) + the content pane — mirroring the real body
* so the skeleton→content swap never shifts the layout.
*
* `withChrome` adds the header + toolbar bands. The explorer's OWN loading state leaves it off (its
* real header already renders above this), but the drawer's `next/dynamic` fallback needs it: at
* that point nothing of the explorer exists yet, so without it the drawer opens on a bare slab of
* content skeleton with no header or toolbar — which is not the shape it settles into.
*/
export const DriveExplorerSkeleton = ({
mode = "grid",
showTree = true,
withChrome = false,
}: {
mode?: "grid" | "preview"
showTree?: boolean
withChrome?: boolean
}) => (
<div className="flex min-h-0 w-full flex-1" aria-hidden>
{showTree ? <TreePaneSkeleton /> : null}
<ContentPaneSkeleton mode={mode} />
<div className="flex min-h-0 w-full flex-1 flex-col" aria-hidden>
{withChrome ? <ChromeSkeleton /> : null}
<div className="flex min-h-0 w-full flex-1">
{showTree ? <TreePaneSkeleton /> : null}
<ContentPaneSkeleton mode={mode} />
</div>
</div>
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* retry) and the empty drive. Split out so DriveExplorer's body branch reads as a three-line switch;
* the loading state is {@link DriveExplorerSkeleton}.
*/
import {type DriveScope} from "@agenta/entities/drive"
import {type SessionDriveData} from "@agenta/entities/drive"
import {Alert} from "@agenta/ui/ui"
import {Tray} from "@phosphor-icons/react"
import {Alert} from "antd"

import {DriveRetryButton} from "./DriveFileRow"
import {type DriveScope} from "./driveTypes"
import {type SessionDriveData} from "./useSessionDrive"

export function DriveErrorState({drive}: {drive: SessionDriveData}) {
return (
Expand Down
Loading
Loading