add workflow generation history persistence - #13638
Conversation
🪼 branch checks and previews
Install Gradio from this PR pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/6314f556436acaeb8139a292f5c0041b1bd4ef96/gradio-6.20.0-py3-none-any.whlInstall Gradio Python Client from this PR pip install "gradio-client @ git+https://github.com/gradio-app/gradio@6314f556436acaeb8139a292f5c0041b1bd4ef96#subdirectory=client/python"Import Gradio JS Client from this PR via CDN import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/6314f556436acaeb8139a292f5c0041b1bd4ef96/browser.js"; |
🦄 change detectedThis Pull Request includes changes to the following packages.
|
imo better to have a single path, so I would be in favor of just Buckets. @hannahblair maybe I'm misunderstanding what you are saying, but you don't need to have PRO account to create Buckets |
oh i misremembered! exclusively buckets is the way go then |
There was a problem hiding this comment.
Pull request overview
Adds generation history persistence for gr.Workflow, storing per-run records (and media) in a Hugging Face Hub bucket and surfacing that history in the workflow canvas UI for browsing/reloading past runs.
Changes:
- Backend: introduce
WorkflowHistory, addhistoryparameter toWorkflow, and wire server/client paths to list/connect/push history records (plus server-side endpoint recording viaworkflow_api). - Frontend: add History panel + connect dialog, and push a history record after successful workflow execution.
- Robustness: handle missing
inputs/outputsin workflow migration, broaden pipeline tag detection, and add an HTTP 422 compatibility fallback.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
test/test_workflow_history.py |
Adds unit tests for history record building and bucket upload/list behaviors. |
js/workflowcanvas/workflow/WorkflowHistoryPanel.svelte |
New UI panel to browse and reload saved generations from a bucket. |
js/workflowcanvas/workflow/WorkflowHistoryConnect.svelte |
New UI dialog to connect/auto-create a history bucket. |
js/workflowcanvas/workflow/WorkflowCanvas.svelte |
Integrates history UI + pushes history records on successful runs. |
js/workflowcanvas/workflow/workflow-migration.ts |
Ensures legacy shape always has inputs/outputs arrays. |
js/workflowcanvas/workflow/workflow-executor.ts |
Falls back to node.task when pipeline_tag is missing. |
gradio/workflow.py |
Adds history plumbing + server endpoints (list/connect/push) and minor related backend updates. |
gradio/workflow_history.py |
New implementation of bucket-backed history persistence (records + media). |
gradio/workflow_api.py |
Records server-side endpoint executions into history when enabled. |
gradio/routes.py |
Uses a safer 422 status constant fallback for compatibility. |
.changeset/late-poets-see.md |
Declares minor bumps for gradio and @gradio/workflowcanvas. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const genInputs: Record< | ||
| string, | ||
| { value: any; type: string; label: string } | ||
| > = {}; | ||
| for (const ref of wfToRun.references) { | ||
| const node = legacyView.nodes.find((n) => n.id === ref.id); | ||
| if (!node) continue; | ||
| const outPort = node.outputs[0]; | ||
| if (!outPort) continue; | ||
| const captured = capturedOutputs[ref.id]; | ||
| const value = captured | ||
| ? ((captured.find((o) => o.portId === outPort.id) ?? captured[0]) | ||
| ?.value ?? null) | ||
| : (node.data?.[outPort.id] ?? null); | ||
| genInputs[ref.id] = { value, type: outPort.type, label: node.label }; | ||
| } |
| def list_history( | ||
| data=None, | ||
| _request: Optional[Request] = None, | ||
| _token: Optional[OAuthToken] = None, | ||
| ) -> str: | ||
| """Return recent generation records for the History panel.""" | ||
| if self._wh is None: | ||
| return json.dumps({"records": [], "repo_id": None}) | ||
| subgraph = data[0] if data else None | ||
| limit = ( | ||
| int(data[1]) if data and len(data) > 1 and data[1] is not None else 50 | ||
| ) | ||
| records = self._wh.list(limit=limit, subgraph=subgraph or None) | ||
| return json.dumps({"records": records, "repo_id": self._wh.repo_id}) |
| def push_history( | ||
| data=None, | ||
| _request: Optional[Request] = None, | ||
| _token: Optional[OAuthToken] = None, | ||
| ) -> str: | ||
| """Accept a pre-built record from a client-side run and push it to Hub. | ||
|
|
||
| data[0]: JSON string of the history record dict. | ||
| """ | ||
| if self._wh is None: | ||
| return json.dumps({"ok": False, "reason": "no_history"}) | ||
| try: | ||
| record = json.loads(data[0]) if data else {} | ||
| if not record.get("id"): | ||
| return json.dumps({"ok": False, "reason": "invalid_record"}) | ||
| self._wh.push(record) | ||
| return json.dumps({"ok": True}) | ||
| except Exception as e: | ||
| logger.debug("push_history failed: %s", e, exc_info=True) | ||
| return json.dumps({"ok": False, "reason": str(e)}) |
| bucket_id=self.repo_id, | ||
| add=[(fh.read(), path_in_repo)], | ||
| ) | ||
| return f"https://huggingface.co/buckets/{self.repo_id}/{path_in_repo}" |
Description
Please include a concise summary, in clear English, of the changes in this pull request. If it closes an issue, please mention it here.
Closes: #(issue)
AI Disclosure
We encourage the use of AI tooling in creating PRs, but the any non-trivial use of AI needs be disclosed. E.g. if you used Claude to write a first draft, you should mention that. Trivial tab-completion doesn't need to be disclosed. You should self-review all PRs, especially if they were generated with AI.
🎯 PRs Should Target Issues
Before your create a PR, please check to see if there is an existing issue for this change. If not, please create an issue before you create this PR, unless the fix is very small.
Not adhering to this guideline will result in the PR being closed.
Testing and Formatting Your Code
PRs will only be merged if tests pass on CI. We recommend at least running the backend tests locally, please set up your Gradio environment locally and run the backed tests:
bash scripts/run_backend_tests.shPlease run these bash scripts to automatically format your code:
bash scripts/format_backend.sh, and (if you made any changes to non-Python files)bash scripts/format_frontend.sh