| ... | ... | @@ -298,133 +298,139 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 298 | 298 | }, [selectedTurn?.turnId, selectedTurnId]); |
| 299 | 299 | |
| 300 | 300 | const shouldUseDragRegion = isElectron && mode !== "sheet"; |
| 301 | | |
| 302 | | return ( |
| 303 | | <div |
| 304 | | className={cn( |
| 305 | | "flex h-full min-w-0 flex-col bg-card", |
| 306 | | mode === "inline" |
| 307 | | ? "w-[42vw] min-w-[360px] max-w-[560px] shrink-0 border-l border-border" |
| 308 | | : "w-full", |
| 309 | | )} |
| 310 | | > |
| 311 | | <div className="border-b border-border"> |
| 312 | | <div |
| 301 | const headerRow = ( |
| 302 | <> |
| 303 | <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]"> |
| 304 | {canScrollTurnStripLeft && ( |
| 305 | <div className="pointer-events-none absolute inset-y-0 left-8 z-10 w-7 bg-linear-to-r from-card to-transparent" /> |
| 306 | )} |
| 307 | {canScrollTurnStripRight && ( |
| 308 | <div className="pointer-events-none absolute inset-y-0 right-8 z-10 w-7 bg-linear-to-l from-card to-transparent" /> |
| 309 | )} |
| 310 | <button |
| 311 | type="button" |
| 313 | 312 | className={cn( |
| 314 | | "flex h-12 items-center justify-between gap-2 px-4", |
| 315 | | shouldUseDragRegion ? "drag-region" : undefined, |
| 313 | "absolute left-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors", |
| 314 | canScrollTurnStripLeft |
| 315 | ? "border-border/70 hover:border-border hover:text-foreground" |
| 316 | : "cursor-not-allowed border-border/40 text-muted-foreground/40", |
| 316 | 317 | )} |
| 318 | onClick={() => scrollTurnStripBy(-180)} |
| 319 | disabled={!canScrollTurnStripLeft} |
| 320 | aria-label="Scroll turn list left" |
| 317 | 321 | > |
| 318 | | <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]"> |
| 319 | | {canScrollTurnStripLeft && ( |
| 320 | | <div className="pointer-events-none absolute inset-y-0 left-8 z-10 w-7 bg-linear-to-r from-card to-transparent" /> |
| 321 | | )} |
| 322 | | {canScrollTurnStripRight && ( |
| 323 | | <div className="pointer-events-none absolute inset-y-0 right-8 z-10 w-7 bg-linear-to-l from-card to-transparent" /> |
| 322 | <ChevronLeftIcon className="size-3.5" /> |
| 323 | </button> |
| 324 | <button |
| 325 | type="button" |
| 326 | className={cn( |
| 327 | "absolute right-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors", |
| 328 | canScrollTurnStripRight |
| 329 | ? "border-border/70 hover:border-border hover:text-foreground" |
| 330 | : "cursor-not-allowed border-border/40 text-muted-foreground/40", |
| 324 | 331 | )} |
| 332 | onClick={() => scrollTurnStripBy(180)} |
| 333 | disabled={!canScrollTurnStripRight} |
| 334 | aria-label="Scroll turn list right" |
| 335 | > |
| 336 | <ChevronRightIcon className="size-3.5" /> |
| 337 | </button> |
| 338 | <div |
| 339 | ref={turnStripRef} |
| 340 | className="turn-chip-strip flex gap-1 overflow-x-auto px-8 py-0.5" |
| 341 | > |
| 325 | 342 | <button |
| 326 | 343 | type="button" |
| 327 | | className={cn( |
| 328 | | "absolute left-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors", |
| 329 | | canScrollTurnStripLeft |
| 330 | | ? "border-border/70 hover:border-border hover:text-foreground" |
| 331 | | : "cursor-not-allowed border-border/40 text-muted-foreground/40", |
| 332 | | )} |
| 333 | | onClick={() => scrollTurnStripBy(-180)} |
| 334 | | disabled={!canScrollTurnStripLeft} |
| 335 | | aria-label="Scroll turn list left" |
| 336 | | > |
| 337 | | <ChevronLeftIcon className="size-3.5" /> |
| 338 | | </button> |
| 339 | | <button |
| 340 | | type="button" |
| 341 | | className={cn( |
| 342 | | "absolute right-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors", |
| 343 | | canScrollTurnStripRight |
| 344 | | ? "border-border/70 hover:border-border hover:text-foreground" |
| 345 | | : "cursor-not-allowed border-border/40 text-muted-foreground/40", |
| 346 | | )} |
| 347 | | onClick={() => scrollTurnStripBy(180)} |
| 348 | | disabled={!canScrollTurnStripRight} |
| 349 | | aria-label="Scroll turn list right" |
| 344 | className="shrink-0 rounded-md" |
| 345 | onClick={selectWholeConversation} |
| 346 | data-turn-chip-selected={selectedTurnId === null} |
| 350 | 347 | > |
| 351 | | <ChevronRightIcon className="size-3.5" /> |
| 348 | <div |
| 349 | className={cn( |
| 350 | "rounded-md border px-2 py-1 text-left transition-colors", |
| 351 | selectedTurnId === null |
| 352 | ? "border-border bg-accent text-accent-foreground" |
| 353 | : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80", |
| 354 | )} |
| 355 | > |
| 356 | <div className="text-[10px] leading-tight font-medium">All turns</div> |
| 357 | </div> |
| 352 | 358 | </button> |
| 353 | | <div |
| 354 | | ref={turnStripRef} |
| 355 | | className="turn-chip-strip flex gap-1 overflow-x-auto px-8 py-0.5" |
| 356 | | > |
| 359 | {orderedTurnDiffSummaries.map((summary) => ( |
| 357 | 360 | <button |
| 361 | key={summary.turnId} |
| 358 | 362 | type="button" |
| 359 | 363 | className="shrink-0 rounded-md" |
| 360 | | onClick={selectWholeConversation} |
| 361 | | data-turn-chip-selected={selectedTurnId === null} |
| 364 | onClick={() => selectTurn(summary.turnId)} |
| 365 | title={summary.turnId} |
| 366 | data-turn-chip-selected={summary.turnId === selectedTurn?.turnId} |
| 362 | 367 | > |
| 363 | 368 | <div |
| 364 | 369 | className={cn( |
| 365 | 370 | "rounded-md border px-2 py-1 text-left transition-colors", |
| 366 | | selectedTurnId === null |
| 371 | summary.turnId === selectedTurn?.turnId |
| 367 | 372 | ? "border-border bg-accent text-accent-foreground" |
| 368 | 373 | : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80", |
| 369 | 374 | )} |
| 370 | 375 | > |
| 371 | | <div className="text-[10px] leading-tight font-medium">All turns</div> |
| 376 | <div className="flex items-center gap-1"> |
| 377 | <span className="text-[10px] leading-tight font-medium"> |
| 378 | Turn{" "} |
| 379 | {summary.checkpointTurnCount ?? |
| 380 | inferredCheckpointTurnCountByTurnId[summary.turnId] ?? |
| 381 | "?"} |
| 382 | </span> |
| 383 | <span className="text-[9px] leading-tight opacity-70"> |
| 384 | {formatTurnChipTimestamp(summary.completedAt)} |
| 385 | </span> |
| 386 | </div> |
| 372 | 387 | </div> |
| 373 | 388 | </button> |
| 374 | | {orderedTurnDiffSummaries.map((summary) => ( |
| 375 | | <button |
| 376 | | key={summary.turnId} |
| 377 | | type="button" |
| 378 | | className="shrink-0 rounded-md" |
| 379 | | onClick={() => selectTurn(summary.turnId)} |
| 380 | | title={summary.turnId} |
| 381 | | data-turn-chip-selected={summary.turnId === selectedTurn?.turnId} |
| 382 | | > |
| 383 | | <div |
| 384 | | className={cn( |
| 385 | | "rounded-md border px-2 py-1 text-left transition-colors", |
| 386 | | summary.turnId === selectedTurn?.turnId |
| 387 | | ? "border-border bg-accent text-accent-foreground" |
| 388 | | : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80", |
| 389 | | )} |
| 390 | | > |
| 391 | | <div className="flex items-center gap-1"> |
| 392 | | <span className="text-[10px] leading-tight font-medium"> |
| 393 | | Turn{" "} |
| 394 | | {summary.checkpointTurnCount ?? |
| 395 | | inferredCheckpointTurnCountByTurnId[summary.turnId] ?? |
| 396 | | "?"} |
| 397 | | </span> |
| 398 | | <span className="text-[9px] leading-tight opacity-70"> |
| 399 | | {formatTurnChipTimestamp(summary.completedAt)} |
| 400 | | </span> |
| 401 | | </div> |
| 402 | | </div> |
| 403 | | </button> |
| 404 | | ))} |
| 405 | | </div> |
| 406 | | </div> |
| 407 | | <ToggleGroup |
| 408 | | className="shrink-0 [-webkit-app-region:no-drag]" |
| 409 | | variant="outline" |
| 410 | | size="xs" |
| 411 | | value={[diffRenderMode]} |
| 412 | | onValueChange={(value) => { |
| 413 | | const next = value[0]; |
| 414 | | if (next === "stacked" || next === "split") { |
| 415 | | setDiffRenderMode(next); |
| 416 | | } |
| 417 | | }} |
| 418 | | > |
| 419 | | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 420 | | <Rows3Icon className="size-3" /> |
| 421 | | </Toggle> |
| 422 | | <Toggle aria-label="Split diff view" value="split"> |
| 423 | | <Columns2Icon className="size-3" /> |
| 424 | | </Toggle> |
| 425 | | </ToggleGroup> |
| 389 | ))} |
| 426 | 390 | </div> |
| 427 | 391 | </div> |
| 392 | <ToggleGroup |
| 393 | className="shrink-0 [-webkit-app-region:no-drag]" |
| 394 | variant="outline" |
| 395 | size="xs" |
| 396 | value={[diffRenderMode]} |
| 397 | onValueChange={(value) => { |
| 398 | const next = value[0]; |
| 399 | if (next === "stacked" || next === "split") { |
| 400 | setDiffRenderMode(next); |
| 401 | } |
| 402 | }} |
| 403 | > |
| 404 | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 405 | <Rows3Icon className="size-3" /> |
| 406 | </Toggle> |
| 407 | <Toggle aria-label="Split diff view" value="split"> |
| 408 | <Columns2Icon className="size-3" /> |
| 409 | </Toggle> |
| 410 | </ToggleGroup> |
| 411 | </> |
| 412 | ); |
| 413 | const headerRowClassName = cn( |
| 414 | "flex items-center justify-between gap-2 px-4", |
| 415 | shouldUseDragRegion ? "drag-region h-[52px] border-b border-border" : "h-12", |
| 416 | ); |
| 417 | |
| 418 | return ( |
| 419 | <div |
| 420 | className={cn( |
| 421 | "flex h-full min-w-0 flex-col bg-card", |
| 422 | mode === "inline" |
| 423 | ? "w-[42vw] min-w-[360px] max-w-[560px] shrink-0 border-l border-border" |
| 424 | : "w-full", |
| 425 | )} |
| 426 | > |
| 427 | {shouldUseDragRegion ? ( |
| 428 | <div className={headerRowClassName}>{headerRow}</div> |
| 429 | ) : ( |
| 430 | <div className="border-b border-border"> |
| 431 | <div className={headerRowClassName}>{headerRow}</div> |
| 432 | </div> |
| 433 | )} |
| 428 | 434 | |
| 429 | 435 | {!activeThread ? ( |
| 430 | 436 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> |