Commit c17c003
authored
feat(input-format): upload files in file fields via the file uploader (#5297)
* feat(input-format): upload files in file fields via the file uploader
The file field in the start-block input format only offered a raw JSON
editor expecting hand-written base64 objects, which users routinely
filled with junk (local paths, raw text, leftover placeholders) and
which never actually fed a run.
Reuse the existing FileUpload component for file-typed fields:
- Add an opt-in controlled mode (value/onValueChange) to FileUpload so it
can be embedded where the value lives outside a subblock; store-bound
consumers (stt/vision/agent) are unchanged.
- Render the uploader for file fields, with a toggle to fall back to the
raw JSON editor for power users / legacy values.
- Detect file fields by normalized type (file[]/files/file/image) so
copilot/API-authored variants render correctly too.
- Wire editor-attached files (already uploaded, run-ready) into manual
runs via the executor's files channel; chat/API runs still override.
Backwards compatible: the inputFormat array shape is unchanged (file
values are stored as a JSON string of run-ready file objects); legacy
free-form values open in JSON mode so nothing is lost.
* refactor(input-format): extract file adapters, share run-input builder, harden types
Follow-up cleanup from /simplify + /cleanup review:
- Extract pure file adapters into input-format-files.ts (filesToControlValue,
controlValueToFiles, serializeInputFormatFiles, defaultFileFieldMode) so they
are unit-tested without a DOM; add tests.
- Derive InputFormatFile from the canonical executor UserFile so the editor and
runtime file shapes can't drift.
- Consolidate the two manual run-input builders into one buildInputFormatInput
helper so manual run and run-from-block handle files identically.
- Narrow file-field detection to the canonical file[] (matches the field-type
dropdown and the existing execution/webhook file paths) so no pre-existing
non-file[] field changes behavior.
- defaultFileFieldMode parses once; the file value is only parsed in upload mode.
- Add lib/component unit tests (45 passing).
* fix(input-format): require run-ready file shape, don't clobber named files input
Addresses review (Greptile P1s):
- parseInputFormatFiles now requires the full run-ready shape (id/name/url +
finite size + type), so a partial file can't open in uploader mode or reach
workflowInput.files only to be rejected by normalizeStartFile (which would
silently drop every file).
- buildInputFormatInput no longer overwrites a user field literally named
'files' with the upload channel; that reserved-name collision is left for the
executor to surface.
* fix(input-format): require file key, keep mixed/partial values in JSON mode
Addresses review round 2 (Greptile P1 + Cursor):
- parseInputFormatFiles now requires a non-empty key (the uploader always sets
one); an external/signed URL with no recoverable key no longer opens uploader
mode or reaches workflowInput.files only to be rejected.
- defaultFileFieldMode uses the uploader only when EVERY entry is run-ready;
a mixed array with any legacy/partial entry stays in JSON mode so the uploader
can't drop the entries it can't represent on the next save.
* fix(input-format): recover internal-url keys, gate uploader to lossless values
Addresses review round 3 (Greptile P1 + Cursor):
- parseInputFormatFiles accepts a key-less file when its url is an internal
/api/files/serve/... url (the executor recovers the key from it, same as
normalizeStartFile); only a missing key with a non-internal url is rejected.
- The file field offers the uploader only when it can represent the stored
value losslessly (empty or all run-ready); for mixed/legacy values it forces
JSON mode and hides the toggle, so a sticky 'upload' preference can no longer
drop entries the uploader cannot show on save.
* fix(input-format): require non-empty id/name/url/type in parseInputFormatFiles
Addresses review round 4 (Greptile): normalizeStartFile rejects falsy
id/name/url/type and file normalization is all-or-nothing, so an empty-string
field (e.g. hand-edited value with an empty id + internal url) would open in
uploader mode and drop every file from the run. Require non-empty strings to
match the executor.
* fix(input-format): validate recovered internal-url keys via parseInternalFileUrl
Addresses review round 5 (Greptile): isInternalFileUrl only checks the URL
prefix, but the executor parses+decodes the key. A malformed internal URL (no
extractable key) passed the prefix check, got collected into workflowInput.files,
then normalizeStartFile rejected it and dropped the whole files array. Now mirror
the executor exactly — attempt parseInternalFileUrl and require a real key.
Tests use a realistic recoverable internal URL.
* fix(input-format): uploaded files always own the files channel
Addresses review round 6 (Greptile): files is NOT in the executor's reserved
input-format names (EXECUTION_CONTROL_OUTPUT_FIELD_NAMES), so the previous guard
silently dropped uploaded file[] values whenever a plain field named files
coexisted. files is the start block's canonical file channel (the chat trigger
names its own file field files), so uploaded files now always populate it and
take precedence — dropping real attachments is the worse outcome, and making
files a reserved-name error isn't viable since it's the legitimate file-field
name.
* chore(input-format): tighten parseInputFormatFiles comment1 parent 32b6a42 commit c17c003
7 files changed
Lines changed: 574 additions & 67 deletions
File tree
- apps/sim
- app/workspace/[workspaceId]/w/[workflowId]
- components/panel/components/editor/components/sub-block/components
- file-upload
- starter
- hooks
- lib/workflows
Lines changed: 32 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
42 | 50 | | |
43 | 51 | | |
44 | | - | |
| 52 | + | |
45 | 53 | | |
46 | 54 | | |
47 | 55 | | |
| |||
165 | 173 | | |
166 | 174 | | |
167 | 175 | | |
| 176 | + | |
| 177 | + | |
168 | 178 | | |
169 | 179 | | |
170 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
171 | 195 | | |
172 | 196 | | |
173 | 197 | | |
| |||
191 | 215 | | |
192 | 216 | | |
193 | 217 | | |
194 | | - | |
| 218 | + | |
195 | 219 | | |
196 | 220 | | |
197 | 221 | | |
| |||
413 | 437 | | |
414 | 438 | | |
415 | 439 | | |
416 | | - | |
417 | | - | |
| 440 | + | |
418 | 441 | | |
419 | | - | |
420 | | - | |
| 442 | + | |
421 | 443 | | |
422 | 444 | | |
423 | 445 | | |
| |||
459 | 481 | | |
460 | 482 | | |
461 | 483 | | |
462 | | - | |
| 484 | + | |
463 | 485 | | |
464 | | - | |
| 486 | + | |
465 | 487 | | |
466 | 488 | | |
467 | | - | |
468 | 489 | | |
469 | 490 | | |
470 | 491 | | |
| |||
501 | 522 | | |
502 | 523 | | |
503 | 524 | | |
504 | | - | |
| 525 | + | |
505 | 526 | | |
506 | | - | |
| 527 | + | |
507 | 528 | | |
508 | | - | |
509 | | - | |
510 | 529 | | |
511 | 530 | | |
512 | 531 | | |
| |||
Lines changed: 110 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
Lines changed: 75 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
0 commit comments