import { ChevronRight, FileText, Loader2, SquareCode } from 'lucide-react'
import {
cn,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import {
getCellCodeBlockLanguage,
getCellLabel,
getCellMonacoLanguage,
getCellSourceText,
getEntryMetadata,
type NotebookDatabaseContext,
} from './AssistantNotebookPreview.utils'
import { ExplorerQueryResults } from '@/components/interfaces/Explorer/ExplorerQuery'
import { QueryResultFooter } from '@/components/interfaces/Explorer/QueryEditor/QueryResultFooter'
import { QueryResultRenderer } from '@/components/interfaces/Explorer/QueryEditor/QueryResultRenderer'
import type { QueryResult } from '@/components/interfaces/Explorer/types'
import { DiffEditor } from '@/components/ui/DiffEditor'
import type { NotebookCellDiffEntry } from '@/data/content/notebooks/notebook-operations'
import type { AgentCell, CellWire } from '@/data/content/notebooks/notebook-schema'
export interface AssistantNotebookPreviewCellProps {
entry: NotebookCellDiffEntry
isExpanded: boolean
onExpandedChange: (isExpanded: boolean) => void
/** Create and run previews omit per-row change glyphs. */
mode: 'create' | 'update' | 'run'
result?: QueryResult
databaseContext: NotebookDatabaseContext
}
/**
* A single-character gutter marker per change type The glyph is decorative —
* `changeLabel` carries the same information into the row's accessible name and its tooltip.
*/
const CHANGE_MARKERS = {
added: { glyph: '+', changeLabel: 'Added', className: 'text-brand-600' },
removed: { glyph: '−', changeLabel: 'Removed', className: 'text-destructive' },
replaced: { glyph: '~', changeLabel: 'Replaced', className: 'text-warning-600' },
moved: { glyph: '↕', changeLabel: 'Moved', className: 'text-foreground-lighter' },
unchanged: { glyph: '', changeLabel: 'Unchanged', className: '' },
} as const
type ChangeMarker = (typeof CHANGE_MARKERS)[keyof typeof CHANGE_MARKERS]
/** The cell a row is labelled by — for a replacement, the proposed cell rather than the current one. */
function getEntryCell(entry: NotebookCellDiffEntry): CellWire | AgentCell {
return entry._tag === 'replaced' ? entry.after : entry.cell
}
/** One row of the diff card: a header line that collapses to a single row, plus its content. */
export const AssistantNotebookPreviewCell = ({
entry,
isExpanded,
onExpandedChange,
mode,
result,
databaseContext,
}: AssistantNotebookPreviewCellProps) => {
const marker = CHANGE_MARKERS[entry._tag]
const isRemoved = entry._tag === 'removed'
const cell = getEntryCell(entry)
const label = getCellLabel(cell)
const metadata = getEntryMetadata(entry, databaseContext)
return (
{mode === 'update' && }
{label}
{metadata.status !== 'hidden' ? (
{metadata.status === 'loading' ? (
Loading database…
) : (
metadata.text
)}
) : null}
{result !== undefined && cell._tag !== 'markdown_cell' && (
)}
)
}
const QueryCellResult = ({
cell,
result,
}: {
cell: Extract
result: QueryResult
}) => {
const rowCount = result.rows?.length ?? 0
const rowLimit = cell._tag === 'database_cell' ? cell.row_limit : undefined
return (
<>
>
)
}
/**
* The gutter glyph, with a tooltip naming the change type it stands for. The glyph stays
* `aria-hidden` — the row's `aria-label` already carries the same word — so the tooltip is a
* pointer affordance rather than a second announcement.
*/
const ChangeGlyph = ({ marker }: { marker: ChangeMarker }) => {
const className = cn('w-3 shrink-0 text-center font-mono text-xs leading-none', marker.className)
// An unchanged cell has no glyph, so there is nothing to explain — render the spacer alone.
if (!marker.glyph) return
return (
{marker.glyph}
{marker.changeLabel}
)
}
/** Same icons the notebook tab uses for query vs markdown cells. */
const CellTypeIcon = ({ cell }: { cell: CellWire | AgentCell }) => {
const Icon = cell._tag === 'markdown_cell' ? FileText : SquareCode
return
}
const CellBody = ({ entry }: { entry: NotebookCellDiffEntry }) =>
entry._tag === 'replaced' ? (
) : (
)
const ReplacedCellBody = ({ before, after }: { before: CellWire; after: AgentCell }) => (
)
/**
* The cell's source, always rendered as literal text via `CodeBlock` — agent-authored markdown
* must never be interpreted into real DOM nodes.
*/
const CellSource = ({ cell }: { cell: CellWire | AgentCell }) => (
)