Files
windmill/frontend/src/lib/components/inputTransformEnv.svelte.ts
T
GuilhemandClaude Opus 5 c4c9677982 feat: attach files to a flow chat message (#11185)
* feat: attach files to a flow chat message

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

* fix: store chat uploads under windmill_uploads and withdraw refused sends cleanly

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

* fix: refuse extra files for a single-file input and keep attachments on retry

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

* fix: clean up partial upload batches, withdraw a stopped send once, free retry payloads

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

* fix: list a chat conversation only once its run starts and require files where the flow does

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

* feat: carry uploaded attachments on the pending chat user message

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

* fix: route rich-composer file paste through the attachment lanes and tighten comments

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

* fix: keep a stopped turn's files for retry and let an explicit media type win

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

* fix: refuse chat attachments sent without text and shorten comments

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

* fix: resolve the attachments input from real flow input reads, ignoring loop iteration

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

* refactor: read flow input references through one parser for the model and attachments controls

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

* fix: withdraw an attachment send stopped after its uploads answered

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

* fix: discard uploads when a send is stopped as its attachments are announced

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

* docs: keep uploadAttachments' doc comment on uploadAttachments

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

* refactor: never delete chat uploads from workspace storage

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

* test: pin that a failed upload aborts the rest of its batch

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:16:10 +02:00

57 lines
1.8 KiB
TypeScript

import { CancelError, WorkspaceService, type LargeFileStorage } from '$lib/gen'
import { resource } from 'runed'
/**
* Whether the workspace has large-file storage the upload endpoints can resolve. Every
* kind counts, not only S3: Azure Blob, Azure Workload Identity, S3 via AWS OIDC and GCS
* all go through the same object-store abstraction, so reading `s3_resource_path` alone
* calls a perfectly good workspace unconfigured.
*/
function storageConfigured(storage: LargeFileStorage | undefined): boolean {
if (!storage) return false
return (
storage.type !== undefined ||
storage.s3_resource_path !== undefined ||
storage.azure_blob_resource_path !== undefined ||
storage.gcs_resource_path !== undefined
)
}
/**
* Whether the workspace can store uploaded files; read `.current`. Assumed configured until
* this workspace's own answer lands, so "no storage" never flashes on navigation or shows
* merely because the fetch failed.
*/
export function useWorkspaceStorageConfigured(ws: () => string | undefined): {
readonly current: boolean
} {
const settings = resource(ws, async (ws, _previousWs, { onCleanup }) => {
if (!ws) return undefined
const req = WorkspaceService.getPublicSettings({ workspace: ws })
// `resource` keeps whatever lands last: cancel a superseded request so a slow
// reply for a workspace we have left cannot overwrite the current one.
onCleanup(() => req.cancel())
try {
return { ws, settings: await req }
} catch (err) {
if (!(err instanceof CancelError)) {
console.error('Failed to fetch workspace settings:', err)
}
return undefined
}
})
const configured = $derived.by(() => {
const loaded = settings.current
return loaded && loaded.ws === ws()
? storageConfigured(loaded.settings.large_file_storage)
: true
})
return {
get current() {
return configured
}
}
}