mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 00:00:46 +00:00
00586e1fbf
Conflicts: WorkspaceItemDrillPicker, RawAppEditorHeader, copilot/global/core. Main rewrote the global chat draft layer to live entirely on `UserDraft` (see #9291 — `globalDraftStore` deleted, replaced by `userDraftAdapter` which surfaces drafts as `WorkspaceItem`s for the chat). The session runtime and the three session editor views were the only remaining consumers of `globalDraftStore`, so this merge folds them onto the same single-source-of-truth. Session ↔ chat plumbing (the user's stated requirement): - `FlowEditorView`, `ScriptEditorView`, `RawAppEditorView` register themselves as the live editor for `(workspace, itemKind)` via `UserDraft.setLiveEditorDraft({...})` on mount and clear on unmount — same registration the `/scripts/edit`, `/flows/edit`, `/apps_raw/edit` route pages now do. The session's chat sees the open path through the `isLiveDraft` hint and can target it with `discord_local_draft` or `read_workspace_item`. - Bidirectional editor ↔ chat sync now goes through `UserDraft.get/save` directly. The session's `workspace_id` (a fork) is passed as `opts.workspace` so reads/writes target the fork's UserDraft scope — the session's chat operates in the same scope, so they share content without an extra bridge. - The slim `FlowDraftValue`/`AppDraftValue` shapes the old store kept are gone; `UserDraft<Flow>` and `UserDraft<RawAppDraft>` store the full editor types (matching how the chat tools write them in `core.ts`). `flowDraftCodec` deleted; `appDraftCodec` renamed `applyDraftValueToRawApp` → `applyDraftToRuntimeRawApp` to reflect that the draft type *is* the editor shape now. - `WorkspaceItemDrillPicker.aiDraftsForKind` reads via `listGlobalDrafts(workspace)` (the new userDraftAdapter export) instead of `globalDraftStore.listDrafts`. The session retry-loop fixes, the not-found EditorHeader, and the drill-picker $effect→callback refactor from earlier on this branch all ride along unchanged.
133 lines
4.4 KiB
Svelte
133 lines
4.4 KiB
Svelte
<script lang="ts">
|
|
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
|
|
import DiffDrawer from '$lib/components/DiffDrawer.svelte'
|
|
import type { WorkspaceItem } from '$lib/components/workspacePicker'
|
|
import { untrack } from 'svelte'
|
|
import type { SessionRuntime } from './sessionRuntime.svelte'
|
|
import type { NewScript } from '$lib/gen'
|
|
import { UserDraft } from '$lib/userDraft.svelte'
|
|
import SessionItemNotFound from './SessionItemNotFound.svelte'
|
|
|
|
let {
|
|
runtime,
|
|
path,
|
|
workspaceId,
|
|
onNavigate,
|
|
initialTestPanelCollapsed = false
|
|
}: {
|
|
runtime: SessionRuntime
|
|
path: string
|
|
workspaceId: string
|
|
onNavigate?: (item: WorkspaceItem) => void
|
|
initialTestPanelCollapsed?: boolean
|
|
} = $props()
|
|
|
|
let diffDrawer: DiffDrawer | undefined = $state()
|
|
|
|
$effect(() => {
|
|
if (workspaceId && path) {
|
|
untrack(() => runtime.loadScript(workspaceId, path))
|
|
}
|
|
})
|
|
|
|
async function restoreFromCurrentTarget() {
|
|
diffDrawer?.closeDrawer()
|
|
await runtime.loadScript(workspaceId, path)
|
|
}
|
|
|
|
// Mark this editor as the live editor draft for the session's workspace
|
|
// so the chat's `isLiveDraft` hint / `discard_local_draft` tool resolve
|
|
// to this path — same registration the regular /scripts/edit page does.
|
|
$effect(() => {
|
|
if (!workspaceId || !path) return
|
|
UserDraft.setLiveEditorDraft({
|
|
workspace: workspaceId,
|
|
itemKind: 'script',
|
|
storagePath: path,
|
|
effectivePath: runtime.scriptStore.val?.path ?? path
|
|
})
|
|
return () =>
|
|
UserDraft.clearLiveEditorDraft('script', { workspace: workspaceId, storagePath: path })
|
|
})
|
|
|
|
// Bidirectional sync between this preview and `UserDraft<NewScript>`.
|
|
// The same path under the same workspace is shared with the session's
|
|
// chat (read_workspace_item / write_script / edit_script) and any other
|
|
// open editor on the same workspace.
|
|
//
|
|
// One-way-reactive discipline: inbound tracks ONLY UserDraft.get
|
|
// (and unwraps `script.content` via untrack); outbound tracks ONLY
|
|
// `script.content` (and unwraps UserDraft via untrack). Without that
|
|
// asymmetry, a user keystroke would re-fire the inbound effect with
|
|
// the pre-keystroke stored value and revert the edit.
|
|
let lastInboundContent: string | undefined = $state(undefined)
|
|
|
|
// Store → editor. Re-runs on UserDraft changes (chat write, other
|
|
// session edit, …). `script.content` is read inside untrack so user
|
|
// keystrokes don't refire this effect.
|
|
$effect(() => {
|
|
if (!workspaceId || !path) return
|
|
const draft = UserDraft.get<NewScript>('script', path, { workspace: workspaceId })
|
|
if (!draft || typeof draft.content !== 'string') return
|
|
const incoming = draft.content
|
|
untrack(() => {
|
|
if (runtime.loadedScriptPath !== path) return
|
|
const script = runtime.scriptStore.val
|
|
if (!script) return
|
|
if (incoming === script.content) return
|
|
lastInboundContent = incoming
|
|
script.content = incoming
|
|
if (draft.language) script.language = draft.language
|
|
if (draft.summary !== undefined) script.summary = draft.summary
|
|
})
|
|
})
|
|
|
|
// Editor → store. Re-runs on `script.content` mutation (user typing
|
|
// or inbound write). UserDraft is read inside untrack so writing here
|
|
// doesn't ping-pong the inbound effect.
|
|
$effect(() => {
|
|
if (!workspaceId || !path) return
|
|
if (runtime.loadedScriptPath !== path) return
|
|
const script = runtime.scriptStore.val
|
|
if (!script) return
|
|
const content = script.content
|
|
if (content === lastInboundContent) return
|
|
untrack(() => {
|
|
const current = UserDraft.get<NewScript>('script', path, { workspace: workspaceId })
|
|
if (current && current.content === content) return
|
|
UserDraft.save<NewScript>(
|
|
'script',
|
|
path,
|
|
{ ...(current ?? script), ...script },
|
|
{
|
|
workspace: workspaceId
|
|
}
|
|
)
|
|
})
|
|
})
|
|
</script>
|
|
|
|
{#if runtime.savedScript.val}
|
|
<DiffDrawer
|
|
bind:this={diffDrawer}
|
|
restoreDeployed={restoreFromCurrentTarget}
|
|
restoreDraft={restoreFromCurrentTarget}
|
|
/>
|
|
{/if}
|
|
{#if runtime.loadingScript && !runtime.loadedScriptPath}
|
|
<div class="p-4 text-secondary text-sm">Loading script {path}…</div>
|
|
{:else if runtime.notFoundScript && !runtime.loadedScriptPath}
|
|
<SessionItemNotFound kind="script" {path} {onNavigate} />
|
|
{:else if runtime.scriptStore.val}
|
|
<ScriptBuilder
|
|
bind:script={runtime.scriptStore.val}
|
|
bind:savedScript={runtime.savedScript.val}
|
|
initialPath={path}
|
|
fullyLoaded={!runtime.loadingScript}
|
|
disableHistoryChange={true}
|
|
{diffDrawer}
|
|
{onNavigate}
|
|
{initialTestPanelCollapsed}
|
|
/>
|
|
{/if}
|