Files
windmill/frontend/src/lib/components/sessions/SessionDiffDrawer.svelte
T
0ea570570e feat(ai-sessions): CRUD markdown artifacts in sessions (#10046)
* feat: add IndexedDB persistence layer for AI-chat artifacts

* feat: add reactive store for AI-chat artifacts

* feat: add artifact chat tools and wire store lifecycle

* feat: add markdown artifact viewer with source toggle

* feat: surface session artifacts in the preview panel and chat list

* feat: tell the copilot when to use artifacts in the session prompt

* test(ai_evals): add artifact case and wire artifact helpers for session context

* fix(copilot): keep in-memory artifacts across same-session resyncs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: unify session composer edits/artifacts/jobs into a status line

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add an artifacts section to the session preview picker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: share markdown prose presets and restyle the artifact viewer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: unify session status popovers into one keyboard-navigable shell

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: reset first-block top margin in all markdown prose presets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: open the preview picker on the artifacts branch for an active artifact

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: keep artifact picker scope independent of branch hydration state

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com>
2026-07-16 11:17:20 +02:00

113 lines
4.0 KiB
Svelte

<script lang="ts">
import WorkspaceDiffDrawer from './WorkspaceDiffDrawer.svelte'
import { ArrowRight, GitFork, Pencil } from 'lucide-svelte'
import { userWorkspaces } from '$lib/stores'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { devBadgeText } from '$lib/utils/devWorkspaceLabel'
import { useSessionDeployModel } from './sessionDeployModel.svelte'
import type { DeployItem } from './sessionDeployModel'
// Session "Edits" drawer. Builds the deploy model over the session
// workspace's drafts and hands it to WorkspaceDiffDrawer, which renders the
// tree + scroll-through diff column. The parent workspace only informs the
// title identity and the compare-page link — deploys land in the session
// workspace itself.
let {
workspaceId,
parentWorkspaceId,
chatId,
keys,
onDataChanged,
onItemDeployed,
onItemDiscarded
}: {
workspaceId: string
parentWorkspaceId?: string
/** CHAT id (Session.chatId — the IndexedDB `chats` key, NOT the session id),
* threaded to the footer's compare-page link as `from_session`: the compare
* page resolves it via readChatModifiedItems to preselect this chat's edits. */
chatId?: string
keys?: Set<string>
/** Notified after a deploy/discard mutated workspace state, so the owner
* (session bar) can refresh its cached draft sources. */
onDataChanged?: () => void
/** Mask maintenance, forwarded to the deploy model (see its args' docs):
* a deploy moves the item's mask entry to its deployed path, a discard
* drops it. */
onItemDeployed?: (item: DeployItem) => void
onItemDiscarded?: (item: DeployItem) => void
} = $props()
const isFork = $derived(!!parentWorkspaceId)
const ws = $derived($userWorkspaces.find((w) => w.id === workspaceId))
const parentWs = $derived(
parentWorkspaceId ? $userWorkspaces.find((w) => w.id === parentWorkspaceId) : undefined
)
let inner: WorkspaceDiffDrawer | undefined = $state(undefined)
const model = useSessionDeployModel(() => ({
workspaceId,
mask: keys,
onDataChanged,
onItemDeployed,
onItemDiscarded
}))
// Editor URL for a row (every item lives in the session workspace).
function editUrlFor(item: DeployItem): string | undefined {
const ws = encodeURIComponent(workspaceId)
const path = item.path
if (item.deployKind === 'flow') return `/flows/edit/${path}?workspace=${ws}`
if (item.deployKind === 'script') return `/scripts/edit/${path}?workspace=${ws}`
if (item.deployKind === 'app') return `/apps/edit/${path}?workspace=${ws}`
if (item.deployKind === 'raw_app') return `/apps_raw/edit/${path}?workspace=${ws}`
return undefined
}
const title = 'Edited during session'
// Compare page = the batch/PR surface. The footer hands off THIS session's
// edits via `from_session`, so the compare page opens preselected.
const compareSessionHref = $derived(
`/forks/compare?workspace_id=${encodeURIComponent(workspaceId)}&mode=${isFork ? 'fork' : 'draft'}` +
(chatId ? `&from_session=${encodeURIComponent(chatId)}` : '')
)
export function open(focusKey?: string) {
inner?.open(focusKey)
}
</script>
<WorkspaceDiffDrawer
bind:this={inner}
{model}
{title}
{editUrlFor}
{compareSessionHref}
workspaceLabel={ws?.name ?? workspaceId}
>
{#snippet titleExtra()}
<div class="flex items-center gap-1.5 text-xs text-secondary min-w-0">
{#if isFork}
<GitFork class="w-3.5 h-3.5 shrink-0" />
<span class="font-medium truncate" title={ws?.name ?? workspaceId}>
{ws?.name ?? workspaceId}
</span>
{#if ws?.is_dev_workspace}
<Badge color="indigo" small>{devBadgeText(ws.dev_workspace_label)}</Badge>
{/if}
<ArrowRight class="w-3 h-3 shrink-0 text-tertiary" />
<span class="font-medium truncate" title={parentWs?.name ?? parentWorkspaceId}>
{parentWs?.name ?? parentWorkspaceId}
</span>
{:else}
<Pencil class="w-3.5 h-3.5 shrink-0" />
<span class="font-medium truncate" title={ws?.name ?? workspaceId}>
{ws?.name ?? workspaceId}
</span>
{/if}
</div>
{/snippet}
</WorkspaceDiffDrawer>