diff --git a/.github/workflows/consolidated-tests-ci.yml b/.github/workflows/consolidated-tests-ci.yml index 0f6f89d354..f035dcd124 100644 --- a/.github/workflows/consolidated-tests-ci.yml +++ b/.github/workflows/consolidated-tests-ci.yml @@ -121,6 +121,8 @@ jobs: UNSLOTH_IS_PRESENT: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -2013,6 +2015,8 @@ jobs: UNSLOTH_IS_PRESENT: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/lint-ci.yml b/.github/workflows/lint-ci.yml index 49b7f7d9b2..00e6e357e2 100644 --- a/.github/workflows/lint-ci.yml +++ b/.github/workflows/lint-ci.yml @@ -44,6 +44,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/mlx-ci.yml b/.github/workflows/mlx-ci.yml index 4cabfd01f5..8cd95bd30a 100644 --- a/.github/workflows/mlx-ci.yml +++ b/.github/workflows/mlx-ci.yml @@ -100,6 +100,8 @@ jobs: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/notebooks-ci.yml b/.github/workflows/notebooks-ci.yml index 587f27ea6d..0881c5ef3a 100644 --- a/.github/workflows/notebooks-ci.yml +++ b/.github/workflows/notebooks-ci.yml @@ -88,6 +88,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: unsloth + persist-credentials: false - name: Checkout unslothai/notebooks @ ${{ env.NOTEBOOKS_REF }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -96,6 +97,7 @@ jobs: ref: ${{ env.NOTEBOOKS_REF }} path: notebooks fetch-depth: 0 # drift check needs git status / diff + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -196,12 +198,15 @@ jobs: exit 1 fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false with: { path: unsloth } - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: unslothai/notebooks ref: ${{ env.NOTEBOOKS_REF }} path: notebooks + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: { python-version: '3.12', cache: 'pip' } - name: Install @@ -239,12 +244,15 @@ jobs: exit 1 fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false with: { path: unsloth } - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: unslothai/notebooks ref: ${{ env.NOTEBOOKS_REF }} path: notebooks + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: { python-version: '3.12', cache: 'pip' } @@ -342,12 +350,15 @@ jobs: exit 1 fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false with: { path: unsloth } - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: unslothai/notebooks ref: ${{ env.NOTEBOOKS_REF }} path: notebooks + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: { python-version: '3.12' } diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index fbfeece614..810bb644ba 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -36,6 +36,8 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false - name: Validate release versions id: prepare @@ -343,6 +345,8 @@ jobs: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false # ── Linux dependencies ── - name: Install Linux dependencies diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index f739e852fd..235fde5253 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -127,6 +127,7 @@ jobs: # Full history so TruffleHog can diff base..head; without # this it sees only the latest commit and reports nothing. fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -722,6 +723,8 @@ jobs: files.pythonhosted.org:443 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -893,6 +896,8 @@ jobs: registry.npmjs.org:443 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -963,6 +968,8 @@ jobs: files.pythonhosted.org:443 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -998,6 +1005,8 @@ jobs: files.pythonhosted.org:443 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -1052,6 +1061,7 @@ jobs: # Need the base commit accessible for `git show # :studio/frontend/package-lock.json` below. fetch-depth: 0 + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-api-smoke.yml b/.github/workflows/studio-api-smoke.yml index 668111d1dc..29f056eca4 100644 --- a/.github/workflows/studio-api-smoke.yml +++ b/.github/workflows/studio-api-smoke.yml @@ -51,6 +51,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps run: | diff --git a/.github/workflows/studio-backend-ci.yml b/.github/workflows/studio-backend-ci.yml index 59cd3a5685..63eb70f7f1 100644 --- a/.github/workflows/studio-backend-ci.yml +++ b/.github/workflows/studio-backend-ci.yml @@ -53,6 +53,8 @@ jobs: python: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -106,6 +108,8 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/studio-frontend-ci.yml b/.github/workflows/studio-frontend-ci.yml index 5c5405c604..a93cdb8661 100644 --- a/.github/workflows/studio-frontend-ci.yml +++ b/.github/workflows/studio-frontend-ci.yml @@ -36,6 +36,8 @@ jobs: working-directory: studio/frontend steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false # FIXME: drop this step once @assistant-ui/* and assistant-stream # leave 0.x -- on 1.x, caret ranges are conventional. Until then, diff --git a/.github/workflows/studio-inference-smoke.yml b/.github/workflows/studio-inference-smoke.yml index 922d883cc9..ea14e4f5d5 100644 --- a/.github/workflows/studio-inference-smoke.yml +++ b/.github/workflows/studio-inference-smoke.yml @@ -67,6 +67,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps for llama.cpp prebuilt run: | @@ -315,6 +317,8 @@ jobs: STUDIO_PORT: '18889' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps for llama.cpp prebuilt run: | @@ -632,6 +636,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps for llama.cpp prebuilt run: | diff --git a/.github/workflows/studio-mac-api-smoke.yml b/.github/workflows/studio-mac-api-smoke.yml index 6a98776fa3..aa7a616413 100644 --- a/.github/workflows/studio-mac-api-smoke.yml +++ b/.github/workflows/studio-mac-api-smoke.yml @@ -44,6 +44,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-mac-inference-smoke.yml b/.github/workflows/studio-mac-inference-smoke.yml index 82438f0c27..817f6b2a29 100644 --- a/.github/workflows/studio-mac-inference-smoke.yml +++ b/.github/workflows/studio-mac-inference-smoke.yml @@ -67,6 +67,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -315,6 +317,8 @@ jobs: STUDIO_PORT: '18898' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -680,6 +684,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-mac-ui-smoke.yml b/.github/workflows/studio-mac-ui-smoke.yml index df3654277f..28a9fc6d1d 100644 --- a/.github/workflows/studio-mac-ui-smoke.yml +++ b/.github/workflows/studio-mac-ui-smoke.yml @@ -44,6 +44,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-mac-update-smoke.yml b/.github/workflows/studio-mac-update-smoke.yml index 2733fef1d1..dd2333251a 100644 --- a/.github/workflows/studio-mac-update-smoke.yml +++ b/.github/workflows/studio-mac-update-smoke.yml @@ -46,6 +46,8 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-tauri-smoke.yml b/.github/workflows/studio-tauri-smoke.yml index 5254ef39c5..159d5dbbe6 100644 --- a/.github/workflows/studio-tauri-smoke.yml +++ b/.github/workflows/studio-tauri-smoke.yml @@ -40,6 +40,8 @@ jobs: timeout-minutes: 25 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux native deps for Tauri / WebKit2GTK run: | diff --git a/.github/workflows/studio-ui-smoke.yml b/.github/workflows/studio-ui-smoke.yml index 82496c3665..1f3a5a8594 100644 --- a/.github/workflows/studio-ui-smoke.yml +++ b/.github/workflows/studio-ui-smoke.yml @@ -52,6 +52,8 @@ jobs: HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps run: | diff --git a/.github/workflows/studio-update-smoke.yml b/.github/workflows/studio-update-smoke.yml index 574b447a94..624001142a 100644 --- a/.github/workflows/studio-update-smoke.yml +++ b/.github/workflows/studio-update-smoke.yml @@ -40,6 +40,8 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Linux deps for llama.cpp prebuilt run: | diff --git a/.github/workflows/studio-windows-api-smoke.yml b/.github/workflows/studio-windows-api-smoke.yml index fd80377352..86a07b41e5 100644 --- a/.github/workflows/studio-windows-api-smoke.yml +++ b/.github/workflows/studio-windows-api-smoke.yml @@ -52,6 +52,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-windows-inference-smoke.yml b/.github/workflows/studio-windows-inference-smoke.yml index 13bd8e58e2..bc13ec8199 100644 --- a/.github/workflows/studio-windows-inference-smoke.yml +++ b/.github/workflows/studio-windows-inference-smoke.yml @@ -62,6 +62,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -388,6 +390,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -796,6 +800,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-windows-ui-smoke.yml b/.github/workflows/studio-windows-ui-smoke.yml index 6ee262163b..90fce0558b 100644 --- a/.github/workflows/studio-windows-ui-smoke.yml +++ b/.github/workflows/studio-windows-ui-smoke.yml @@ -57,6 +57,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/studio-windows-update-smoke.yml b/.github/workflows/studio-windows-update-smoke.yml index c16edc5aff..0303bc746d 100644 --- a/.github/workflows/studio-windows-update-smoke.yml +++ b/.github/workflows/studio-windows-update-smoke.yml @@ -58,6 +58,8 @@ jobs: PYTHONUTF8: '1' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/version-compat-ci.yml b/.github/workflows/version-compat-ci.yml index b14a759916..1ebea81066 100644 --- a/.github/workflows/version-compat-ci.yml +++ b/.github/workflows/version-compat-ci.yml @@ -58,6 +58,8 @@ jobs: timeout-minutes: 12 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -83,6 +85,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -107,6 +111,8 @@ jobs: timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -129,6 +135,8 @@ jobs: timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -151,6 +159,8 @@ jobs: timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -173,6 +183,8 @@ jobs: timeout-minutes: 12 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' @@ -200,6 +212,8 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false with: { path: unsloth } - name: Clone unsloth-zoo @ main run: | @@ -279,6 +293,8 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' diff --git a/.github/workflows/wheel-smoke.yml b/.github/workflows/wheel-smoke.yml index a2d5d650e2..464a8e324a 100644 --- a/.github/workflows/wheel-smoke.yml +++ b/.github/workflows/wheel-smoke.yml @@ -42,6 +42,8 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/studio/backend/main.py b/studio/backend/main.py index 38409d34a6..c1c9ed1d90 100644 --- a/studio/backend/main.py +++ b/studio/backend/main.py @@ -284,7 +284,7 @@ def _build_csp(script_nonce: "str | None" = None) -> str: "img-src 'self' data: blob: https://t0.gstatic.com " "https://t1.gstatic.com https://t2.gstatic.com " "https://t3.gstatic.com; " - "connect-src 'self'; " + "connect-src 'self' https://huggingface.co https://datasets-server.huggingface.co; " "style-src 'self' 'unsafe-inline'; " f"{script_src}; " "font-src 'self' data:; " diff --git a/studio/backend/models/training.py b/studio/backend/models/training.py index 6b5e95e188..31f1d575d7 100644 --- a/studio/backend/models/training.py +++ b/studio/backend/models/training.py @@ -130,20 +130,23 @@ class TrainingStartRequest(BaseModel): @field_validator("num_epochs") @classmethod def _check_num_epochs(cls, v: int) -> int: + # 0 is a sentinel meaning "use max_steps instead"; the frontend's + # steps-vs-epochs toggle sends it. if v is None: return 1 - if v < 1 or v > _MAX_EPOCHS: - raise ValueError(f"num_epochs must be in [1, {_MAX_EPOCHS}] (got {v!r})") + if v < 0 or v > _MAX_EPOCHS: + raise ValueError(f"num_epochs must be in [0, {_MAX_EPOCHS}] (got {v!r})") return v @field_validator("max_steps") @classmethod - def _check_max_steps(cls, v): + def _check_max_steps(cls, v: Optional[int]) -> Optional[int]: + # 0 is the frontend's sentinel for "use num_epochs instead". if v is None: return v - if not isinstance(v, int) or v < 1 or v > _MAX_STEPS: + if not isinstance(v, int) or v < 0 or v > _MAX_STEPS: raise ValueError( - f"max_steps must be a positive int <= {_MAX_STEPS} (got {v!r})" + f"max_steps must be a non-negative int <= {_MAX_STEPS} (got {v!r})" ) return v @@ -158,7 +161,7 @@ class TrainingStartRequest(BaseModel): @field_validator("warmup_steps") @classmethod - def _check_warmup_steps(cls, v): + def _check_warmup_steps(cls, v: Optional[int]) -> Optional[int]: if v is None: return v if not isinstance(v, int) or v < 0 or v > _MAX_STEPS: @@ -321,6 +324,16 @@ class TrainingStartRequest(BaseModel): description = "Physical GPU indices to use, for example [0, 1]. Omit or pass [] to use automatic selection. Explicit gpu_ids are unsupported when the parent CUDA_VISIBLE_DEVICES uses UUID/MIG entries.", ) + @model_validator(mode = "after") + def _check_steps_or_epochs(self) -> "TrainingStartRequest": + # num_epochs and max_steps each accept 0 as a "use the other one" + # sentinel. If both resolve to 0 there's nothing to train against. + if (self.max_steps is None or self.max_steps == 0) and self.num_epochs == 0: + raise ValueError( + "Either num_epochs or max_steps must be > 0; both cannot be 0." + ) + return self + class TrainingJobResponse(BaseModel): """Immediate response when training is initiated""" diff --git a/studio/frontend/src/components/assistant-ui/reasoning.tsx b/studio/frontend/src/components/assistant-ui/reasoning.tsx index fe913baf2a..e4401cc12e 100644 --- a/studio/frontend/src/components/assistant-ui/reasoning.tsx +++ b/studio/frontend/src/components/assistant-ui/reasoning.tsx @@ -19,10 +19,8 @@ import { useAuiState, } from "@assistant-ui/react"; import { copyToClipboard } from "@/lib/copy-to-clipboard"; -import { Idea01Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; import { type VariantProps, cva } from "class-variance-authority"; -import { ChevronDownIcon, CopyIcon, CheckIcon } from "lucide-react"; +import { CheckIcon, ChevronDownIcon, CopyIcon, LightbulbIcon } from "lucide-react"; import { type CSSProperties, type ComponentProps, @@ -128,10 +126,7 @@ function ReasoningTrigger({ )} {...props} > - + { }; const ThreadWelcome: FC<{ hideComposer?: boolean }> = ({ hideComposer }) => { + const [currentEmoji, setCurrentEmoji] = useState("large sloth drink.png"); + + useEffect(() => { + const hour = new Date().getHours(); + if (hour >= 6 && hour < 12) setCurrentEmoji("large sloth drink.png"); + else if (hour >= 12 && hour < 17) setCurrentEmoji("sloth magnify final.png"); + else if (hour >= 17 && hour < 21) setCurrentEmoji("sloth shy large.png"); + else setCurrentEmoji("unsloth-gem.png"); + }, []); + + const currentEmojiSrc = + currentEmoji === "unsloth-gem.png" + ? `/${currentEmoji}` + : `/Sloth emojis/${currentEmoji}`; + return (
Sloth mascot diff --git a/studio/frontend/src/features/training/api/train-api.ts b/studio/frontend/src/features/training/api/train-api.ts index bb18fb34aa..781dbe6139 100644 --- a/studio/frontend/src/features/training/api/train-api.ts +++ b/studio/frontend/src/features/training/api/train-api.ts @@ -17,10 +17,41 @@ function isAbortError(error: unknown): boolean { return error instanceof DOMException && error.name === "AbortError"; } +type FastApiValidationError = { + loc?: unknown[]; + msg?: string; +}; + +function formatDetail(detail: unknown): string | null { + if (typeof detail === "string" && detail) return detail; + if (!Array.isArray(detail)) return null; + const parts = detail + .map((entry) => { + if (!entry || typeof entry !== "object") return ""; + const { loc, msg } = entry as FastApiValidationError; + const path = Array.isArray(loc) + ? loc.filter((segment) => segment !== "body").join(".") + : ""; + const message = typeof msg === "string" ? msg : ""; + if (path && message) return `${path}: ${message}`; + return path || message; + }) + .filter(Boolean); + return parts.length > 0 ? parts.join("; ") : null; +} + async function readError(response: Response): Promise { try { - const payload = (await response.json()) as { detail?: string; message?: string }; - return payload.detail || payload.message || `Request failed (${response.status})`; + const payload = (await response.json()) as { + detail?: unknown; + message?: string; + }; + const formattedDetail = formatDetail(payload.detail); + if (formattedDetail) return formattedDetail; + if (typeof payload.message === "string" && payload.message) { + return payload.message; + } + return `Request failed (${response.status})`; } catch { return `Request failed (${response.status})`; }