Files
windmill/frontend/src/lib/components/diff_drawer.ts
T
Ruben FiszelandClaude Opus 5.5 77c0a26515 feat: add ai review to drafts, draft diffs and session review (#12063)
* feat: add ai review to the drafts page, draft diffs and the session review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: drop an ai review when its workspace changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: follow the diff version picker in ai review and review only your own draft from the badge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: give the drafts page ai review the full width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:28:41 +01:00

92 lines
4.1 KiB
TypeScript

import type { Value } from '$lib/utils'
export type DiffVersionOption = {
/** Opaque to the drawer — a script hash, a flow version id, an app version id. */
id: string
/** Identifies the version: its number and hash. Carries the weight in the list. */
label: string
/** Who deployed it, and when — rendered under the label in secondary text, so the
* version reads first and the attribution second. */
subtitle?: string
/** The version currently deployed. */
isHead?: boolean
}
/** How many deployed versions the diff picker asks for at a time. The history endpoints
* still answer whole when nobody asks — the panels and the CLI read them that way — so
* this is the picker's own appetite, not their default. */
export const VERSION_PAGE_SIZE = 20
/** Offers an AI review of the shown change, for a diff of a draft against its deployed version. */
export type DiffAiReview = {
workspaceId: string
kind: string
path: string
/** Never deployed: the left side is a placeholder, not a version. */
added?: boolean
}
export type DiffDrawerDiff =
| {
mode: 'normal'
deployed: Value
/** Names the deployed side, e.g. `Deployed d2154d55 by bob`. The version
* identity is stripped from the displayed metadata, so without this the
* reader cannot tell what their draft is being compared against. */
deployedLabel?: string
/** Deployed versions the reader can compare against, newest first. The
* drawer only renders the picker; each editor supplies its own list and
* fetcher because scripts, flows and apps identify versions differently. */
versions?: DiffVersionOption[]
/** Loads one version's payload. Returning `undefined` leaves the current
* comparison in place rather than blanking the diff. */
loadVersion?: (id: string) => Promise<Value | undefined>
/** Fetches the next, older page of `versions`. `versions` holds one page so the
* drawer opens without waiting on a path a pipeline has deployed thousands of
* times; the reader asks for the rest. Returning nothing ends the list. */
loadMoreVersions?: () => Promise<DiffVersionOption[] | undefined>
/** Moves the draft's base to the head and keeps its content, rendered as a
* header action so the user takes the latest with the diff in front of them.
* Called with the version the drawer is showing as head (from `versions`), so
* the base adopted is the one the reader just looked at. */
onTakeLatest?: (head?: string) => void | Promise<void>
/** The version the draft forked from. The drawer offers `onTakeLatest` only
* while it differs from the head, and passes that head to it. */
draftBase?: string
/** The version the deployed payload passed here came from, used as the head when
* no `versions` list loaded. Without either, the drawer cannot tell whether the
* draft is behind and offers nothing. */
deployedHead?: string
draft?: Value | undefined
current: Value
defaultDiffType?: 'deployed' | 'draft'
button?: { text: string; onClick: () => void }
aiReview?: DiffAiReview
}
| {
mode: 'simple'
original: Value
current: Value
title: string
button?: { text: string; onClick: () => void }
aiReview?: DiffAiReview
}
export interface DiffDrawerI {
/** Pass the token from `beginOpening` to continue that opening; called without one,
* the drawer claims a fresh opening, so any reuse invalidates a fetch still in
* flight rather than being overwritten by it. */
openDrawer: (token?: number) => void
closeDrawer: () => void
/** Takes the opening's token on the same terms as `openDrawer`. */
setDiff: (diff: DiffDrawerDiff, token?: number) => void
/** Claim the drawer for one opening. Filling it takes awaited fetches, and a path
* change remounts the editor while this drawer stays, so the token lives here: an
* editor checks `ownsOpening` before every write and drops the opening it started
* when it goes away. */
beginOpening: () => number
ownsOpening: (token: number) => boolean
/** Drop that opening and, when it still owns the drawer, what it put on screen. */
abandonOpening: (token: number) => void
}