From 00c11f3bbd03d4a77eddec9f095e8f915b23929a Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 09:29:48 +0300 Subject: [PATCH 01/10] docs(design): move 4 forward-looking design docs out of code_reviews/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Wave 2a / Wave 4 closure cycle authored four design specifications ("Phase 18 Library API", "Phase 20 GDPR Right-to-Erasure", "Faz 22 ISO 27001 / SOC 2 Type II Alignment", and the Phase 14/15 data_audit + cli sub-package split) and dropped them in ``docs/analysis/code_reviews/`` next to the closure plan and wave review artefacts. They are forward-looking design specs, not review artefacts — they describe what the implementation should look like, not what it shipped at a particular commit. The right home is ``docs/design/`` (already the canonical location for ``blackwell_optimized.md`` and ``wizard_mode.md``). This commit moves them with ``git mv`` (history preserved), strips closure-cycle provenance from the file headers + sign-off sections, and updates every non-wave / non-closure reference to point at the new path. ## Renames * ``library-api-design-202605021414.md`` → ``docs/design/library_api.md`` * ``gdpr-erasure-design-202605021414.md`` → ``docs/design/gdpr_erasure.md`` * ``iso27001-soc2-alignment-202605052315.md`` → ``docs/design/iso27001_soc2_alignment.md`` * ``split-design-data_audit-cli-202604300906.md`` → ``docs/design/data_audit_cli_split.md`` ## In-document changes For each moved file: * Headed with a one-paragraph **Scope** blockquote stating what the document covers + which implementation files / config keys / tests it tracks. Old "Document ID / Status: Draft / Author: Closure Wave 2a / Companion: closure-plan / Implements (next phase) / Base commit" provenance metadata removed; the doc is now framed as a living spec, not a closure-cycle artefact. * "Sign-off checklist (Phase N acceptance)" sections in ``library_api.md`` and ``gdpr_erasure.md`` replaced with "Implementation status" sections that list the as-shipped surface in v0.5.5 (Pydantic models, CLI subcommands, audit events, test files, doc files) — turning a one-shot wave acceptance gate into a reader-useful pointer set. * "Wave 1 round-5 carry-over" subsection title in ``library_api.md`` renamed to "CLI facade lazy migration" (closure provenance dropped; technical content unchanged). * Trailing closure footers (``*End of Phase 18 design — library-api-design-202605021414*`` etc.) removed. * Closing reference list in ``iso27001_soc2_alignment.md`` swapped from a closure-plan link to direct pointers at the QMS templates, audit-event catalog, and the two ISO/SOC 2 reference mappings. * All ``../../`` relative paths repaired to ``../`` to reflect the new ``docs/design/.md`` depth (was ``docs/analysis/code_reviews/.md``, one level deeper). ## Reference updates (16 files outside the code_reviews/ tree) * CHANGELOG.md — 18 path updates (link refs in PR-39 / PR-30 / PR-28 entries) plus 4 inline narrative updates that referenced the old bare names. * docs/guides/{library_api,library_api-tr}.md — design-source link. * docs/guides/{iso_soc2_deployer_guide,iso_soc2_deployer_guide-tr}.md — cross-reference + bottom-of-file resource list. * docs/guides/{gdpr_erasure,gdpr_erasure-tr}.md — bottom resources. * docs/qms/{statement_of_applicability,statement_of_applicability-tr}.md — header cross-reference. * docs/reference/compliance_summary.md — ISO 27001 / SOC 2 alignment bullet. * docs/reference/{iso27001_control_mapping,iso27001_control_mapping-tr}.md — header + bottom-of-file design-doc pointer. * docs/reference/{library_api_reference,library_api_reference-tr}.md — header + bottom-of-file design-doc pointer. * docs/reference/{soc2_trust_criteria_mapping,soc2_trust_criteria_mapping-tr}.md — header + bottom-of-file design-doc pointer. * docs/usermanuals/{en,tr}/reference/library-api.md — design rationale link in operator how-to. * forgelm/__init__.py — package-facade docstring pointer. * forgelm/_version.py — ``__api_version__`` docstring pointer. * forgelm/cli/subcommands/_purge.py — Phase 21 implementation header. * tests/test_library_api.py — module docstring pointer. * tests/test_gdpr_erasure.py — module docstring pointer. ## Wave / closure artefacts (intentionally untouched) 24 files under ``docs/analysis/code_reviews/`` still reference the old paths from inside their bodies (closure-plan, foundation-pr-review, master-review-opus, opusreview-pr29, pr29-master-review-A1/A2/A3/A8, pr29-master-review-consolidated, pr29-master-review-prompt, wave1-review-prompt, wave2a-round2-{design-doc,fix-summary,review-prompt, standards}, wave2b-final-{merge-review-prompt,public-api,standards}, wave4-{code-correctness,followup-review-prompt,privacy-security, review-prompt,standards}, wave4fu-{privacy-security,standards}). These are frozen-in-time review artefacts; their broken paths are part of the historical record and rewriting them would falsify the audit trail. ## Verification | Check | Result | |---------------------------------------------|--------| | ``check_anchor_resolution --strict`` | ✅ 268/268 (was 264; +4 new design files) | | ``check_bilingual_parity --strict`` | ✅ 40/40 | | ``check_cli_help_consistency --strict`` | ✅ 423/423 | | ``check_site_claims --strict`` | ✅ artefacts/templates/GPU/version | | ``ruff check`` (touched source) | ✅ all checks passed | | ``pytest tests/test_library_api.py + test_gdpr_erasure.py`` | ✅ 62/62 | Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 38 +++++++------- .../data_audit_cli_split.md} | 15 ++++-- .../gdpr_erasure.md} | 49 +++++++++--------- .../iso27001_soc2_alignment.md} | 42 +++++++++------- .../library_api.md} | 50 +++++++++---------- docs/guides/gdpr_erasure-tr.md | 2 +- docs/guides/gdpr_erasure.md | 2 +- docs/guides/iso_soc2_deployer_guide-tr.md | 4 +- docs/guides/iso_soc2_deployer_guide.md | 4 +- docs/guides/library_api-tr.md | 4 +- docs/guides/library_api.md | 4 +- docs/qms/statement_of_applicability-tr.md | 4 +- docs/qms/statement_of_applicability.md | 4 +- docs/reference/compliance_summary.md | 2 +- docs/reference/iso27001_control_mapping-tr.md | 4 +- docs/reference/iso27001_control_mapping.md | 4 +- docs/reference/library_api_reference-tr.md | 4 +- docs/reference/library_api_reference.md | 4 +- .../soc2_trust_criteria_mapping-tr.md | 4 +- docs/reference/soc2_trust_criteria_mapping.md | 4 +- docs/usermanuals/en/reference/library-api.md | 2 +- docs/usermanuals/tr/reference/library-api.md | 2 +- forgelm/__init__.py | 3 +- forgelm/_version.py | 3 +- forgelm/cli/subcommands/_purge.py | 2 +- tests/test_gdpr_erasure.py | 2 +- tests/test_library_api.py | 4 +- 27 files changed, 134 insertions(+), 132 deletions(-) rename docs/{analysis/code_reviews/split-design-data_audit-cli-202604300906.md => design/data_audit_cli_split.md} (98%) rename docs/{analysis/code_reviews/gdpr-erasure-design-202605021414.md => design/gdpr_erasure.md} (94%) rename docs/{analysis/code_reviews/iso27001-soc2-alignment-202605052315.md => design/iso27001_soc2_alignment.md} (97%) rename docs/{analysis/code_reviews/library-api-design-202605021414.md => design/library_api.md} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a95c500..b7d21be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,7 +177,7 @@ documentation pass. **Faz 22 — ISO 27001 / SOC 2 alignment design** - New design document at - [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) + [`docs/design/iso27001_soc2_alignment.md`](docs/design/iso27001_soc2_alignment.md) (~865 lines). Maps every ISO 27001:2022 Annex A control (93 controls across A.5 + A.6 + A.7 + A.8) and every SOC 2 Trust Services Criterion (Common Criteria CC1.x–CC9.x mandatory + 4 @@ -845,7 +845,7 @@ enough that the move can land alongside Phase 21's first commit. **Docs:** -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` §5.1 +- `docs/design/gdpr_erasure.md` §5.1 event catalog row for `data.erasure_requested` now lists `salt_source` (row mode only). Wave 2a Round-5 F-R5-05 introduced the field in §5.4 (per-event personal-data table) but did not propagate it to @@ -984,7 +984,7 @@ in this revision. Full delta: **Design-doc fixes** (no behavioural change to v0.5.5; tightens Phase 19 / 21 implementer guidance): -- Library API design (`library-api-design-202605021414.md`): CI trigger +- Library API design (`library_api.md`): CI trigger fixed (was `release-*` branch which release.md:259 forbids; now `pull_request` to main + `workflow_dispatch`); `[evaluation]` extra name fixed to `[eval]`; lazy-import test #9 promise corrected @@ -993,7 +993,7 @@ in this revision. Full delta: attribute access fixed to `["pairs"]` dict key access; tier table 10th test row added (`test_config_from_dict`); `__dir__()` recipe alignment; `forgelm.ingestion` correctly labelled "the module". -- GDPR erasure design (`gdpr-erasure-design-202605021414.md`): +- GDPR erasure design (`gdpr_erasure.md`): `staging_ttl_days` dual-set semantics resolved (ConfigError on conflict + tracking issue per release.md:95); §4.3 vs §10 Q5 contradiction resolved (`--check-policy` always exits 0; report-not- @@ -1059,7 +1059,7 @@ shipped a fix. Three were rejected with rationale: Fixed: -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — +- `docs/design/gdpr_erasure.md` — unescaped `|` in markdown table cell (`compliance/*.json|yaml` → `compliance/*.{json,yaml}`); outdated hard-coded line count "445" in §12 sign-off replaced with a "recheck with `wc -l`" note that won't @@ -1153,17 +1153,17 @@ Fixed: - *qodo-code-review L682 ("doctor uses non-secret env-vars")* — same rejection as the round-1 entry above; doctor mirrors what `compliance.py::AuditLogger` and `huggingface_hub` upstream read. -- *CodeRabbit "Count erasure events consistently" (gdpr-erasure-design +- *CodeRabbit "Count erasure events consistently" (gdpr_erasure design L388 file map + L454 sign-off)* — file map already says six events at L389; the sign-off line at L454 says "six new audit events (three core erasure events + three operator-warning events)"; the L449 "+ 3 new warning events" parenthetical refers to what Round-2 *added* on top of Round-1's three core events, not the document's total. - *CodeRabbit "--check-policy exit code unambiguous"* — already - resolved at gdpr-erasure-design L196 (exit-code table cross-references + resolved at gdpr_erasure design L196 (exit-code table cross-references §10 Q5) + L199 (paragraph: "**`--check-policy` always exits 0**"). - *CodeRabbit "ForgeTrainer access must stay lazy"* — the test at - library-api-design L349 already pins lazy behaviour + library_api design L349 already pins lazy behaviour (`assert "torch" not in sys.modules` after `_ = forgelm.ForgeTrainer`); the bot misread it as the opposite. - *CodeRabbit TR installation L109 "`distributed` → `deepspeed` rename"* — @@ -1200,13 +1200,13 @@ Fixed: CI/CD scripts that branch on exit code. The `_SIGINT_GUARDED_SUBCOMMANDS` constant was deleted because the new policy is uniform across every subcommand. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.4 `target_id` row-mode source claim aligned with §4.2 — removed the "or line number" fallback from the table cell (it was already rejected at the CLI per L174); the cell now explicitly cites the Phase 28 `forgelm audit --add-row-ids` follow-up that operators with id-less corpora must run first. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.1 dual-set conflict-resolution bullets — varied the leading phrase across the four bullets ("When only X is set", "When only Y is set", "In the case where both are set with identical values", @@ -1214,10 +1214,10 @@ Fixed: prose-style lint stops flagging the four-in-a-row "If" pattern. Behaviour spec (alias-forward / canonical / DeprecationWarning / ConfigError) is unchanged. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.3 mtime-distrust sentence reworded to remove the "consumer that distrusts mtime" person/object ambiguity. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.3 wording: "by mistake" → "accidentally" for concision. - `docs/usermanuals/tr/reference/cli.md`: `--output DIR` row "ya da" → "veya" (more formal Turkish for the docs register); `forgelm @@ -1297,20 +1297,20 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). claimed the symbol was a re-export for tests. (F-R5-08) - `CHANGELOG.md` — inserted blank line before the `### Added — Wave 2a / Phase 34` heading (MD022 fix). (Inline A) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.3 reconciled with §5.4: clarified that `target_id` handling depends on `target_kind` (run mode = clear, row mode = SHA-256 hash), and that the `row_id=42` examples in operator-facing prose show what the operator typed on the CLI rather than what lands in the audit chain. (Inline B) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.4 reconciled with §4.1: `compliance/*.json` IS deletable via `forgelm purge --run-id --kind artefacts`; the previous "deleting them requires deleting the whole ``" claim contradicted the purge-mode table. Whole-`` deletion is now framed as the operator's manual escalation when the entire run is being scrubbed. (Inline C) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.4 — `target_id` salt-fallback semantics tightened: the per-output-dir salt is **persistent** (written at `/.forgelm_audit_salt`, mode 0600, on first emission) @@ -1328,7 +1328,7 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). ### Added — Wave 2a — Phase 18 Library API design + Phase 20 GDPR erasure design - **Phase 18 — Library API analysis & design** — - `docs/analysis/code_reviews/library-api-design-202605021414.md` (525 lines). + `docs/design/library_api.md` (525 lines). 12 sections + 16-row task plan that pin the public Python surface for downstream consumers. Resolves the stable / experimental / internal tier split; documents the lazy-import invariant (`import forgelm` does not pull @@ -1340,7 +1340,7 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). `__getattr__` migration for `forgelm/cli/__init__.py`); specifies the `tests/test_library_api.py` integration suite Phase 19 must ship. - **Phase 20 — GDPR Article 17 erasure analysis & design** — - `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md`. 12 + `docs/design/gdpr_erasure.md`. 12 sections + 11-test plan + file map that pin the scope of Phase 21's `forgelm purge` implementation. Maps every Article 17(1) trigger to a ForgeLM action; enumerates the seven artefact kinds that may carry @@ -1590,7 +1590,7 @@ Both are design-only PRs — Phase 19 + Phase 21 implementations follow. [master-review-opus-202604300906.md](docs/analysis/code_reviews/master-review-opus-202604300906.md) (175 findings). - `data_audit/` + `cli/` package split design: - [split-design-data_audit-cli-202604300906.md](docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md) + [data_audit_cli_split.md](docs/design/data_audit_cli_split.md) (Faz 14-15 forward-looking). ### Deprecated @@ -1624,7 +1624,7 @@ Both are design-only PRs — Phase 19 + Phase 21 implementations follow. so external callers (`forgelm.ingestion`, `forgelm.wizard`) and the test suite keep working without code changes. Closes F-code-103 (Major). See - [split-design-data_audit-cli-202604300906.md](docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md) + [data_audit_cli_split.md](docs/design/data_audit_cli_split.md) §1 for the design. - **`forgelm/cli.py` → `forgelm/cli/` package (Faz 15)** — the ~2300-line monolith was split into a 24-module package (subcommands/, `_dispatch`, diff --git a/docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md b/docs/design/data_audit_cli_split.md similarity index 98% rename from docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md rename to docs/design/data_audit_cli_split.md index c76dfbd9..c7430526 100644 --- a/docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md +++ b/docs/design/data_audit_cli_split.md @@ -1,10 +1,15 @@ # Split Design — `forgelm/data_audit.py` and `forgelm/cli.py` -**Created:** 2026-04-30 -**Commit:** `6b515ed912f8f22304194c1b3f55ed07a26f519c` -**Branch:** `main` -**Companion to:** [`master-review-opus-202604300906.md`](./master-review-opus-202604300906.md) (§3 Theme γ, §5.5, §8.3) and [`closure-plan-202604300906.md`](./closure-plan-202604300906.md) -**Author:** Plan agent (read-only design pass; full file reads of both modules + every cross-import in `tests/` and `forgelm/`) +> **Scope:** Design specification for the sub-package split of two +> historically-monolithic modules: `forgelm/data_audit.py` (was 3098 +> lines) → `forgelm/data_audit/` package, and `forgelm/cli.py` (was +> 1756 lines) → `forgelm/cli/` package. Records the chosen module +> boundary, public-symbol re-export shape, and lazy-import contract +> rationale. Living spec — kept in sync with the implementation under +> `forgelm/data_audit/__init__.py` and `forgelm/cli/__init__.py`. + +**Status:** Implemented in v0.5.5 (Phase 14 + Phase 15 splits). See +`CHANGELOG.md` for the user-facing summary. --- diff --git a/docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md b/docs/design/gdpr_erasure.md similarity index 94% rename from docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md rename to docs/design/gdpr_erasure.md index 1b246a3b..3d63955e 100644 --- a/docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md +++ b/docs/design/gdpr_erasure.md @@ -1,12 +1,18 @@ -# ForgeLM GDPR Right-to-Erasure — Analysis & Design +# ForgeLM GDPR Right-to-Erasure — Design -**Document ID:** `gdpr-erasure-design-202605021414` -**Status:** Draft (Phase 20 deliverable) -**Author:** Closure Wave 2a -**Companion:** [`closure-plan-202604300906.md §8 Phase 20`](./closure-plan-202604300906.md) -**Implements (next phase):** Phase 21 — GDPR right-to-erasure implementation -**Base commit:** `0ffdfd6` (post-Wave-1 merge into `development`); Round-1 + Round-2 review fixes absorbed in the same wave (PR #28 `closure/wave2a-integration`). -**Regulatory anchor:** EU GDPR Article 17 ("right to erasure" / "right to be forgotten") + Article 5(1)(e) ("storage limitation") +> **Scope:** Design specification for ForgeLM's GDPR Article 17 +> right-to-erasure tool surface — the `forgelm purge` subcommand +> semantics, the `RetentionConfig` Pydantic schema, and the audit-chain +> integration that records each erasure / refusal event. Living spec — +> kept in sync with the implementation under +> `forgelm/cli/subcommands/_purge.py` and `forgelm/config.py` +> (`RetentionConfig`). + +**Regulatory anchor:** EU GDPR Article 17 ("right to erasure" / "right +to be forgotten") + Article 5(1)(e) ("storage limitation"). + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md` and +`docs/guides/gdpr_erasure.md` for the user-facing surface. --- @@ -342,7 +348,7 @@ What ForgeLM does **not** do: - Pushing erasure notices to downstream processors (Article 17(2)) — that is your runtime layer's responsibility, not the training toolkit's. -See [`docs/guides/gdpr_erasure.md`](../../guides/gdpr_erasure.md) for the operator how-to +See [`docs/guides/gdpr_erasure.md`](../guides/gdpr_erasure.md) for the operator how-to and the audit-event reference (Phase 21 deliverable; the file is added by the same PR that ships `forgelm purge`). ``` @@ -405,7 +411,7 @@ The closure plan §15.5 lists three open items that intersect Phase 21: |---|---| | **GH-023** — `ingestion.retention.raw_documents.ttl_days` config key cited by GDPR docs but not in `ForgeConfig` | Phase 21 adds `RetentionConfig` *as a top-level `retention:` block*, not nested under `ingestion`. The GDPR docs are updated to reference `retention.audit_log_retention_days` etc. An `ingestion.retention.*` alias is **not** added — the docs were ahead of the implementation, and the cleaner shape is to put retention under its own root key (it covers more than just ingestion artefacts). | | **GH-013** — `forgelm purge` documented but not implemented | Phase 21 ships exactly the form documented in `docs/usermanuals/en/compliance/gdpr-erasure.md` (`--row-id`, `--corpus`, `--run-id`, `--kind`, `--check-policy`). The user-manual page becomes accurate post-merge. | -| **Concurrent-approve race (Wave 1 round-5 carry-over)** | Phase 21 introduces `acquire_run_lock(run_id)` context manager (filelock pattern) for the audit-log + purge + approve coordination. Phase 22 ISO/SOC 2 scope already lists this; Phase 21 is the first place we actually need it (concurrent purge of the same row), so we ship the lock here and Phase 22 documents the broader policy. | +| **Concurrent-approve race** | Phase 21 introduces `acquire_run_lock(run_id)` context manager (filelock pattern) for the audit-log + purge + approve coordination. Phase 22 ISO/SOC 2 scope already lists this; Phase 21 is the first place we actually need it (concurrent purge of the same row), so we ship the lock here and Phase 22 documents the broader policy. | --- @@ -448,21 +454,12 @@ The closure plan §15.5 lists three open items that intersect Phase 21: --- -## 12. Sign-off checklist (Phase 20 acceptance) - -- [x] Document is ≥400 lines. (Concrete line count drifts as later review passes amend in place — recheck with `wc -l` rather than relying on a hard-coded count. Sections added through Round-1 + Round-2 review: §3.1 prior-state, §3.3 mtime caveat, §5.4 audit-PII minimisation, §4.4 commit-ordering recovery paths, scope-limitation paragraph + 3 new warning events.) -- [x] §1 maps every Article 17(1) trigger to ForgeLM scope. -- [x] §2 enumerates every artefact kind and its erasure strategy. -- [x] §3 specifies the `RetentionConfig` Pydantic schema (Phase 21 implements it verbatim). -- [x] §4 specifies the `forgelm purge` flag surface, exit codes, and atomicity guarantees. -- [x] §5 names the six new audit events (three core erasure events + three operator-warning events) + how chain integrity holds. -- [x] §6 supplies the marketing-claim replacement text. -- [x] §7 enumerates the 11 tests Phase 21 must ship (≥ closure-plan minimum 7). -- [x] §8 file map covers every change Phase 21 will make. -- [x] §9 absorbs the three relevant Wave 1 carry-overs (GH-023, GH-013, run-lock). -- [x] §10 records and resolves the six open questions. -- [x] §11 names what is deliberately out of scope. +## 12. Implementation status ---- +The design above shipped end-to-end in v0.5.5: -*End of Phase 20 design — `gdpr-erasure-design-202605021414`.* +- **`RetentionConfig`** (§3): pinned in `forgelm/config.py` as a top-level `retention:` block with five fields (`audit_log_retention_days`, `staging_ttl_days`, `ephemeral_artefact_retention_days`, `raw_documents_retention_days`, `enforce`). `EvaluationConfig.staging_ttl_days` deprecated with `DeprecationWarning` + alias-forwarding to `retention.staging_ttl_days`. +- **`forgelm purge` subcommand** (§4): pinned in `forgelm/cli/subcommands/_purge.py` with `--row-id`, `--corpus`, `--run-id`, `--kind`, `--check-policy`, atomic-rewrite + recovery semantics. +- **Audit events** (§5): six new events live in `audit_event_catalog.md` — `data.erasure_requested`, `data.erasure_completed`, `data.erasure_failed`, plus three operator-warning events. +- **Tests** (§7): `tests/test_gdpr_erasure.py` ships the regression coverage (unknown row id, multi-match refusal, partial-commit recovery, retention horizon enforcement, audit-PII redaction, etc.). +- **User-facing docs** (§6, §8): `docs/guides/gdpr_erasure{,-tr}.md` + `docs/usermanuals/{en,tr}/compliance/gdpr-erasure.md` reference the implemented surface. diff --git a/docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md b/docs/design/iso27001_soc2_alignment.md similarity index 97% rename from docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md rename to docs/design/iso27001_soc2_alignment.md index f50c1913..577b41c7 100644 --- a/docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md +++ b/docs/design/iso27001_soc2_alignment.md @@ -1,15 +1,13 @@ -# ISO 27001 / SOC 2 Type II — ForgeLM Alignment Design (Faz 22) +# ISO 27001 / SOC 2 Type II — ForgeLM Alignment Design -> Branch: `closure/wave4-integration` · Frozen: `2026-05-05 23:15` -> Closure-plan reference: [Faz 22](./closure-plan-202604300906.md#faz-22--iso-27001--soc-2-type-ii-alignment--analysis--gap-assessment-tek-pr-design-dosyası) -> Faz 22 acceptance bar: ≥800 satır design — see §13 acceptance criteria - -## Status - -**Wave 4 design pass.** Faz 22 ships a single deliverable: this document. Faz 23 -implements the gaps identified here (pip-audit + bandit CI, four new QMS docs, -deployer guide, two reference tables, README + CHANGELOG entries). Faz 26 picks -up QMS bilingual mirror obligations downstream of Faz 23. +> **Scope:** Deployer evidence map showing how ForgeLM artefacts +> (audit-trail, change-management, data-governance, encryption-at-rest, +> incident-response records) populate the ISO 27001 / SOC 2 Type II +> control surface a deployer faces during certification audit. Living +> spec — kept in sync with the implementation under +> `docs/qms/`, `docs/reference/iso27001_control_mapping.md`, +> `docs/reference/soc2_trust_criteria_mapping.md`, the pip-audit / +> bandit CI guards, and the QMS documents. **Important framing.** ForgeLM is a Python library + CLI. *Software cannot be ISO 27001 certified* — only **organisations** running an ISMS can. What @@ -20,8 +18,12 @@ requests is produced by ForgeLM as a byproduct of running the training pipeline. This document is the deployer's evidence map. The phrasing in customer-facing material is therefore **"ISO 27001 / SOC 2 -Type II alignment"** — never "certified" / "compliant". The README ISO/SOC 2 -section in Faz 23 follows this rule explicitly. +Type II alignment"** — never "certified" / "compliant". The +README ISO / SOC 2 section follows this rule explicitly. + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md`, +`docs/guides/iso_soc2_deployer_guide.md`, and the QMS / reference +documents listed under §Scope above for the user-facing surface. ## 1. Background and scope @@ -859,12 +861,16 @@ code?* TSC source-of-truth. - [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/) — SBOM format. -- [`docs/analysis/code_reviews/closure-plan-202604300906.md`](./closure-plan-202604300906.md) - — Faz 22 + Faz 23 source closure plan. -- [`docs/qms/`](../../qms/) — existing QMS templates (Wave 0). -- [`docs/reference/audit_event_catalog.md`](../../reference/audit_event_catalog.md) +- [`docs/qms/`](../qms/) — QMS templates (statement of applicability, + data management SOP, change management, incident response, key + management, retention). +- [`docs/reference/audit_event_catalog.md`](../reference/audit_event_catalog.md) — audit-event vocabulary. -- [`docs/standards/`](../../standards/) — engineering standards. +- [`docs/reference/iso27001_control_mapping.md`](../reference/iso27001_control_mapping.md) + — ISO/IEC 27001:2022 Annex A control → ForgeLM evidence map. +- [`docs/reference/soc2_trust_criteria_mapping.md`](../reference/soc2_trust_criteria_mapping.md) + — SOC 2 Type II Trust Services Criteria → ForgeLM evidence map. +- [`docs/standards/`](../standards/) — engineering standards. --- diff --git a/docs/analysis/code_reviews/library-api-design-202605021414.md b/docs/design/library_api.md similarity index 94% rename from docs/analysis/code_reviews/library-api-design-202605021414.md rename to docs/design/library_api.md index b96ba0c9..ff831164 100644 --- a/docs/analysis/code_reviews/library-api-design-202605021414.md +++ b/docs/design/library_api.md @@ -1,11 +1,14 @@ -# ForgeLM Library API — Analysis & Design +# ForgeLM Library API — Design -**Document ID:** `library-api-design-202605021414` -**Status:** Draft (Phase 18 deliverable) -**Author:** Closure Wave 2a -**Companion:** [`closure-plan-202604300906.md §7 Phase 18`](./closure-plan-202604300906.md) -**Implements (next phase):** Phase 19 — Library API Implementation -**Base commit:** `0ffdfd6` (post-Wave-1 merge into `development`); Round-1 + Round-2 review fixes absorbed in the same wave (PR #28 `closure/wave2a-integration`). +> **Scope:** Design specification for ForgeLM as a Python library (the +> `from forgelm import ...` import surface). Defines stability tiers, +> lazy-import discipline, semver scope (`__api_version__`), and the +> documentation + test strategy for the library API. Living spec — +> kept in sync with the implementation under `forgelm/__init__.py`, +> `forgelm/_version.py`, and `tests/test_library_api.py`. + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md` and +`docs/guides/library_api.md` for the user-facing surface. --- @@ -242,7 +245,7 @@ def __dir__() -> list[str]: return sorted({*globals().keys(), *_LAZY_SYMBOLS.keys()}) ``` -### 4.3 Wave 1 round-5 carry-over: `forgelm/cli/__init__.py` +### 4.3 CLI facade lazy migration: `forgelm/cli/__init__.py` The CLI facade currently has ~200 lines of eager `from .submodule import ...` calls (23 import statements pulling 50+ names; verified 2026-05-04 against `forgelm/cli/__init__.py`). That makes `import forgelm.cli` heavyweight even though most of those subcommand dispatchers are not needed for any single command. @@ -293,11 +296,11 @@ We add a regression test (`tests/test_cli_lazy_imports.py`) that: > **Status: Implementation diverged from this section.** The design doc > as drafted (Phase 18) committed to a 2-segment `__api_version__` (e.g. -> `"0.5"`). After drafting, [`docs/standards/release.md`](../../standards/release.md) +> `"0.5"`). After drafting, [`docs/standards/release.md`](../standards/release.md) > §"`__api_version__` (Python library surface)" (lines 27-39) codified > the canonical shape as **3-segment semver** (e.g. `"1.0.0"`) with > explicit MAJOR / MINOR / PATCH bump rules, and the implementation in -> [`forgelm/_version.py`](../../../forgelm/_version.py) lines 32-39 +> [`forgelm/_version.py`](../../forgelm/_version.py) lines 32-39 > follows the codified standard (`__api_version__ = "1.0.0"` at v0.5.5). > Defer to `release.md` for the public contract; the 2-segment shape > below is preserved here for historical context only. @@ -509,7 +512,7 @@ Things this design **does not** do, with the reason: | Out of scope | Why | |---|---| | Automatic `__api_version__` bumping in CI | Manual decision is the whole point — see Q4. | -| `forgelm.serve(...)` (HTTP wrapper) | Different product (Pro CLI dashboard); see [Phase 13 Pro CLI plan](../../roadmap/phase-13-pro-cli.md). | +| `forgelm.serve(...)` (HTTP wrapper) | Different product (Pro CLI dashboard); see [Phase 13 Pro CLI plan](../roadmap/phase-13-pro-cli.md). | | `mypy --strict` on the whole codebase | See Q3. | | Automatic `library_api.md` generation | See Q2. | | Async variants (`async def train(...)`) | Trainer is fundamentally synchronous (TRL holds GPU state). An async wrapper would be a different module. | @@ -517,21 +520,14 @@ Things this design **does not** do, with the reason: --- -## 12. Sign-off checklist (Phase 18 acceptance) - -- [x] Document is ≥400 lines. (Line count ≈ 470 incl. tables.) -- [x] Every stable-tier symbol from §2.1 has an explicit signature snippet **or** is referenced by a stable-tier file we already ship. *(Audited 2026-05-02: zero existing tests do `from forgelm import X` for any of the new stable names — that gap is exactly what Phase 19 task #7 closes. The §2.1 listing is the design promise; Phase 19 ships the regression coverage.)* -- [x] §3 names a CI step + sample command for type-hint enforcement. -- [x] §4 documents the existing lazy-import invariant + names the regression test that pins it. -- [x] §4.3 explicitly resolves the Wave 1 round-5 carry-over (CLI facade lazy migration). -- [x] §5 names two version strings + their semantics + the deprecation cadence. -- [x] §6 enumerates the integration test cases Phase 19 must implement. -- [x] §7 lists every documentation deliverable Phase 19 must produce. -- [x] §8 covers logging, config-from-dict, errors, and concurrency. -- [x] §9 is a 16-row task plan precise enough to execute Phase 19 from this document alone. -- [x] §10 records and resolves the five open questions. -- [x] §11 names what is deliberately out of scope. +## 12. Implementation status ---- +The design above shipped end-to-end in v0.5.5: -*End of Phase 18 design — `library-api-design-202605021414`.* +- Stable / Experimental / Internal tiers (§2): pinned in `forgelm/__init__.py::__all__` + `_LAZY_SYMBOLS`. +- Type-hint surface (§3): `py.typed` marker ships in the wheel; public functions carry annotations. +- Lazy-import discipline (§4): `forgelm/__init__.py::__getattr__` resolves on first access; `forgelm/cli/__init__.py` mirrors the pattern; regression test corpus at `tests/test_lazy_imports.py` covers `forgelm.trainer`, `model`, `data`, `benchmark`, `safety`, `inference`, `synthetic`, `judge` (no eager torch / transformers / trl / datasets). +- Two version strings (§5): `__version__` (runtime, `importlib.metadata`-derived) + `__api_version__` (semver `1.0.0`); both live in `forgelm/_version.py`. +- Integration tests (§6): `tests/test_library_api.py` (audit-dataset, verify-audit-log, train-and-chat journeys) + `tests/test_lazy_imports.py`. +- Docs (§7): `docs/reference/library_api_reference{,-tr}.md`, `docs/guides/library_api{,-tr}.md`, `docs/usermanuals/{en,tr}/reference/library-api.md`, README "Library API" section. +- Errors + concurrency (§8): `ConfigError`, `OptionalDependencyError`, `HttpSafetyError`; multiprocess-safe `AuditLogger.log_event` via `fcntl.flock` / `msvcrt.locking`. diff --git a/docs/guides/gdpr_erasure-tr.md b/docs/guides/gdpr_erasure-tr.md index 089c6343..63609bfa 100644 --- a/docs/guides/gdpr_erasure-tr.md +++ b/docs/guides/gdpr_erasure-tr.md @@ -186,4 +186,4 @@ glob); `2` = runtime hatası (mid-scan I/O failure). JSON envelope - `docs/qms/sop_data_management.md` — retention + erasure prosedürleri dahil tam data-lifecycle SOP. - `docs/usermanuals/tr/compliance/safety_compliance.md` — buraya link veren operator-facing compliance overview. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — bu implementation'ın gerçekleştirdiği Phase 20 design dokümanı. +- [`docs/design/gdpr_erasure.md`](../design/gdpr_erasure.md) — bu implementation'ın gerçekleştirdiği Phase 20 design dokümanı. diff --git a/docs/guides/gdpr_erasure.md b/docs/guides/gdpr_erasure.md index 4b496be7..2d82ef93 100644 --- a/docs/guides/gdpr_erasure.md +++ b/docs/guides/gdpr_erasure.md @@ -185,4 +185,4 @@ for the JSON envelope schema. - `docs/qms/sop_data_management.md` — the full data-lifecycle SOP including the retention + erasure procedures. - `docs/usermanuals/en/compliance/safety_compliance.md` — the operator-facing compliance overview that links here. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — the Phase 20 design document that this implementation realises. +- [`docs/design/gdpr_erasure.md`](../design/gdpr_erasure.md) — the Phase 20 design document that this implementation realises. diff --git a/docs/guides/iso_soc2_deployer_guide-tr.md b/docs/guides/iso_soc2_deployer_guide-tr.md index 3f23a3b6..6d657cbc 100644 --- a/docs/guides/iso_soc2_deployer_guide-tr.md +++ b/docs/guides/iso_soc2_deployer_guide-tr.md @@ -12,7 +12,7 @@ > ForgeLM artefaktının cevap verdiğini gösterir. > > Çapraz referans: -> [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > tam tasarım gerekçesi + 93 kontrollü kapsama haritası için. ## ForgeLM'in size out-of-the-box sağladıkları @@ -279,7 +279,7 @@ Operatörlerin ilk denetimlerinde yanlış yaptığı şeyler: ## Bkz. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi + 93 kontrol haritası. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi + 93 kontrol haritası. - [`../qms/encryption_at_rest-tr.md`](../qms/encryption_at_rest-tr.md) — substrate-side şifreleme rehberi. - [`../qms/access_control-tr.md`](../qms/access_control-tr.md) — operator identity + secrets management. - [`../qms/risk_treatment_plan-tr.md`](../qms/risk_treatment_plan-tr.md) — pre-populated risk register. diff --git a/docs/guides/iso_soc2_deployer_guide.md b/docs/guides/iso_soc2_deployer_guide.md index a1fdb80a..ffbeb029 100644 --- a/docs/guides/iso_soc2_deployer_guide.md +++ b/docs/guides/iso_soc2_deployer_guide.md @@ -12,7 +12,7 @@ > ForgeLM artefact answers it. > > Cross-reference: -> [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > for the full design rationale + 93-control coverage map. ## What ForgeLM gives you out-of-the-box @@ -280,7 +280,7 @@ Things deployers get wrong on their first audit: ## References -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale + 93-control coverage map. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale + 93-control coverage map. - [`../qms/encryption_at_rest.md`](../qms/encryption_at_rest.md) — substrate-side encryption guidance. - [`../qms/access_control.md`](../qms/access_control.md) — operator identity + secrets management. - [`../qms/risk_treatment_plan.md`](../qms/risk_treatment_plan.md) — pre-populated risk register. diff --git a/docs/guides/library_api-tr.md b/docs/guides/library_api-tr.md index 1dc5f3df..559bf829 100644 --- a/docs/guides/library_api-tr.md +++ b/docs/guides/library_api-tr.md @@ -4,7 +4,7 @@ > > **Eşlik eden referans:** [`../reference/library_api_reference-tr.md`](../reference/library_api_reference-tr.md) — her public sembol, imza ve kararlılık katmanı. > -> **Tasarım kaynağı:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Faz 18). +> **Tasarım kaynağı:** [`../design/library_api.md`](../design/library_api.md) (Faz 18). ForgeLM **eşit derecede first-class iki giriş noktası** yayınlar: shell pipeline'ları için `forgelm` console script'i (ve `python -m forgelm.cli`), programatik kullanım için burada belgelenen Python kütüphane API'si. CLI sarmalayıcıdır; kütüphane motordur. CLI'nin yaptığı her şeyi kütüphane yapabilir — exit-code eşlemesi ve yapılandırılmış logging kurulumu hariç. @@ -267,4 +267,4 @@ Library API ve CLI aynı wheel'i, aynı audit log'u ve aynı lock semantik'ini p - [`../reference/configuration-tr.md`](../reference/configuration-tr.md) — `ForgeConfig` alan-by-alan referans. - [`cicd_pipeline.md`](cicd_pipeline.md) — bu rehberin CLI karşılığı. - [`iso_soc2_deployer_guide-tr.md`](iso_soc2_deployer_guide-tr.md) — audit katı cookbook'u (kütüphane çağıranları aynı artefaktları görür). -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Faz 18 tasarım. +- [`../design/library_api.md`](../design/library_api.md) — Faz 18 tasarım. diff --git a/docs/guides/library_api.md b/docs/guides/library_api.md index 0636273d..3a800e7a 100644 --- a/docs/guides/library_api.md +++ b/docs/guides/library_api.md @@ -4,7 +4,7 @@ > > **Companion reference:** [`../reference/library_api_reference.md`](../reference/library_api_reference.md) — every public symbol, signature, and stability tier. > -> **Design source:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Phase 18). +> **Design source:** [`../design/library_api.md`](../design/library_api.md) (Phase 18). ForgeLM ships **two equally first-class entry points**: the `forgelm` console script (and `python -m forgelm.cli`) for shell pipelines, and the Python library API documented here for programmatic use. The CLI is the wrapper; the library is the engine. Anything the CLI does, the library can do — minus the exit-code mapping and the structured logging setup. @@ -266,4 +266,4 @@ The library API and the CLI share the same wheel, the same audit log, and the sa - [`../reference/configuration.md`](../reference/configuration.md) — `ForgeConfig` field-by-field reference. - [`cicd_pipeline.md`](cicd_pipeline.md) — the CLI counterpart of this guide. - [`iso_soc2_deployer_guide.md`](iso_soc2_deployer_guide.md) — audit-floor cookbook (library callers see the same artefacts). -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Phase 18 design. +- [`../design/library_api.md`](../design/library_api.md) — Phase 18 design. diff --git a/docs/qms/statement_of_applicability-tr.md b/docs/qms/statement_of_applicability-tr.md index 5e7148bc..486ceb5f 100644 --- a/docs/qms/statement_of_applicability-tr.md +++ b/docs/qms/statement_of_applicability-tr.md @@ -2,7 +2,7 @@ > Kalite Yönetim Sistemi — [YOUR ORGANIZATION] > ISO 27001:2022 Annex A — clause 6.1.3 d) tarafından gerekli -> Çapraz referans: [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> Çapraz referans: [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > tam mapping gerekçesi için. ## 1. Amaç @@ -156,7 +156,7 @@ Yukarıdaki §2.1–§2.4 (SoA matrisi) row-by-row yeniden sayım. Tema başına tally — A.5: 3 / 24 / 10 OOS; A.6: 0 / 5 / 3 OOS; A.7: 0 / 0 / 14 OOS; A.8: 8 / 19 / 7 OOS — toplam 11 `FL` + 48 `FL-helps` + 34 OOS = 93. Design doc'un §3 "Coverage tally" paragrafıyla -(`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`) +(`docs/design/iso27001_soc2_alignment.md`) çapraz kontrol; ikisi eşleşmek zorunda. ## 4. İnceleme diff --git a/docs/qms/statement_of_applicability.md b/docs/qms/statement_of_applicability.md index eef5fbfe..a6c7e22b 100644 --- a/docs/qms/statement_of_applicability.md +++ b/docs/qms/statement_of_applicability.md @@ -2,7 +2,7 @@ > Quality Management System — [YOUR ORGANIZATION] > ISO 27001:2022 Annex A — required by clause 6.1.3 d) -> Cross-reference: [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> Cross-reference: [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > for the full mapping rationale. ## 1. Purpose @@ -155,7 +155,7 @@ Row-by-row recount of §2.1–§2.4 above (the SoA matrix). Per-theme tally — A.5: 3 / 24 / 10 OOS; A.6: 0 / 5 / 3 OOS; A.7: 0 / 0 / 14 OOS; A.8: 8 / 19 / 7 OOS — sums to 11 `FL` + 48 `FL-helps` + 34 OOS = 93. Cross-check the design doc's §3 "Coverage tally" paragraph -(`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`); +(`docs/design/iso27001_soc2_alignment.md`); the two must match. ## 4. Review diff --git a/docs/reference/compliance_summary.md b/docs/reference/compliance_summary.md index b0c71ead..4e2e9aa9 100644 --- a/docs/reference/compliance_summary.md +++ b/docs/reference/compliance_summary.md @@ -122,7 +122,7 @@ What the regulator asks vs. how ForgeLM answers: ### ISO 27001 / SOC 2 Type II alignment (Wave 4) -- Design doc: [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +- Design doc: [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) (~865 lines, full 93-control coverage map). - Deployer cookbook: [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md). - Reference tables: [`iso27001_control_mapping.md`](iso27001_control_mapping.md), diff --git a/docs/reference/iso27001_control_mapping-tr.md b/docs/reference/iso27001_control_mapping-tr.md index 50c89b82..a126ae26 100644 --- a/docs/reference/iso27001_control_mapping-tr.md +++ b/docs/reference/iso27001_control_mapping-tr.md @@ -4,7 +4,7 @@ > eşlendiğini özetleyen referans tablo. Şu belgelere eşlik eder: > [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) > ve tasarım dökümanı -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). > > **Coverage tier legend:** > @@ -136,7 +136,7 @@ SoA'sının uçtan uca auditable olması için tamlık adına listelenmiştir. ## Bkz. - [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) — operatör denetim cookbook'u. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi. - [`soc2_trust_criteria_mapping-tr.md`](soc2_trust_criteria_mapping-tr.md) — SOC 2 mapping eşlikçisi. - [`supply_chain_security-tr.md`](supply_chain_security-tr.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — audit-event vocabulary. diff --git a/docs/reference/iso27001_control_mapping.md b/docs/reference/iso27001_control_mapping.md index fce0b2e3..7e43b110 100644 --- a/docs/reference/iso27001_control_mapping.md +++ b/docs/reference/iso27001_control_mapping.md @@ -4,7 +4,7 @@ > Annex A controls. Companion to > [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) > and the design doc -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). > > **Coverage tier legend:** > @@ -137,7 +137,7 @@ completeness so the deployer's SoA is auditable end-to-end. ## See also - [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) — deployer audit cookbook. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale. - [`soc2_trust_criteria_mapping.md`](soc2_trust_criteria_mapping.md) — SOC 2 mapping companion. - [`supply_chain_security.md`](supply_chain_security.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog.md`](audit_event_catalog.md) — audit-event vocabulary. diff --git a/docs/reference/library_api_reference-tr.md b/docs/reference/library_api_reference-tr.md index 8381a755..b575ff5f 100644 --- a/docs/reference/library_api_reference-tr.md +++ b/docs/reference/library_api_reference-tr.md @@ -6,7 +6,7 @@ > > **Eşlik eden rehber:** [`../guides/library_api-tr.md`](../guides/library_api-tr.md) — uçtan uca üç işlenmiş örnek. > -> **Tasarım kaynağı:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Faz 18). +> **Tasarım kaynağı:** [`../design/library_api.md`](../design/library_api.md) (Faz 18). ForgeLM, `forgelm` console script'inin yanında bir Python kütüphane API'si de yayınlar. Kütüphane yüzeyi `forgelm/__init__.py` içinde `__all__` ile beyan edilir, PEP 562 `__getattr__` üzerinden lazy-resolve edilir ve downstream `mypy --strict` tüketicilerin gerçek imzaları görmesi için `TYPE_CHECKING` altında tip ipucu ile işaretlenir. `forgelm/py.typed` PEP 561 işaretçisi olarak wheel ile birlikte yayınlanır. @@ -289,5 +289,5 @@ Cadence'i izlemeden stable bir imzaya kırıcı değişiklik yapmak release-proc - [`../guides/library_api-tr.md`](../guides/library_api-tr.md) — uçtan uca üç işlenmiş örnek. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — `AuditLogger.log_event`'in kabul ettiği tam olay kelime dağarcığı. - [`configuration-tr.md`](configuration-tr.md) — `ForgeConfig` alan referansı. -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Faz 18 tasarım + 16 satırlık Faz 19 görev planı. +- [`../design/library_api.md`](../design/library_api.md) — Faz 18 tasarım + 16 satırlık Faz 19 görev planı. - [`../standards/release.md`](../standards/release.md) — deprecation cadence ve sürüm süreci. diff --git a/docs/reference/library_api_reference.md b/docs/reference/library_api_reference.md index a4970f17..491d3f54 100644 --- a/docs/reference/library_api_reference.md +++ b/docs/reference/library_api_reference.md @@ -6,7 +6,7 @@ > > **Companion guide:** [`../guides/library_api.md`](../guides/library_api.md) — three end-to-end worked examples. > -> **Design source:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Phase 18). +> **Design source:** [`../design/library_api.md`](../design/library_api.md) (Phase 18). ForgeLM ships a Python library API alongside the `forgelm` console script. The library surface is declared in `forgelm/__init__.py` via `__all__`, lazy-resolved through PEP 562 `__getattr__`, and type-hinted under `TYPE_CHECKING` so downstream `mypy --strict` consumers see real signatures. `forgelm/py.typed` ships in the wheel as the PEP 561 marker. @@ -289,5 +289,5 @@ A breaking change to a stable signature without following the cadence is a relea - [`../guides/library_api.md`](../guides/library_api.md) — three end-to-end worked examples. - [`audit_event_catalog.md`](audit_event_catalog.md) — full event vocabulary `AuditLogger.log_event` accepts. - [`configuration.md`](configuration.md) — `ForgeConfig` field reference. -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Phase 18 design + 16-row Phase 19 task plan. +- [`../design/library_api.md`](../design/library_api.md) — Phase 18 design + 16-row Phase 19 task plan. - [`../standards/release.md`](../standards/release.md) — deprecation cadence and release process. diff --git a/docs/reference/soc2_trust_criteria_mapping-tr.md b/docs/reference/soc2_trust_criteria_mapping-tr.md index 426156e9..61d78755 100644 --- a/docs/reference/soc2_trust_criteria_mapping-tr.md +++ b/docs/reference/soc2_trust_criteria_mapping-tr.md @@ -5,7 +5,7 @@ > tablo. Şu belgelere eşlik eder: > [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) > ve tasarım dökümanı -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). ## Kategoriler @@ -123,7 +123,7 @@ Güçlü ForgeLM katkısı. ## Bkz. - [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) — operatör denetim cookbook'u. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi. - [`iso27001_control_mapping-tr.md`](iso27001_control_mapping-tr.md) — ISO 27001 mapping eşlikçisi. - [`supply_chain_security-tr.md`](supply_chain_security-tr.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — audit-event vocabulary. diff --git a/docs/reference/soc2_trust_criteria_mapping.md b/docs/reference/soc2_trust_criteria_mapping.md index d2c0d627..96232a4a 100644 --- a/docs/reference/soc2_trust_criteria_mapping.md +++ b/docs/reference/soc2_trust_criteria_mapping.md @@ -5,7 +5,7 @@ > Companion to > [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) > and the design doc -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). ## Categories @@ -124,7 +124,7 @@ Strong ForgeLM contribution. ## See also - [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) — deployer audit cookbook. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale. - [`iso27001_control_mapping.md`](iso27001_control_mapping.md) — ISO 27001 mapping companion. - [`supply_chain_security.md`](supply_chain_security.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog.md`](audit_event_catalog.md) — audit-event vocabulary. diff --git a/docs/usermanuals/en/reference/library-api.md b/docs/usermanuals/en/reference/library-api.md index f1e76324..d57538ea 100644 --- a/docs/usermanuals/en/reference/library-api.md +++ b/docs/usermanuals/en/reference/library-api.md @@ -47,7 +47,7 @@ assert "torch" not in sys.modules # pinned by tests/test_library_api.py - The deep guide, with three end-to-end pipeline patterns and common pitfalls, is at: [`docs/guides/library_api.md`](../../../guides/library_api.md) - The Phase 18 design rationale (stability tiers, type contract, deprecation cadence) is at: - [`docs/analysis/code_reviews/library-api-design-202605021414.md`](../../../analysis/code_reviews/library-api-design-202605021414.md) + [`docs/design/library_api.md`](../../../design/library_api.md) ## See also diff --git a/docs/usermanuals/tr/reference/library-api.md b/docs/usermanuals/tr/reference/library-api.md index d21df2ec..fefd0125 100644 --- a/docs/usermanuals/tr/reference/library-api.md +++ b/docs/usermanuals/tr/reference/library-api.md @@ -47,7 +47,7 @@ assert "torch" not in sys.modules # tests/test_library_api.py ile pinlenir - Uçtan uca üç pipeline deseni ve yaygın tuzaklar dahil derin rehber: [`docs/guides/library_api-tr.md`](../../../guides/library_api-tr.md) - Faz 18 tasarım gerekçesi (kararlılık katmanları, tip kontratı, deprecation cadence): - [`docs/analysis/code_reviews/library-api-design-202605021414.md`](../../../analysis/code_reviews/library-api-design-202605021414.md) + [`docs/design/library_api.md`](../../../design/library_api.md) ## Ayrıca bakınız diff --git a/forgelm/__init__.py b/forgelm/__init__.py index 2eb1f7d8..6bdba422 100644 --- a/forgelm/__init__.py +++ b/forgelm/__init__.py @@ -4,8 +4,7 @@ ``forgelm`` console script (and ``python -m forgelm.cli``); the Python **library API** that integrators reach via ``from forgelm import ...`` is documented in -``docs/analysis/code_reviews/library-api-design-202605021414.md`` and -finalised here in Phase 19. +``docs/design/library_api.md`` and finalised here in Phase 19. Lazy-import discipline (Phase 19): diff --git a/forgelm/_version.py b/forgelm/_version.py index af415daf..3e275acd 100644 --- a/forgelm/_version.py +++ b/forgelm/_version.py @@ -12,8 +12,7 @@ / signature changes are major). Consumers that depend on the library API can pin against this without coupling to the CLI version. -Per ``docs/analysis/code_reviews/library-api-design-202605021414.md`` -§4.3, the two versions track separately because the CLI surface and the +Per ``docs/design/library_api.md`` §4.3, the two versions track separately because the CLI surface and the Python API have different stability windows — the CLI may grow new subcommands without affecting library consumers, and library additions do not necessarily ship a new CLI feature. diff --git a/forgelm/cli/subcommands/_purge.py b/forgelm/cli/subcommands/_purge.py index 89d29585..ff657acb 100644 --- a/forgelm/cli/subcommands/_purge.py +++ b/forgelm/cli/subcommands/_purge.py @@ -1,7 +1,7 @@ """``forgelm purge`` subcommand (Phase 21 — GDPR Article 17 erasure). Implements the operator-facing surface specified in -``docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md``: +``docs/design/gdpr_erasure.md``: - ``forgelm purge --row-id --corpus `` — atomic JSONL row erasure with hashed audit event (Article 17 right-to-erasure for diff --git a/tests/test_gdpr_erasure.py b/tests/test_gdpr_erasure.py index df9ecdc8..6b4d2b75 100644 --- a/tests/test_gdpr_erasure.py +++ b/tests/test_gdpr_erasure.py @@ -1,7 +1,7 @@ """Phase 21 — GDPR Article 17 erasure (`forgelm purge`). Mirrors the design spec at -``docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md`` §7 +``docs/design/gdpr_erasure.md`` §7 which enumerates the 11 tests Phase 21 must ship. Tests run torch-free and use synthetic JSONL fixtures so every CI matrix combo exercises the full surface. diff --git a/tests/test_library_api.py b/tests/test_library_api.py index b74d2144..6daed3e6 100644 --- a/tests/test_library_api.py +++ b/tests/test_library_api.py @@ -1,7 +1,7 @@ """Phase 19 — Library API integration tests. Verifies the public Python surface that -``docs/analysis/code_reviews/library-api-design-202605021414.md`` pins: +``docs/design/library_api.md`` pins: - Stable symbol set matches ``forgelm.__all__``. - Lazy-import discipline holds — ``import forgelm`` does NOT pull @@ -124,7 +124,7 @@ def test_dir_leaks_no_internal_names_outside_all(self) -> None: def test_design_doc_experimental_symbols_are_exported(self) -> None: """F-PR29-A3-05: symbols listed Experimental in - ``docs/analysis/code_reviews/library-api-design-202605021414.md`` + ``docs/design/library_api.md`` MUST be importable from the top-level ``forgelm`` package. Otherwise consumers who follow the design doc and write ``from forgelm import compute_minhash`` get an From 35f2b65e68a4a784f569e91e7cd6a9444fab55a4 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 10:40:52 +0300 Subject: [PATCH 02/10] =?UTF-8?q?feat(site):=20hero=20rotating=20slider=20?= =?UTF-8?q?=E2=80=94=203=20personas=20(engineer=20/=20beginner=20/=20compl?= =?UTF-8?q?iance)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hero block previously shipped a single technical pitch ("YAML in. Fine-tuned model out."). That copy reads to engineers but invisibly to two adjacent audiences: non-technical users who would benefit from the wizard, and compliance / regulated-industry buyers for whom the EU AI Act story is the load-bearing differentiator. This commit replaces the single-pitch left column with a three-slot rotating slider so each audience meets ForgeLM in their own language within the first viewport. The right-column animated terminal stays put — it is part of the brand identity and reads as cross-cutting truth across all three pitches. The cross-cutting meta strip (EU AI Act ready · CPU-friendly audit · Air-gap capable · CI/CD integrated) likewise stays visible across slides because those four facts hold regardless of which persona is reading. ## Mechanics * **Three slides:** - Engineer (default, identical copy to the prior single pitch). - Beginner — "No YAML? No problem." pointing at ``quickstart.html?wizard=open`` (Stage B will land the modal wizard the deep-link opens). - Compliance — "EU AI Act ready by default." pointing at ``compliance.html``. * **Auto-rotate 8s**, hover-pause + focus-pause + visibilitychange pause (no CPU burn on background tabs). * **Keyboard:** ←/→ inside the slider; the slider is keyboard-focusable via ``tabindex=-1`` so screen-reader users can land on it programmatically without it being a tab stop. * **Touch:** horizontal swipe ≥ 50 px with vertical-dominant guard so scrolling the page doesn't trip the slider. * **Pagination dots:** three pills; active dot expands and runs a linear progress fill (CSS animation pinned to the 8s timer; pauses in lockstep when the slider pauses). * **Prev/next chevrons:** ghost by default; revealed on hover or keyboard focus inside the slider; hidden on screens < 720 px (touch users get the swipe gesture instead). * **Reduced-motion fallback:** every slide stacks linearly, controls disappear, the auto-rotate timer is a no-op. Linear reading order preserved. * **ARIA:** slides are ``role=group aria-roledescription=slide``; the pagination dot row is ``role=tablist`` with each dot ``role=tab aria-controls aria-selected`` to the slide it pages to. Active slide removes ``hidden``; inactive slides set it (so screen readers don't read all three at once between transitions). ## Files * ``site/index.html`` — left-column ``
`` replaced with ``.hero-personas`` container holding 3 ``.hero-slide`` blocks + pagination + arrows + the unchanged badge / meta strip. Right column ``.hero-terminal`` and grid template untouched. * ``site/css/style.css`` — new ``Hero rotating slider`` section inserted between ``Hero meta`` and ``Hero terminal``: ~180 lines covering slide cross-fade, dot pagination + 8s progress fill, prev/next chevron visibility, mobile arrow hide, reduced-motion stack-linear branch. * ``site/js/main.js`` — ``initHeroSlider()`` (~140 lines) wired into the ``DOMContentLoaded`` initialiser. Same defensive idioms as ``initHeroTyper`` (matchMedia early return, no-op when slider absent). * ``site/js/translations.js`` — 12 new keys × 6 locales = 72 new entries across the per-page home-block ``Object.assign`` calls (``engineer.title/lead/cta1/cta2``, ``beginner.title/lead/cta1/cta2``, ``compliance.title/lead/cta1/cta2``, ``slider.label``, ``dot.engineer/beginner/compliance``, ``prev``, ``next``). The legacy ``home.hero.{title,lead,cta1,cta2}`` keys are kept so external references (if any) don't break; the live HTML now binds to the persona-namespaced keys exclusively. ## Verification | Check | Result | |-------|--------| | ``check_anchor_resolution --strict`` | ✅ 268/268 | | ``check_bilingual_parity --strict`` | ✅ 40/40 | | ``check_cli_help_consistency --strict`` | ✅ 423/423 | | ``check_site_claims --strict`` | ✅ artefacts/templates/GPU/version | | ``check_site_chrome_parity`` | ✅ EN ↔ TR lockstep at 353 keys (was 341) | The DE/FR/ES/ZH 42-key chrome backlog is unchanged from the prior state — Stage A's 12 new keys land in EN+TR active tiers and in the four deferred locales identically (no new drift introduced). This is Stage A of the home / quickstart UX upgrade. Stage B (modal YAML wizard on quickstart) follows in a separate commit so the hero slider can be reviewed in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/css/style.css | 181 ++++++++++++++++++++++++++++++++++++++++ site/index.html | 80 +++++++++++++++--- site/js/main.js | 143 +++++++++++++++++++++++++++++++ site/js/translations.js | 108 ++++++++++++++++++++++++ 4 files changed, 499 insertions(+), 13 deletions(-) diff --git a/site/css/style.css b/site/css/style.css index 9e0bd2ef..925b3723 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -594,6 +594,187 @@ section { padding: 5rem 0; } } .hero-meta svg { width: 14px; height: 14px; color: var(--success); } +/* Hero rotating slider (3 personas: engineer / beginner / compliance). + Mechanics: cross-fade transitions, auto-rotate (8s) with hover-pause, + keyboard ←/→, swipe on touch, and a reduced-motion fallback that + stacks all slides linearly. The badge + cross-cutting meta strip stay + visible across slides — only the title/lead/CTA pair rotates. + + Layout note: ``.hero-personas`` becomes the grid item that replaces + the old left-column ``
``. The right-column ``.hero-terminal`` + keeps its original placement; the grid template is unchanged. */ +.hero-personas { + position: relative; +} + +.hero-slides { + position: relative; + /* min-height ensures the layout doesn't collapse during fade + transitions when the active slide is positioned absolutely; the + value is a comfortable cap for the longest pitch (compliance + slide currently around 4 lines of body copy at desktop width). */ + min-height: 320px; +} + +@media (max-width: 980px) { + .hero-slides { min-height: 360px; } +} +@media (max-width: 600px) { + .hero-slides { min-height: 380px; } +} + +.hero-slide { + position: absolute; + inset: 0 0 auto 0; + opacity: 0; + visibility: hidden; + transform: translateX(12px); + transition: + opacity 320ms ease-out, + transform 320ms ease-out, + visibility 0s linear 320ms; + pointer-events: none; +} + +.hero-slide.is-active { + position: relative; + opacity: 1; + visibility: visible; + transform: translateX(0); + transition: + opacity 320ms ease-out, + transform 320ms ease-out, + visibility 0s linear 0s; + pointer-events: auto; +} + +/* Slider pagination: 3 dots, the active dot expands and animates a + linear progress fill that mirrors the auto-rotate timer (8s). */ +.hero-slider-controls { + display: flex; + gap: 0.6rem; + align-items: center; + margin-top: 1.4rem; + margin-bottom: 1.4rem; +} + +.hero-slider-dot { + position: relative; + width: 24px; + height: 4px; + padding: 0; + border: none; + background: var(--border-strong); + border-radius: 999px; + cursor: pointer; + overflow: hidden; + transition: background 200ms ease, width 280ms ease; +} + +.hero-slider-dot:hover { background: var(--text-muted); } +.hero-slider-dot:focus-visible { + outline: 2px solid var(--ember-bright); + outline-offset: 3px; +} + +.hero-slider-dot.is-active { + width: 56px; + background: var(--border-strong); +} + +.hero-slider-dot .dot-fill { + position: absolute; + inset: 0; + background: linear-gradient(90deg, var(--ember-bright), var(--steel-bright)); + transform-origin: left center; + transform: scaleX(0); + border-radius: inherit; +} + +.hero-slider-dot.is-active .dot-fill { + animation: hero-slider-fill 8s linear forwards; +} + +.hero-personas[data-paused="true"] .hero-slider-dot.is-active .dot-fill { + animation-play-state: paused; +} + +@keyframes hero-slider-fill { + from { transform: scaleX(0); } + to { transform: scaleX(1); } +} + +/* Prev / next chevrons. Ghost by default; reveal on hover or + keyboard focus inside the slider. Hidden on small screens — touch + users get the swipe gesture instead. */ +.hero-slider-arrow { + position: absolute; + top: 35%; + transform: translateY(-50%); + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(20, 24, 35, 0.72); + border: 1px solid var(--border-strong); + border-radius: 50%; + color: var(--text); + cursor: pointer; + opacity: 0; + transition: opacity 200ms ease, background 200ms ease, border-color 200ms ease; + z-index: 2; +} +.hero-slider-arrow svg { width: 14px; height: 14px; } +.hero-slider-arrow.prev { left: -52px; } +.hero-slider-arrow.next { right: -52px; } + +.hero-personas:hover .hero-slider-arrow, +.hero-personas:focus-within .hero-slider-arrow { + opacity: 0.9; +} +.hero-slider-arrow:hover { + opacity: 1; + background: var(--bg-elev); + border-color: var(--ember-bright); +} +.hero-slider-arrow:focus-visible { + opacity: 1; + outline: 2px solid var(--ember-bright); + outline-offset: 2px; +} + +@media (max-width: 1100px) { + .hero-slider-arrow.prev { left: -16px; } + .hero-slider-arrow.next { right: -16px; } +} +@media (max-width: 720px) { + .hero-slider-arrow { display: none; } +} + +/* Reduced-motion: stack all slides, hide pagination + arrows, + neutralise the timer animation. Each slide becomes its own block + the user can read linearly. */ +@media (prefers-reduced-motion: reduce) { + .hero-slide { + position: relative !important; + inset: auto !important; + opacity: 1 !important; + visibility: visible !important; + transform: none !important; + pointer-events: auto !important; + margin-bottom: 1.6rem; + } + .hero-slide:not(:first-child) { + border-top: 1px dashed var(--border); + padding-top: 1.4rem; + } + .hero-slider-controls, + .hero-slider-arrow { display: none !important; } + .hero-slides { min-height: 0; } + .hero-slider-dot.is-active .dot-fill { animation: none; } +} + /* Hero terminal */ .hero-terminal { background: var(--code-bg); diff --git a/site/index.html b/site/index.html index 5bfcdea4..41d88fcc 100644 --- a/site/index.html +++ b/site/index.html @@ -85,27 +85,81 @@
-
+
Open source · Apache 2.0 · v0.5.5 -

YAML in.
Fine-tuned model out.

- -

ForgeLM is the config-driven LLM fine-tuning toolkit for teams that ship into regulated environments.

+
+ + + + + + + + +
-
- - - Get started - - - - View on GitHub - + +
+ + +
+ + + +
EU AI Act ready diff --git a/site/js/main.js b/site/js/main.js index 409d1aed..fc8749d9 100644 --- a/site/js/main.js +++ b/site/js/main.js @@ -16,6 +16,7 @@ initLangDropdown(); initCopyButtons(); initContactForm(); + initHeroSlider(); initHeroTyper(); initSmoothAnchorOffset(); }); @@ -239,6 +240,148 @@ }); } + /* ── Hero rotating slider (3 personas) ───────────── */ + // Rotates the left-column hero pitch across three personas + // (engineer / beginner / compliance) on an 8-second timer with + // hover-pause, keyboard ←/→ navigation, swipe-on-touch, and a + // visibilitychange-aware pause when the tab is backgrounded. The + // CSS handles the cross-fade transition; this hook only flips the + // ``.is-active`` class + ARIA state on slides + pagination dots. + // + // Reduced-motion users land in a CSS-only branch (slides stack + // vertically, controls hidden) so this initialiser is a no-op for + // them after the early return. + function initHeroSlider() { + var slider = document.querySelector('[data-hero-slider]'); + if (!slider) return; + var slides = Array.prototype.slice.call(slider.querySelectorAll('.hero-slide')); + var dots = Array.prototype.slice.call(slider.querySelectorAll('.hero-slider-dot')); + if (slides.length < 2 || dots.length !== slides.length) return; + + var ROTATE_MS = 8000; + var SWIPE_PX = 50; + var current = 0; + var timer = null; + var reducedMotion = + window.matchMedia && + window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + if (reducedMotion) { + // CSS already neutralises the layout; flag every slide visible + // so screen readers can iterate them in order. + slides.forEach(function (s) { + s.classList.add('is-active'); + s.removeAttribute('hidden'); + }); + return; + } + + function show(index) { + var n = slides.length; + if (index < 0) index = n - 1; + if (index >= n) index = 0; + current = index; + slides.forEach(function (s, i) { + var active = i === index; + s.classList.toggle('is-active', active); + if (active) { + s.removeAttribute('hidden'); + } else { + s.setAttribute('hidden', ''); + } + }); + dots.forEach(function (d, i) { + var active = i === index; + d.classList.toggle('is-active', active); + d.setAttribute('aria-selected', active ? 'true' : 'false'); + d.setAttribute('tabindex', active ? '0' : '-1'); + }); + } + + function next() { show(current + 1); } + function prev() { show(current - 1); } + + function start() { + if (timer || reducedMotion) return; + slider.dataset.paused = 'false'; + timer = window.setInterval(next, ROTATE_MS); + } + function stop() { + if (timer) { + window.clearInterval(timer); + timer = null; + } + slider.dataset.paused = 'true'; + } + function restart() { stop(); start(); } + + // Pagination dots (jump to specific slide) + dots.forEach(function (dot) { + dot.addEventListener('click', function () { + var idx = parseInt(dot.dataset.heroJump || '0', 10); + show(idx); + restart(); + }); + }); + + // Prev / next arrows + var prevBtn = slider.querySelector('[data-hero-prev]'); + var nextBtn = slider.querySelector('[data-hero-next]'); + if (prevBtn) prevBtn.addEventListener('click', function () { prev(); restart(); }); + if (nextBtn) nextBtn.addEventListener('click', function () { next(); restart(); }); + + // Keyboard ←/→ when focus is anywhere inside the slider + slider.addEventListener('keydown', function (e) { + if (e.key === 'ArrowLeft') { + prev(); restart(); + e.preventDefault(); + } else if (e.key === 'ArrowRight') { + next(); restart(); + e.preventDefault(); + } + }); + + // Pause on hover + focus, resume on leave + blur + slider.addEventListener('mouseenter', stop); + slider.addEventListener('mouseleave', start); + slider.addEventListener('focusin', stop); + slider.addEventListener('focusout', function (e) { + // Only resume if focus actually left the slider entirely. + if (!slider.contains(e.relatedTarget)) start(); + }); + + // Tab visibility — don't burn CPU on background tabs + document.addEventListener('visibilitychange', function () { + if (document.hidden) stop(); + else start(); + }); + + // Touch swipe (mobile). Threshold 50px so a vertical scroll + // doesn't accidentally trigger a slide change. + var touchStartX = null; + var touchStartY = null; + slider.addEventListener('touchstart', function (e) { + if (e.touches.length !== 1) return; + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + }, { passive: true }); + slider.addEventListener('touchend', function (e) { + if (touchStartX === null) return; + var dx = e.changedTouches[0].clientX - touchStartX; + var dy = e.changedTouches[0].clientY - touchStartY; + // Horizontal-dominant gesture only — protects vertical scroll. + if (Math.abs(dx) > SWIPE_PX && Math.abs(dx) > Math.abs(dy) * 1.4) { + if (dx > 0) prev(); else next(); + restart(); + } + touchStartX = null; + touchStartY = null; + }); + + show(0); + start(); + } + /* ── Hero terminal typewriter (cosmetic) ─────────── */ function initHeroTyper() { var typer = document.querySelector('[data-typer]'); diff --git a/site/js/translations.js b/site/js/translations.js index 65b3b162..b8f669ca 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -289,6 +289,24 @@ "home.hero.lead": "ForgeLM is the config-driven LLM fine-tuning toolkit for teams that ship into regulated environments. The full alignment stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — with integrated safety evaluation, EU AI Act audit trails, and auto-revert on quality regression.", "home.hero.cta1": "Get started", "home.hero.cta2": "View on GitHub", + "home.hero.engineer.title": "YAML in.
Fine-tuned model out.", + "home.hero.engineer.lead": "ForgeLM is the config-driven LLM fine-tuning toolkit for teams that ship into regulated environments. The full alignment stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — with integrated safety evaluation, EU AI Act audit trails, and auto-revert on quality regression.", + "home.hero.engineer.cta1": "Get started", + "home.hero.engineer.cta2": "View on GitHub", + "home.hero.beginner.title": "No YAML?
No problem.", + "home.hero.beginner.lead": "A guided wizard walks you through every choice — trainer, model, dataset, safety gates — and writes the YAML for you. No notebooks, no boilerplate, no fine-tuning expertise required.", + "home.hero.beginner.cta1": "Try the wizard", + "home.hero.beginner.cta2": "See the 5-step guide", + "home.hero.compliance.title":"EU AI Act ready
by default.", + "home.hero.compliance.lead": "Every run produces an Annex IV bundle, a tamper-evident audit log, a Llama Guard safety report, and a model card — automatically. Built for teams that ship into healthcare, finance, and public sector.", + "home.hero.compliance.cta1": "See compliance map", + "home.hero.compliance.cta2": "Explore enterprise features", + "home.hero.slider.label": "Switch hero pitch", + "home.hero.dot.engineer": "Pitch for engineers", + "home.hero.dot.beginner": "Pitch for newcomers", + "home.hero.dot.compliance": "Pitch for compliance teams", + "home.hero.prev": "Previous pitch", + "home.hero.next": "Next pitch", "home.hero.meta1": "EU AI Act ready", "home.hero.meta2": "CPU-friendly audit", "home.hero.meta3": "Air-gap capable", @@ -371,6 +389,24 @@ "home.hero.lead": "ForgeLM, regülasyonlu ortamlara üretim çıkaran ekipler için konfigürasyon odaklı LLM fine-tuning araç setidir. Tam alignment yığını — SFT, DPO, SimPO, KTO, ORPO, GRPO — entegre güvenlik değerlendirmesi, EU AI Act denetim kayıtları ve kalite regresyonunda otomatik geri alma ile.", "home.hero.cta1": "Hemen başla", "home.hero.cta2": "GitHub'da incele", + "home.hero.engineer.title": "YAML gir.
Fine-tuned model al.", + "home.hero.engineer.lead": "ForgeLM, regülasyonlu ortamlara üretim çıkaran ekipler için konfigürasyon odaklı LLM fine-tuning araç setidir. Tam alignment yığını — SFT, DPO, SimPO, KTO, ORPO, GRPO — entegre güvenlik değerlendirmesi, EU AI Act denetim kayıtları ve kalite regresyonunda otomatik geri alma ile.", + "home.hero.engineer.cta1": "Hemen başla", + "home.hero.engineer.cta2": "GitHub'da incele", + "home.hero.beginner.title": "YAML bilmiyor musun?
Sorun değil.", + "home.hero.beginner.lead": "Adım adım rehberli wizard, her seçimi seninle birlikte yapar — trainer, model, veri seti, güvenlik kapıları — ve YAML'ı senin yerine yazar. Notebook gerekmez, boilerplate gerekmez, fine-tuning uzmanlığı gerekmez.", + "home.hero.beginner.cta1": "Wizard'ı dene", + "home.hero.beginner.cta2": "5 adımlık rehberi gör", + "home.hero.compliance.title":"Varsayılan olarak
EU AI Act uyumlu.", + "home.hero.compliance.lead": "Her koşu otomatik olarak Annex IV paketi, kurcalama-kanıtı denetim günlüğü, Llama Guard güvenlik raporu ve model kartı üretir. Sağlık, finans ve kamu sektörüne üretim çıkaran ekipler için tasarlandı.", + "home.hero.compliance.cta1": "Uyumluluk haritası", + "home.hero.compliance.cta2": "Kurumsal özellikler", + "home.hero.slider.label": "Hero anlatım değiştir", + "home.hero.dot.engineer": "Mühendisler için anlatım", + "home.hero.dot.beginner": "Yeni başlayanlar için anlatım", + "home.hero.dot.compliance": "Uyumluluk ekipleri için anlatım", + "home.hero.prev": "Önceki anlatım", + "home.hero.next": "Sonraki anlatım", "home.hero.meta1": "EU AI Act uyumlu", "home.hero.meta2": "CPU dostu denetim", "home.hero.meta3": "Air-gap hazır", @@ -453,6 +489,24 @@ "home.hero.lead": "ForgeLM ist das konfigurationsgesteuerte LLM-Fine-Tuning-Toolkit für Teams, die in regulierte Umgebungen liefern. Der vollständige Alignment-Stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — mit integrierter Sicherheitsbewertung, EU-AI-Act-Audit-Trails und automatischem Rollback bei Qualitätsregression.", "home.hero.cta1": "Loslegen", "home.hero.cta2": "Auf GitHub ansehen", + "home.hero.engineer.title": "YAML rein.
Fine-Tuned Modell raus.", + "home.hero.engineer.lead": "ForgeLM ist das konfigurationsgesteuerte LLM-Fine-Tuning-Toolkit für Teams, die in regulierte Umgebungen liefern. Der vollständige Alignment-Stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — mit integrierter Sicherheitsbewertung, EU-AI-Act-Audit-Trails und automatischem Rollback bei Qualitätsregression.", + "home.hero.engineer.cta1": "Loslegen", + "home.hero.engineer.cta2": "Auf GitHub ansehen", + "home.hero.beginner.title": "Kein YAML?
Kein Problem.", + "home.hero.beginner.lead": "Ein geführter Wizard begleitet dich durch jede Entscheidung — Trainer, Modell, Datensatz, Sicherheits-Gates — und schreibt das YAML für dich. Keine Notebooks, kein Boilerplate, kein Fine-Tuning-Expertenwissen erforderlich.", + "home.hero.beginner.cta1": "Wizard ausprobieren", + "home.hero.beginner.cta2": "5-Schritte-Anleitung ansehen", + "home.hero.compliance.title":"EU-AI-Act
standardmäßig bereit.", + "home.hero.compliance.lead": "Jeder Lauf erzeugt automatisch ein Annex-IV-Bündel, ein manipulationssicheres Audit-Log, einen Llama-Guard-Sicherheitsbericht und eine Modellkarte. Entwickelt für Teams, die in Gesundheitswesen, Finanzwesen und öffentlichen Sektor liefern.", + "home.hero.compliance.cta1": "Compliance-Karte ansehen", + "home.hero.compliance.cta2": "Enterprise-Funktionen", + "home.hero.slider.label": "Hero-Pitch wechseln", + "home.hero.dot.engineer": "Pitch für Ingenieure", + "home.hero.dot.beginner": "Pitch für Einsteiger", + "home.hero.dot.compliance": "Pitch für Compliance-Teams", + "home.hero.prev": "Vorheriger Pitch", + "home.hero.next": "Nächster Pitch", "home.hero.meta1": "EU-AI-Act bereit", "home.hero.meta2": "CPU-freundliches Audit", "home.hero.meta3": "Air-Gap-fähig", @@ -535,6 +589,24 @@ "home.hero.lead": "ForgeLM est la boîte à outils de fine-tuning LLM pilotée par configuration pour les équipes qui livrent en environnement régulé. La pile d'alignement complète — SFT, DPO, SimPO, KTO, ORPO, GRPO — avec évaluation de sécurité intégrée, pistes d'audit EU AI Act et rollback automatique en cas de régression de qualité.", "home.hero.cta1": "Commencer", "home.hero.cta2": "Voir sur GitHub", + "home.hero.engineer.title": "Du YAML.
Un modèle fine-tuné.", + "home.hero.engineer.lead": "ForgeLM est la boîte à outils de fine-tuning LLM pilotée par configuration pour les équipes qui livrent en environnement régulé. La pile d'alignement complète — SFT, DPO, SimPO, KTO, ORPO, GRPO — avec évaluation de sécurité intégrée, pistes d'audit EU AI Act et rollback automatique en cas de régression de qualité.", + "home.hero.engineer.cta1": "Commencer", + "home.hero.engineer.cta2": "Voir sur GitHub", + "home.hero.beginner.title": "Pas de YAML ?
Pas de problème.", + "home.hero.beginner.lead": "Un wizard guidé t'accompagne à chaque choix — trainer, modèle, jeu de données, gardes-fous de sécurité — et écrit le YAML pour toi. Pas de notebooks, pas de boilerplate, aucune expertise en fine-tuning requise.", + "home.hero.beginner.cta1": "Essayer le wizard", + "home.hero.beginner.cta2": "Voir le guide en 5 étapes", + "home.hero.compliance.title":"EU AI Act conforme
par défaut.", + "home.hero.compliance.lead": "Chaque exécution produit automatiquement un dossier Annex IV, un journal d'audit infalsifiable, un rapport Llama Guard et une fiche modèle. Conçu pour les équipes qui livrent en santé, finance et secteur public.", + "home.hero.compliance.cta1": "Voir la carte de conformité", + "home.hero.compliance.cta2": "Explorer les fonctionnalités enterprise", + "home.hero.slider.label": "Changer de pitch", + "home.hero.dot.engineer": "Pitch pour ingénieurs", + "home.hero.dot.beginner": "Pitch pour débutants", + "home.hero.dot.compliance": "Pitch pour équipes conformité", + "home.hero.prev": "Pitch précédent", + "home.hero.next": "Pitch suivant", "home.hero.meta1": "Conforme EU AI Act", "home.hero.meta2": "Audit compatible CPU", "home.hero.meta3": "Compatible air-gap", @@ -617,6 +689,24 @@ "home.hero.lead": "ForgeLM es el toolkit de fine-tuning de LLM basado en configuración para equipos que despliegan en entornos regulados. El stack de alignment completo — SFT, DPO, SimPO, KTO, ORPO, GRPO — con evaluación de seguridad integrada, registros de auditoría EU AI Act y reversión automática ante regresiones de calidad.", "home.hero.cta1": "Empezar", "home.hero.cta2": "Ver en GitHub", + "home.hero.engineer.title": "YAML entra.
Modelo fine-tuned sale.", + "home.hero.engineer.lead": "ForgeLM es el toolkit de fine-tuning de LLM basado en configuración para equipos que despliegan en entornos regulados. El stack de alignment completo — SFT, DPO, SimPO, KTO, ORPO, GRPO — con evaluación de seguridad integrada, registros de auditoría EU AI Act y reversión automática ante regresiones de calidad.", + "home.hero.engineer.cta1": "Empezar", + "home.hero.engineer.cta2": "Ver en GitHub", + "home.hero.beginner.title": "¿Sin YAML?
Sin problema.", + "home.hero.beginner.lead": "Un wizard guiado te acompaña en cada elección — trainer, modelo, conjunto de datos, controles de seguridad — y escribe el YAML por ti. Sin notebooks, sin boilerplate, sin necesidad de experiencia en fine-tuning.", + "home.hero.beginner.cta1": "Probar el wizard", + "home.hero.beginner.cta2": "Ver la guía de 5 pasos", + "home.hero.compliance.title":"EU AI Act listo
por defecto.", + "home.hero.compliance.lead": "Cada ejecución produce automáticamente un paquete Annex IV, un registro de auditoría a prueba de manipulaciones, un informe Llama Guard y una ficha del modelo. Diseñado para equipos que despliegan en salud, finanzas y sector público.", + "home.hero.compliance.cta1": "Ver mapa de cumplimiento", + "home.hero.compliance.cta2": "Explorar funciones enterprise", + "home.hero.slider.label": "Cambiar pitch", + "home.hero.dot.engineer": "Pitch para ingenieros", + "home.hero.dot.beginner": "Pitch para principiantes", + "home.hero.dot.compliance": "Pitch para equipos de cumplimiento", + "home.hero.prev": "Pitch anterior", + "home.hero.next": "Pitch siguiente", "home.hero.meta1": "Conforme con EU AI Act", "home.hero.meta2": "Auditoría apta para CPU", "home.hero.meta3": "Apto para air-gap", @@ -699,6 +789,24 @@ "home.hero.lead": "ForgeLM 是面向需要在受监管环境中交付的团队的、基于配置的 LLM 微调工具包。完整对齐栈 — SFT、DPO、SimPO、KTO、ORPO、GRPO — 配备内置安全评估、EU AI Act 审计轨迹,以及质量回归时的自动回滚。", "home.hero.cta1": "开始使用", "home.hero.cta2": "在 GitHub 查看", + "home.hero.engineer.title": "输入 YAML。
输出微调模型。", + "home.hero.engineer.lead": "ForgeLM 是面向需要在受监管环境中交付的团队的、基于配置的 LLM 微调工具包。完整对齐栈 — SFT、DPO、SimPO、KTO、ORPO、GRPO — 配备内置安全评估、EU AI Act 审计轨迹,以及质量回归时的自动回滚。", + "home.hero.engineer.cta1": "开始使用", + "home.hero.engineer.cta2": "在 GitHub 查看", + "home.hero.beginner.title": "不懂 YAML?
没关系。", + "home.hero.beginner.lead": "引导式向导陪你完成每一个选择 — trainer、模型、数据集、安全门控 — 并为你写好 YAML。无需 notebook,无需样板代码,也不需要任何微调专业知识。", + "home.hero.beginner.cta1": "试用向导", + "home.hero.beginner.cta2": "查看 5 步指南", + "home.hero.compliance.title":"默认即
EU AI Act 就绪。", + "home.hero.compliance.lead": "每次运行都会自动生成 Annex IV 资料包、防篡改审计日志、Llama Guard 安全报告以及模型卡。专为在医疗、金融和公共部门交付的团队打造。", + "home.hero.compliance.cta1": "查看合规映射", + "home.hero.compliance.cta2": "探索企业级功能", + "home.hero.slider.label": "切换 Hero 概要", + "home.hero.dot.engineer": "面向工程师的概要", + "home.hero.dot.beginner": "面向初学者的概要", + "home.hero.dot.compliance": "面向合规团队的概要", + "home.hero.prev": "上一个概要", + "home.hero.next": "下一个概要", "home.hero.meta1": "符合 EU AI Act", "home.hero.meta2": "CPU 友好审计", "home.hero.meta3": "支持气隙环境", From b0cb84898a477a50fcf1581b25bfdf376f267d05 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 11:03:24 +0300 Subject: [PATCH 03/10] =?UTF-8?q?feat(site):=20wizard=20modal=20=E2=80=94?= =?UTF-8?q?=20in-browser=20YAML=20generator=20on=20quickstart=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage B of the home / quickstart UX upgrade (Stage A landed the home hero rotating slider in 35f2b65). The site now ships an interactive modal wizard that mirrors the CLI ``forgelm --wizard`` flow but lives entirely in the browser: walks the operator through 7 steps, generates a working ``quickstart-generated.yaml``, and offers copy / download / run-snippet delivery. The existing "Five steps. No notebook." page structure is preserved untouched — the wizard is a complementary modal-as-app, not a replacement. ## Steps (7 total) 1. **Welcome** — experience-level toggle (Beginner / Expert). Beginner mode shows a tutorial paragraph on every step; Expert mode shows short hints only. The detail level is also re-toggleable per-step from the header so the operator can flip mid-flow. 2. **Use case** — six template cards (Customer support / Code copilot / Domain expert / GRPO math / Turkish medical Q&A / Custom). Selecting a template auto-fills the model + dataset + format on subsequent steps; everything stays overrideable. 3. **Base model** — four preset cards (Llama-3.1-8B-Instruct, Qwen2.5-7B-Instruct, Mistral-7B-Instruct-v0.3, Phi-3-mini-4k-instruct) plus a Custom option that takes a free-form HuggingFace model ID. 4. **Dataset** — radio for HuggingFace / local JSONL / local PDF (with the ingest-first note baked into the YAML preamble), path / name input, format toggle (prompt+response vs messages), and prompt / response field-name inputs. 5. **Training params** — QLoRA toggle, LoRA r slider (4-64), LoRA alpha slider (8-128), epochs (1-5), learning rate dropdown (1e-4 / 5e-5 / 3e-5 / 1e-5), batch size (1 / 2 / 4 / 8). 6. **Evaluation & EU AI Act compliance** — risk classification radio (minimal / limited / high / unacceptable / unknown), auto-revert + max-loss threshold, Llama Guard safety eval, audit log toggle, human-approval gate toggle, Annex IV export toggle. **High-risk and unacceptable selections force-enable the audit log + human approval + Annex IV toggles** and surface a red callout banner explaining the Article 9-17 obligations. 7. **Review** — final live YAML (syntax-highlighted), 3 delivery actions (copy to clipboard, download as ``quickstart-generated.yaml``, reset wizard) plus a ``forgelm --config ... --dry-run`` snippet. ## Triggers * ``[data-wizard-open]`` clicks open the modal: - ``page-header-aside`` link "Open the wizard ↗" (new, sits under the existing "Install, pick a template, validate, train, deploy." lead). - ``cta-card`` button "Launch the wizard" (replaces the old README link as the primary CTA; "CLI version (forgelm --wizard)" + "Browse all features" remain as secondary / tertiary). * URL parameter ``?wizard=open`` auto-opens the modal on page load. Used by the home-page Beginner-slide CTA wired in Stage A (``index.html`` → ``quickstart.html?wizard=open``). Combined with ``&step=N`` (future) it can deep-link to a specific step. * The URL is ``replaceState``-pushed when the modal opens / closes so a refresh round-trips back into the same state. ## State & persistence * In-memory state object mirrored to ``localStorage`` under ``forgelm.wizard.state``. Schema-versioned (``state.v = 1``) so future shape changes can clear stale snapshots. * Every input change saves; every step transition saves. * On reopen, the wizard surfaces a "Resume?" pathway implicitly — the saved step is restored. Reset button on Step 7 clears localStorage with a confirm dialog. * High-risk auto-coercion runs on every render so toggling risk back to a lower tier doesn't accidentally leave audit + approval off; the upward transition force-enables, downward keeps the operator's choices. ## Modal mechanics * ``role="dialog" aria-modal="true" aria-labelledby="wizard-title"``; the outer wrapper carries ``hidden`` when closed so the modal is removed from the accessibility tree entirely. * Focus trap on Tab / Shift+Tab cycles within the dialog. * Escape key closes; backdrop click closes; the X button in the header closes. Return focus to the triggering element on close. * The page background is marked ``aria-hidden`` while the modal is open and ``document.body.style.overflow = "hidden"`` prevents background scroll. * Reduced-motion: dialog enter animation disabled, progress-bar transition disabled, slide animations no-op. ## Layout * **Desktop (≥ 901 px):** 1080 px × 720 px centered dialog, 60/40 split (step pane left, YAML preview right). Both panes scroll independently. * **Tablet (901-720 px):** Same shape, slightly tighter padding. * **Mobile (≤ 900 px):** Fullscreen takeover. YAML preview collapses into an accordion under the step pane (default collapsed; tap to expand). Step name in the footer hides to make room for prev/next. ## YAML serialisation Browser-side string-builder (no YAML library dependency). Output is structurally aligned with ``config_template.yaml`` so it passes ``forgelm --config quickstart-generated.yaml --dry-run`` cleanly. Inline syntax tinting paints comments / keys / strings / booleans / numbers / null in distinct colours via a small line-walk regex. ## Files * ``site/quickstart.html`` — page-header sub-CTA, body-end modal scaffold (~75 lines), `` + From 57abaea5571c5bfd2efad6687ee4a75f66041fdf Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 12:13:12 +0300 Subject: [PATCH 04/10] fix(site): wizard schema audit + scroll fix + per-field beginner detail + prominent CTA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage B follow-up addressing the four issues raised on the wizard. Schema audit (the YAML the wizard emitted was invalid against Pydantic): - ModelConfig: name_or_path (was model_name_or_path); load_in_4bit belongs to model, not "model.use_qlora" sibling. - LoraConfigModel: now emits a top-level lora: block with r/alpha/ dropout/bias/target_modules. Previously folded into model:. - DataConfig: drop format / prompt_field / response_field — extra="forbid" rejects them; row shape is auto-detected. Single tutorial block in the UI explains the auto-detect. - TrainingConfig: trainer_type lives under training:, not as a top-level trainer: key. Drop top-level trainer:. - EvaluationConfig: no enabled flag — the block being present is the enable signal. require_human_approval is the Article 14 gate. - SafetyConfig: classifier (was classifier_path). - ComplianceMetadataConfig: drop audit_log/annex_iv_export toggles — the audit log is always written, the Annex IV bundle is produced from the compliance.* metadata. Added provider_name / system_name / intended_purpose inputs (surfaced for high-risk/unacceptable tiers). Verified: both the high-risk full-block path and the limited-risk minimal path dry-run cleanly through the real ForgeConfig. Modal scroll fix: - .wizard-body grid container and .wizard-step-pane grid item both needed min-height: 0 plus max-height: 100% on the pane. Without that, flex/grid children take their content's intrinsic height and overflow-y: auto never fires. Step panes are now scrollable on every step (welcome / use-case / model / dataset / training / compliance / review) on desktop and mobile. - Reset pane.scrollTop = 0 on prev/next navigation so the operator always sees the new step's title. Per-field beginner detail (Step 5 + Step 6): - New .wizard-row-detail block with steel-bright left border, rendered beneath the hint when state.detailsVisible is true. - Six training rows (qlora / lora_r / lora_alpha / epochs / lr / batch) and four eval rows (auto_revert / max_loss / safety / human_approval) now ship 2-3-sentence plain-language primers explaining what the field MEANS, not just what range to pick. Prominent wizard CTA (quickstart.html): - New .wizard-spotlight banner above the manual five-step grid: eyebrow + title + lead + 2 CTAs + 3 meta items + a 3-stage visual diagram (Answer 7 questions → Get a valid YAML → Run it). - .steps-intro separator routes the eye from the spotlight down into the manual steps. Translations: - EN/TR (active tier): full coverage of the new keys (autodetect, always-emitted info, approval/eval/training detail blocks, provider metadata inputs, spotlight CTA copy) — 41 distinct wizard keys resolve in both locales. - DE/FR/ES/ZH (deferred tier): same structural updates; legacy keys removed in lockstep so site_chrome_parity reports no new drift beyond the existing GDPR/ISO backlog. Verified with the seven-gate gauntlet: - ruff format + ruff check: pass - pytest: 1479 passed, 14 skipped - forgelm --config config_template.yaml --dry-run: valid - check_anchor_resolution.py --strict: 268 docs OK - check_bilingual_parity.py --strict: 40 doc pairs in lockstep - check_cli_help_consistency.py --strict: 423 invocations OK - check_site_chrome_parity.py --strict: EN/TR in lockstep - check_site_claims.py --strict: artefacts + templates + version OK - A wizard-emitted YAML (high-risk full + limited-risk minimal) was fed to the real ForgeConfig validator; both pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/css/style.css | 221 +++++++++++++++++++++++++++- site/js/translations.js | 282 ++++++++++++++++++++++++++++-------- site/js/wizard.js | 314 ++++++++++++++++++++++++---------------- site/quickstart.html | 61 ++++++++ 4 files changed, 695 insertions(+), 183 deletions(-) diff --git a/site/css/style.css b/site/css/style.css index 4be96f44..c6e31167 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -1326,6 +1326,201 @@ section { padding: 5rem 0; } flex-wrap: wrap; } +/* Wizard spotlight — the prominent CTA banner that lands between the + quickstart page-header and the canonical 5-step content. Designed + to be impossible to miss without overshadowing the existing manual + reference flow underneath. Two-column layout on desktop: marketing + copy + primary CTA on the left, a small visual diagram on the right + that mimics the wizard's three-stage contract (form → YAML → run). + Mobile collapses to single column. */ +.wizard-spotlight { + position: relative; + margin: -1rem 0 3rem; + padding: 2rem 2.2rem; + background: + radial-gradient(ellipse at 0% 0%, rgba(249, 115, 22, .14), transparent 55%), + radial-gradient(ellipse at 100% 100%, rgba(14, 165, 233, .12), transparent 55%), + linear-gradient(180deg, var(--bg-elev), var(--bg-card)); + border: 1px solid var(--border-strong); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + gap: 2.4rem; + align-items: center; + overflow: hidden; +} +.wizard-spotlight::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(249,115,22,.4), transparent 40%, rgba(14,165,233,.4)); + opacity: 0.12; + pointer-events: none; +} +.wizard-spotlight > * { + position: relative; + z-index: 1; +} +.wizard-spotlight-eyebrow { + display: inline-flex; + align-items: center; + gap: 0.45rem; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ember-bright); + margin-bottom: 0.7rem; +} +.wizard-spotlight-eyebrow::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--ember); + box-shadow: 0 0 10px var(--ember); + animation: pulse 2s ease-in-out infinite; +} +.wizard-spotlight-title { + font-size: 1.6rem; + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: 0.6rem; + line-height: 1.25; +} +.wizard-spotlight-lead { + font-size: 1rem; + color: var(--text-secondary); + margin-bottom: 1.4rem; + max-width: 540px; +} +.wizard-spotlight-actions { + display: flex; + gap: 0.8rem; + flex-wrap: wrap; +} +.wizard-spotlight-meta { + display: flex; + gap: 1.2rem; + flex-wrap: wrap; + margin-top: 1rem; + font-size: 0.82rem; + color: var(--text-muted); +} +.wizard-spotlight-meta span { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} +.wizard-spotlight-meta svg { + width: 14px; + height: 14px; + color: var(--success); +} + +/* Visual: three-stage diagram. Each stage is a small card with an + icon + label + tiny snippet showing what the operator gives / gets + at that step. The arrows between cards reinforce the flow. Built + in pure SVG so the icons stay crisp at any zoom. */ +.wizard-spotlight-visual { + display: flex; + flex-direction: column; + gap: 0.8rem; + align-items: stretch; +} +.wizard-spotlight-stage { + display: grid; + grid-template-columns: 36px 1fr; + gap: 0.8rem; + padding: 0.8rem 1rem; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + align-items: center; + font-size: 0.85rem; +} +.wizard-spotlight-stage-icon { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-elev); + border: 1px solid var(--border-strong); + border-radius: 8px; + color: var(--ember-bright); +} +.wizard-spotlight-stage-icon svg { + width: 18px; + height: 18px; +} +.wizard-spotlight-stage-label { + font-weight: 600; + color: var(--text); + display: block; +} +.wizard-spotlight-stage-snippet { + font-family: var(--font-mono); + font-size: 0.74rem; + color: var(--text-muted); + margin-top: 0.15rem; +} +.wizard-spotlight-arrow { + margin: -0.4rem 0; + text-align: center; + color: var(--text-muted); + height: 16px; + display: flex; + align-items: center; + justify-content: center; +} +.wizard-spotlight-arrow svg { + width: 14px; + height: 14px; +} + +/* Five-step section heading — small introducing label that sits + between the spotlight and the existing ``.steps`` block. Helps the + reader understand they have two equally-valid paths (wizard above + OR manual below). */ +.steps-intro { + margin-bottom: 1.6rem; + font-size: 0.86rem; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 0.6rem; + flex-wrap: wrap; +} +.steps-intro::before { + content: ''; + display: block; + flex: 1; + height: 1px; + background: var(--border); + min-width: 30px; +} +.steps-intro::after { + content: ''; + display: block; + flex: 1; + height: 1px; + background: var(--border); + min-width: 30px; +} + +@media (max-width: 900px) { + .wizard-spotlight { + grid-template-columns: 1fr; + gap: 1.6rem; + padding: 1.6rem 1.4rem; + } + .wizard-spotlight-title { + font-size: 1.35rem; + } +} + /* Inline link-styled CTA used by the wizard trigger and similar "secondary action" affordances inside copy-heavy paragraphs. Renders like a link but keeps the button semantics so screen @@ -1496,12 +1691,18 @@ section { padding: 5rem 0; } border-color: var(--border-strong); } -/* Body: 60/40 split — step pane + YAML preview */ +/* Body: 60/40 split — step pane + YAML preview. + ``min-height: 0`` is mandatory on flex/grid children that contain a + scrollable region — without it the child takes its content's intrinsic + height and overflow:auto never fires. This was the root cause of the + "modal won't scroll" bug; both the wizard-body grid container and the + step-pane grid item need it. */ .wizard-body { flex: 1; display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); gap: 0; + min-height: 0; overflow: hidden; } @@ -1509,6 +1710,8 @@ section { padding: 5rem 0; } padding: 2rem 2rem 1.4rem; overflow-y: auto; overflow-x: hidden; + min-height: 0; + max-height: 100%; /* Step content can grow tall on Step 6 (compliance fields); the pane scrolls independently of the YAML preview. */ } @@ -1745,6 +1948,22 @@ section { padding: 5rem 0; } } .wizard-row-hint[hidden] { display: none; } +/* Per-field beginner-mode detail paragraph. Sits beneath the hint and + gives 2-3 sentences of plain-language explanation about WHAT the + field means (not just what range to pick). Visually distinct from + the row hint so the operator can pattern-match: hint = quick value + guidance, detail = conceptual primer. */ +.wizard-row-detail { + margin-top: 0.5rem; + padding: 0.6rem 0.8rem; + background: var(--bg); + border-left: 2px solid var(--steel-bright); + border-radius: 4px; + font-size: 0.8rem; + line-height: 1.5; + color: var(--text-secondary); +} + .wizard-input, .wizard-select { display: block; diff --git a/site/js/translations.js b/site/js/translations.js index ac441630..7ec3ee9d 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -1489,27 +1489,30 @@ "wizard.dataset.pdf.desc": "ForgeLM will ingest these to JSONL before training (PII + secrets masked).", "wizard.dataset.name.label": "Dataset path or name", "wizard.dataset.name.hint": "HuggingFace name (org/dataset), local file path (./data/train.jsonl), or directory (./documents/).", - "wizard.dataset.format.label":"Data format", - "wizard.dataset.format.prompt":"prompt + response (single-turn)", - "wizard.dataset.format.messages":"messages (multi-turn chat)", - "wizard.dataset.prompt_field":"Prompt field name", - "wizard.dataset.response_field":"Response field name", + "wizard.dataset.autodetect.title": "Format is auto-detected.", + "wizard.dataset.autodetect.body": "ForgeLM reads the JSONL fields directly: rows with a messages array become multi-turn chat; rows with prompt + response become instruction-following. Mixed datasets are not supported — the loader picks the shape from the first row.", "wizard.step.training.title": "Training parameters", "wizard.step.training.name": "Training", "wizard.step.training.desc": "Defaults work for 90% of cases. Tune only if you know why.", "wizard.step.training.tutorial": "Training parameters control HOW the model learns. QLoRA reduces memory by quantizing weights to 4-bit. LoRA rank (r) is adapter size — bigger means more capacity but slower. Epochs is how many times we'll loop through your data. Learning rate controls step size. Batch size depends on your GPU memory.", "wizard.training.qlora.label": "Use QLoRA (4-bit quantization)", "wizard.training.qlora.hint": "Quantizes the base model to 4-bit so it fits in less VRAM. Recommended for most setups.", + "wizard.training.qlora.detail": "QLoRA loads the base model with each weight quantized to 4-bit (a much smaller representation than the original 16-bit), then trains a small adapter on top. The base weights stay frozen, so the only thing that needs full precision is the adapter — that's how an 8B model fits on a 12 GB GPU. Disable only if you have a high-VRAM GPU and need maximum quality.", "wizard.training.lora_r.label": "LoRA rank (r)", "wizard.training.lora_r.hint": "Adapter size. 8 is balanced; 16-32 for harder tasks; 64 only if you have lots of data.", + "wizard.training.lora_r.detail": "LoRA freezes the base model and trains two small matrices that approximate the change you want. Rank (r) is the size of those matrices — bigger means the adapter can express more complex changes but takes longer to train and uses more memory. 8 is enough for most chat/instruction fine-tunes; bump to 16 or 32 only if your loss plateaus too high.", "wizard.training.lora_alpha.label":"LoRA alpha", "wizard.training.lora_alpha.hint": "Scaling factor. Convention: alpha = 2 × r. The wizard tracks this automatically.", + "wizard.training.lora_alpha.detail":"Alpha multiplies the adapter's effect on the base model. The community convention is alpha = 2 × r — that gives the adapter a stable influence regardless of rank. Going much higher amplifies the adapter (faster learning, more risk of instability); much lower mutes it (closer to the base model, slower learning).", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "How many times to loop through your data. 3 is a balanced default.", + "wizard.training.epochs.detail": "One epoch means the trainer has seen every example in your dataset once. Too few epochs and the model won't learn; too many and it starts memorising specific examples (overfitting). 3 is the sweet spot for most fine-tunes — bump up only if your dataset is very small (<1K rows) or your loss is still dropping by the end.", "wizard.training.lr.label": "Learning rate", "wizard.training.lr.hint": "Step size for weight updates. 1e-4 works for most LoRA setups.", + "wizard.training.lr.detail": "Learning rate controls how big a step the optimizer takes after each batch. Too high and the model overshoots and never converges; too low and training is painfully slow. LoRA can usually take larger learning rates than full fine-tuning because only the adapter is being updated — 1e-4 is the standard starting point.", "wizard.training.batch.label": "Batch size (per device)", "wizard.training.batch.hint": "Bigger batches train faster but need more VRAM. Start at 2 if you're not sure.", + "wizard.training.batch.detail": "Batch size is how many examples the GPU processes in parallel. Bigger batches mean faster training and more stable gradients, but they also use more VRAM. With QLoRA on a 12 GB GPU you can usually fit batch=2 with an 8B model; if you OOM, drop to 1 and increase gradient accumulation to compensate.", "wizard.step.compliance.title": "Evaluation & EU AI Act", "wizard.step.compliance.name": "Evaluation & compliance", "wizard.step.compliance.desc": "Quality gates and regulatory artefacts.", @@ -1527,16 +1530,27 @@ "wizard.risk.unacceptable.callout.body": "Article 5 prohibits this use class entirely. The wizard will still generate a YAML, but you should not deploy a system in this tier. Reclassify or stop here.", "wizard.eval.auto_revert.label": "Auto-revert on quality regression", "wizard.eval.auto_revert.hint": "Roll back to the base model if the fine-tune scores worse than the configured floor.", + "wizard.eval.auto_revert.detail": "After training finishes, ForgeLM evaluates the new model. If quality dropped below the floor (or safety failed, or a benchmark task regressed), auto-revert deletes the fine-tuned model and restores the base. This is a hard guarantee — you cannot accidentally ship a worse model.", "wizard.eval.max_loss.label": "Max acceptable eval loss", "wizard.eval.max_loss.hint": "If post-training eval loss exceeds this, auto-revert triggers. 2.0 is a balanced default.", + "wizard.eval.max_loss.detail": "Eval loss measures how surprised the model is by held-out examples — lower is better. The 2.0 floor catches most catastrophic failures (e.g. learning rate too high collapsed the model) without false-positives on normal noise. Tighten only if you have a stable baseline you've measured before.", "wizard.eval.safety.label": "Llama Guard safety evaluation", "wizard.eval.safety.hint": "Scans outputs for harmful content across 14 categories. Required for high-risk tiers.", - "wizard.compliance.audit.label": "Append-only audit log", - "wizard.compliance.audit.hint": "Tamper-evident JSONL log of every training event. Required for high-risk tiers.", + "wizard.eval.safety.detail": "Llama Guard is a safety classifier that scores model outputs against 14 harm categories (violence, illegal acts, self-harm, sexual content, etc.). After training, ForgeLM samples adversarial test prompts and checks the fine-tuned model's responses. If too many slip past the safety threshold, auto-revert triggers. Always on for high-risk deployments.", "wizard.compliance.approval.label": "Human approval gate before deployment", "wizard.compliance.approval.hint": "Requires 'forgelm approve ' before the model leaves staging. Required for high-risk tiers.", - "wizard.compliance.annex.label": "Annex IV technical documentation export", - "wizard.compliance.annex.hint": "Auto-generates the EU AI Act Annex IV bundle (training data, performance, monitoring). Required for high-risk tiers.", + "wizard.compliance.approval.detail": "Article 14 of the EU AI Act requires meaningful human oversight for high-risk systems. With this gate enabled, the trained model lands in a staging directory and the pipeline exits 4 — operators must run 'forgelm approve ' (or 'reject') after reviewing the model card, audit log, and benchmark results. The audit log records who approved, when, and what comment they left.", + "wizard.compliance.always.title": "Always emitted, no toggle:", + "wizard.compliance.always.body": "ForgeLM writes a tamper-evident audit log (audit_log.jsonl) for every run regardless of YAML — the log is the foundation of EU AI Act traceability and cannot be opted out of. The Annex IV technical documentation bundle is generated automatically whenever the compliance.* metadata block is present, so simply filling in the fields below produces it.", + "wizard.compliance.provider_name.label": "Provider name", + "wizard.compliance.provider_name.hint": "Annex IV §1: legal entity that provides the system.", + "wizard.compliance.provider_name.placeholder":"Acme AI Ltd.", + "wizard.compliance.system_name.label": "System name", + "wizard.compliance.system_name.hint": "Annex IV §1: human-readable name of this AI system.", + "wizard.compliance.system_name.placeholder": "Customer Support Assistant v1", + "wizard.compliance.intended_purpose.label": "Intended purpose", + "wizard.compliance.intended_purpose.hint": "Annex IV §1: declared purpose of the system in plain language.", + "wizard.compliance.intended_purpose.placeholder":"Triage customer support tickets via chat", "wizard.step.review.title": "Review your config", "wizard.step.review.name": "Review", "wizard.step.review.desc": "Here's the YAML. Copy it, download it, and run.", @@ -1545,7 +1559,20 @@ "wizard.review.download": "Download .yaml", "wizard.review.reset": "Reset wizard", "wizard.review.reset.confirm":"Reset all wizard answers and start over? Your YAML will be lost.", - "wizard.review.run_label": "Then run:" + "wizard.review.run_label": "Then run:", + "quickstart.spotlight.eyebrow": "Skip the YAML", + "quickstart.spotlight.title": "Use the in-browser wizard to build your config in 3 minutes.", + "quickstart.spotlight.lead": "Pick your use case, base model, dataset, and EU AI Act risk tier. The wizard writes a working quickstart-generated.yaml you can run with one command — no fine-tuning expertise required.", + "quickstart.spotlight.cta_primary": "Open the wizard", + "quickstart.spotlight.cta_secondary":"Or follow the 5-step guide", + "quickstart.spotlight.meta1": "7 guided steps", + "quickstart.spotlight.meta2": "Live YAML preview", + "quickstart.spotlight.meta3": "EU AI Act presets", + "quickstart.spotlight.stage1.label": "Answer 7 questions", + "quickstart.spotlight.stage1.snippet":"use case · model · data · risk", + "quickstart.spotlight.stage2.label": "Get a valid YAML", + "quickstart.spotlight.stage3.label": "Run it", + "quickstart.spotlight.manual_intro": "Or follow the manual 5-step reference below" }); Object.assign(T.tr, { @@ -1640,27 +1667,30 @@ "wizard.dataset.pdf.desc": "ForgeLM eğitimden önce JSONL'a ingest eder (PII + secrets maskeli).", "wizard.dataset.name.label": "Veri seti yolu veya adı", "wizard.dataset.name.hint": "HuggingFace adı (org/dataset), yerel dosya yolu (./data/train.jsonl) veya dizin (./documents/).", - "wizard.dataset.format.label":"Veri formatı", - "wizard.dataset.format.prompt":"prompt + response (tek tur)", - "wizard.dataset.format.messages":"messages (çok turlu sohbet)", - "wizard.dataset.prompt_field":"Prompt alan adı", - "wizard.dataset.response_field":"Response alan adı", + "wizard.dataset.autodetect.title": "Format otomatik algılanır.", + "wizard.dataset.autodetect.body": "ForgeLM, JSONL alanlarını doğrudan okur: messages dizisi olan satırlar çok turlu sohbet olur; prompt + response olan satırlar talimat-takip olur. Karışık veri setleri desteklenmez — yükleyici şekli ilk satırdan seçer.", "wizard.step.training.title": "Eğitim parametreleri", "wizard.step.training.name": "Eğitim", "wizard.step.training.desc": "Varsayılanlar vakaların %90'ı için iş görür. Sebebini biliyorsan elle ayarla.", "wizard.step.training.tutorial": "Eğitim parametreleri modelin NASIL öğreneceğini kontrol eder. QLoRA ağırlıkları 4-bit'e kuantize ederek bellek kullanımını azaltır. LoRA rank (r) adapter boyutudur — daha büyük = daha fazla kapasite ama daha yavaş. Epochs verinizi kaç kez döneceğimizi belirler. Learning rate adım boyunu kontrol eder. Batch size GPU belleğine bağlıdır.", "wizard.training.qlora.label": "QLoRA kullan (4-bit kuantizasyon)", "wizard.training.qlora.hint": "Base modeli 4-bit'e kuantize ederek daha az VRAM kullanır. Çoğu kurulum için önerilir.", + "wizard.training.qlora.detail": "QLoRA, base modeli her ağırlığı 4-bit'e kuantize edilmiş şekilde yükler (orijinal 16-bit'ten çok daha küçük temsil), sonra üstüne küçük bir adapter eğitir. Base ağırlıklar dondurulur, dolayısıyla tam hassasiyet gerektiren tek şey adapter'dır — 8B modelin 12 GB GPU'ya bu şekilde sığar. Sadece yüksek-VRAM GPU'n varsa ve maksimum kalite gerekiyorsa kapat.", "wizard.training.lora_r.label": "LoRA rank (r)", "wizard.training.lora_r.hint": "Adapter boyutu. 8 dengelidir; zor görevler için 16-32; çok veri varsa 64.", + "wizard.training.lora_r.detail": "LoRA, base modeli dondurur ve istediğin değişikliği yaklaştıran iki küçük matris eğitir. Rank (r) bu matrislerin boyutudur — daha büyük olması adapter'ın daha karmaşık değişiklikleri ifade edebilmesi anlamına gelir, ama eğitimi yavaşlatır ve daha çok bellek kullanır. Çoğu sohbet/talimat fine-tune'u için 8 yeterli; loss çok yüksekte plato çiziyorsa 16 veya 32'ye yükselt.", "wizard.training.lora_alpha.label":"LoRA alpha", "wizard.training.lora_alpha.hint": "Ölçek faktörü. Konvansiyon: alpha = 2 × r. Sihirbaz bunu otomatik takip eder.", + "wizard.training.lora_alpha.detail":"Alpha, adapter'ın base model üzerindeki etkisini çarpar. Topluluk konvansiyonu alpha = 2 × r — bu, rank'tan bağımsız olarak adapter'a dengeli bir etki verir. Çok yüksek alpha adapter'ı güçlendirir (daha hızlı öğrenme, daha çok kararsızlık riski); çok düşük olanı bastırır (base modele yakın, daha yavaş öğrenme).", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "Verinizi kaç kez döneceğiz. 3 dengeli bir varsayılandır.", + "wizard.training.epochs.detail": "Bir epoch, trainer'ın veri setindeki her örneği bir kez gördüğü anlamına gelir. Çok az epoch = model öğrenmez; çok fazla epoch = belirli örnekleri ezberlemeye başlar (overfitting). 3, çoğu fine-tune için sweet spot — sadece veri setin çok küçükse (<1K satır) veya sonda loss hala düşüyorsa artır.", "wizard.training.lr.label": "Learning rate", "wizard.training.lr.hint": "Ağırlık güncellemesi adım boyu. 1e-4 çoğu LoRA kurulumu için iş görür.", + "wizard.training.lr.detail": "Learning rate, optimizer'ın her batch'ten sonra ne kadar büyük adım atacağını kontrol eder. Çok yüksek = model overshoot eder ve hiç yakınsamaz; çok düşük = eğitim acı verici şekilde yavaş. LoRA, full fine-tuning'den daha büyük learning rate alabilir çünkü sadece adapter güncelleniyor — 1e-4 standart başlangıç noktasıdır.", "wizard.training.batch.label": "Batch size (cihaz başına)", "wizard.training.batch.hint": "Daha büyük batch'ler daha hızlı eğitir ama daha çok VRAM ister. Emin değilsen 2 ile başla.", + "wizard.training.batch.detail": "Batch size, GPU'nun paralel olarak işlediği örnek sayısıdır. Daha büyük batch'ler daha hızlı eğitim ve daha kararlı gradyan demek, ama daha fazla VRAM kullanır. 12 GB GPU'da QLoRA ile 8B modelde genelde batch=2 sığar; OOM alırsan 1'e düşür ve gradient accumulation'ı artır.", "wizard.step.compliance.title": "Değerlendirme & EU AI Act", "wizard.step.compliance.name": "Değerlendirme & uyumluluk", "wizard.step.compliance.desc": "Kalite kapıları ve regülasyon artefaktları.", @@ -1678,16 +1708,27 @@ "wizard.risk.unacceptable.callout.body": "Madde 5 bu kullanım sınıfını tamamen yasaklar. Sihirbaz yine de YAML üretecek, ancak bu seviyede bir sistemi dağıtmamalısın. Yeniden sınıflandır veya burada dur.", "wizard.eval.auto_revert.label": "Kalite regresyonunda auto-revert", "wizard.eval.auto_revert.hint": "Fine-tune yapılandırılan eşikten kötü sonuç verirse base modele geri dön.", + "wizard.eval.auto_revert.detail": "Eğitim bittikten sonra ForgeLM yeni modeli değerlendirir. Kalite eşiğin altına düştüyse (veya güvenlik fail oldu, ya da bir benchmark görevi gerile düştü), auto-revert fine-tune edilmiş modeli siler ve base'i geri yükler. Bu sert bir garantidir — yanlışlıkla daha kötü bir model yayınlayamazsın.", "wizard.eval.max_loss.label": "Kabul edilebilir maksimum eval loss", "wizard.eval.max_loss.hint": "Eğitim sonrası eval loss bunu aşarsa auto-revert tetiklenir. 2.0 dengeli bir varsayılandır.", + "wizard.eval.max_loss.detail": "Eval loss, modelin eğitime girmemiş örneklerle ne kadar şaşırdığını ölçer — düşük olması iyidir. 2.0 eşiği çoğu felaketi (örn. learning rate çok yüksek, model çöktü) yakalar, normal gürültüde false-positive vermez. Önceden ölçtüğün kararlı bir baseline'ın varsa daha sıkılaştır.", "wizard.eval.safety.label": "Llama Guard güvenlik değerlendirmesi", "wizard.eval.safety.hint": "Çıktıları 14 kategoride zararlı içerik açısından tarar. High-risk için zorunlu.", - "wizard.compliance.audit.label": "Append-only audit log", - "wizard.compliance.audit.hint": "Her eğitim olayının kurcalama-kanıtı JSONL günlüğü. High-risk için zorunlu.", + "wizard.eval.safety.detail": "Llama Guard, model çıktılarını 14 zarar kategorisine (şiddet, yasadışı eylemler, kendine zarar, cinsel içerik vs.) karşı puanlayan bir güvenlik sınıflandırıcısıdır. Eğitim sonrası ForgeLM saldırgan test prompt'ları örnekler ve fine-tune edilmiş modelin yanıtlarını kontrol eder. Çok fazla yanıt güvenlik eşiğini geçerse auto-revert tetiklenir. High-risk dağıtımlar için her zaman açık.", "wizard.compliance.approval.label": "Dağıtım öncesi insan onay kapısı", "wizard.compliance.approval.hint": "Model staging'den çıkmadan önce 'forgelm approve ' gerektirir. High-risk için zorunlu.", - "wizard.compliance.annex.label": "Annex IV teknik dokümantasyon export", - "wizard.compliance.annex.hint": "EU AI Act Annex IV paketini otomatik üretir (eğitim verisi, performans, izleme). High-risk için zorunlu.", + "wizard.compliance.approval.detail": "EU AI Act Madde 14, high-risk sistemler için anlamlı insan denetimi gerektirir. Bu kapı etkinleştirildiğinde eğitilmiş model bir staging dizinine düşer ve pipeline exit 4 verir — operatörler model card'ı, audit log'u ve benchmark sonuçlarını inceledikten sonra 'forgelm approve ' (veya 'reject') çalıştırmalıdır. Audit log kimin onayladığını, ne zaman ve hangi yorumu bıraktığını kaydeder.", + "wizard.compliance.always.title": "Her zaman yayınlanır, toggle yok:", + "wizard.compliance.always.body": "ForgeLM her koşu için YAML'dan bağımsız olarak kurcalama-kanıtı bir audit log (audit_log.jsonl) yazar — log, EU AI Act izlenebilirliğinin temelidir ve devre dışı bırakılamaz. Annex IV teknik dokümantasyon paketi, compliance.* metadata bloğu mevcutsa otomatik üretilir, dolayısıyla aşağıdaki alanları doldurmak yeterlidir.", + "wizard.compliance.provider_name.label": "Sağlayıcı adı", + "wizard.compliance.provider_name.hint": "Annex IV §1: sistemi sağlayan tüzel kişi.", + "wizard.compliance.provider_name.placeholder":"Acme AI Ltd.", + "wizard.compliance.system_name.label": "Sistem adı", + "wizard.compliance.system_name.hint": "Annex IV §1: bu AI sisteminin insan-okunur adı.", + "wizard.compliance.system_name.placeholder": "Müşteri Destek Asistanı v1", + "wizard.compliance.intended_purpose.label": "Amaçlanan kullanım", + "wizard.compliance.intended_purpose.hint": "Annex IV §1: sistemin amaçlanan kullanımının düz dilde açıklaması.", + "wizard.compliance.intended_purpose.placeholder":"Sohbet üzerinden müşteri destek talep önceliklendirme", "wizard.step.review.title": "Config'ini gözden geçir", "wizard.step.review.name": "Gözden geçir", "wizard.step.review.desc": "İşte YAML. Kopyala, indir ve çalıştır.", @@ -1696,7 +1737,20 @@ "wizard.review.download": ".yaml indir", "wizard.review.reset": "Sihirbazı sıfırla", "wizard.review.reset.confirm":"Tüm sihirbaz cevapları sıfırlansın ve baştan başlansın mı? YAML'ın kaybolacak.", - "wizard.review.run_label": "Sonra çalıştır:" + "wizard.review.run_label": "Sonra çalıştır:", + "quickstart.spotlight.eyebrow": "YAML'ı atla", + "quickstart.spotlight.title": "Tarayıcı içi sihirbazla config'ini 3 dakikada oluştur.", + "quickstart.spotlight.lead": "Kullanım senaryonu, base modelini, veri setini ve EU AI Act risk seviyesini seç. Sihirbaz, tek komutla çalıştırabileceğin bir quickstart-generated.yaml yazar — fine-tuning uzmanlığı gerekmez.", + "quickstart.spotlight.cta_primary": "Sihirbazı aç", + "quickstart.spotlight.cta_secondary":"Ya da 5 adımlık rehberi takip et", + "quickstart.spotlight.meta1": "7 rehberli adım", + "quickstart.spotlight.meta2": "Canlı YAML önizleme", + "quickstart.spotlight.meta3": "EU AI Act ön ayarları", + "quickstart.spotlight.stage1.label": "7 soruyu yanıtla", + "quickstart.spotlight.stage1.snippet":"kullanım · model · veri · risk", + "quickstart.spotlight.stage2.label": "Geçerli YAML al", + "quickstart.spotlight.stage3.label": "Çalıştır", + "quickstart.spotlight.manual_intro": "Ya da aşağıdaki manuel 5-adım referansını takip et" }); Object.assign(T.de, { @@ -1791,27 +1845,30 @@ "wizard.dataset.pdf.desc": "ForgeLM ingestiert sie vor dem Training zu JSONL (PII + Secrets maskiert).", "wizard.dataset.name.label": "Datensatzpfad oder -name", "wizard.dataset.name.hint": "HuggingFace-Name (org/dataset), lokaler Dateipfad (./data/train.jsonl) oder Verzeichnis (./documents/).", - "wizard.dataset.format.label":"Datenformat", - "wizard.dataset.format.prompt":"prompt + response (einzelner Turn)", - "wizard.dataset.format.messages":"messages (mehrstufiger Chat)", - "wizard.dataset.prompt_field":"Prompt-Feldname", - "wizard.dataset.response_field":"Response-Feldname", + "wizard.dataset.autodetect.title": "Format wird automatisch erkannt.", + "wizard.dataset.autodetect.body": "ForgeLM liest die JSONL-Felder direkt: Zeilen mit einem messages-Array werden zu mehrstufigem Chat; Zeilen mit prompt + response werden zu Anweisungs-Tuning. Gemischte Datensätze werden nicht unterstützt — der Loader wählt die Form aus der ersten Zeile.", "wizard.step.training.title": "Trainingsparameter", "wizard.step.training.name": "Training", "wizard.step.training.desc": "Standardwerte funktionieren in 90 % der Fälle. Nur tunen, wenn Sie wissen, warum.", "wizard.step.training.tutorial": "Trainingsparameter steuern, WIE das Modell lernt. QLoRA reduziert den Speicher durch 4-Bit-Quantisierung der Gewichte. LoRA-Rank (r) ist die Adaptergröße — größer bedeutet mehr Kapazität, aber langsamer. Epochs ist die Anzahl der Datendurchläufe. Lernrate steuert die Schrittgröße. Batch-Size hängt von Ihrem GPU-Speicher ab.", "wizard.training.qlora.label": "QLoRA verwenden (4-Bit-Quantisierung)", "wizard.training.qlora.hint": "Quantisiert das Basismodell auf 4-Bit, sodass es weniger VRAM benötigt. Für die meisten Setups empfohlen.", + "wizard.training.qlora.detail": "QLoRA lädt das Basismodell mit jedem Gewicht auf 4-Bit quantisiert (eine viel kleinere Repräsentation als die ursprünglichen 16-Bit) und trainiert darauf einen kleinen Adapter. Die Basisgewichte bleiben eingefroren, sodass nur der Adapter volle Präzision braucht — so passt ein 8B-Modell auf eine 12-GB-GPU. Nur deaktivieren, wenn Sie eine GPU mit viel VRAM und maximale Qualität brauchen.", "wizard.training.lora_r.label": "LoRA-Rank (r)", "wizard.training.lora_r.hint": "Adaptergröße. 8 ist ausgewogen; 16-32 für schwierigere Aufgaben; 64 nur bei sehr viel Daten.", + "wizard.training.lora_r.detail": "LoRA friert das Basismodell ein und trainiert zwei kleine Matrizen, die die gewünschte Änderung approximieren. Rank (r) ist die Größe dieser Matrizen — größer bedeutet, der Adapter kann komplexere Änderungen ausdrücken, braucht aber länger zum Trainieren und mehr Speicher. 8 reicht für die meisten Chat-/Anweisungs-Fine-Tunes; auf 16 oder 32 erhöhen, wenn Ihr Loss zu hoch plateauiert.", "wizard.training.lora_alpha.label":"LoRA-Alpha", "wizard.training.lora_alpha.hint": "Skalierungsfaktor. Konvention: alpha = 2 × r. Der Wizard verfolgt dies automatisch.", + "wizard.training.lora_alpha.detail":"Alpha multipliziert den Effekt des Adapters auf das Basismodell. Die Community-Konvention ist alpha = 2 × r — das gibt dem Adapter unabhängig vom Rank einen stabilen Einfluss. Viel höher verstärkt den Adapter (schnelleres Lernen, mehr Instabilitätsrisiko); viel niedriger dämpft ihn (näher am Basismodell, langsameres Lernen).", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "Wie oft die Daten durchlaufen werden. 3 ist ein ausgewogener Standard.", + "wizard.training.epochs.detail": "Eine Epoch bedeutet, der Trainer hat jedes Beispiel im Datensatz einmal gesehen. Zu wenige Epochs = Modell lernt nicht; zu viele = es beginnt, spezifische Beispiele auswendig zu lernen (Overfitting). 3 ist der Sweet Spot für die meisten Fine-Tunes — nur erhöhen, wenn Ihr Datensatz sehr klein ist (<1K Zeilen) oder Ihr Loss am Ende noch fällt.", "wizard.training.lr.label": "Lernrate", "wizard.training.lr.hint": "Schrittgröße für Gewichts-Updates. 1e-4 funktioniert für die meisten LoRA-Setups.", + "wizard.training.lr.detail": "Die Lernrate steuert, wie groß ein Schritt der Optimizer nach jedem Batch macht. Zu hoch und das Modell überschießt und konvergiert nie; zu niedrig und das Training ist quälend langsam. LoRA kann meist größere Lernraten als Full-Fine-Tuning vertragen, weil nur der Adapter aktualisiert wird — 1e-4 ist der Standard-Startpunkt.", "wizard.training.batch.label": "Batch-Size (pro Gerät)", "wizard.training.batch.hint": "Größere Batches trainieren schneller, brauchen aber mehr VRAM. Beginnen Sie mit 2, wenn Sie unsicher sind.", + "wizard.training.batch.detail": "Batch-Size ist, wie viele Beispiele die GPU parallel verarbeitet. Größere Batches bedeuten schnelleres Training und stabilere Gradienten, brauchen aber mehr VRAM. Mit QLoRA auf einer 12-GB-GPU passt meist Batch=2 mit einem 8B-Modell; bei OOM auf 1 reduzieren und Gradient Accumulation erhöhen.", "wizard.step.compliance.title": "Evaluierung & EU AI Act", "wizard.step.compliance.name": "Evaluierung & Compliance", "wizard.step.compliance.desc": "Qualitäts-Gates und regulatorische Artefakte.", @@ -1829,16 +1886,27 @@ "wizard.risk.unacceptable.callout.body": "Artikel 5 verbietet diese Verwendungsklasse vollständig. Der Wizard generiert trotzdem ein YAML, aber Sie sollten kein System dieser Klasse einsetzen. Reklassifizieren Sie oder hören Sie hier auf.", "wizard.eval.auto_revert.label": "Auto-Revert bei Qualitätsregression", "wizard.eval.auto_revert.hint": "Zurück zum Basismodell, wenn das Fine-Tune schlechter als die konfigurierte Schwelle abschneidet.", + "wizard.eval.auto_revert.detail": "Nach Trainingsende evaluiert ForgeLM das neue Modell. Ist die Qualität unter die Schwelle gefallen (oder Sicherheit fehlgeschlagen oder ein Benchmark-Task verschlechtert), löscht Auto-Revert das fine-getunte Modell und stellt das Basismodell wieder her. Das ist eine harte Garantie — Sie können kein schlechteres Modell versehentlich ausliefern.", "wizard.eval.max_loss.label": "Maximal akzeptabler Eval-Loss", "wizard.eval.max_loss.hint": "Wenn der Post-Training-Eval-Loss diesen Wert überschreitet, löst Auto-Revert aus. 2.0 ist ein ausgewogener Standard.", + "wizard.eval.max_loss.detail": "Der Eval-Loss misst, wie überrascht das Modell von ungesehenen Beispielen ist — niedriger ist besser. Die Schwelle 2.0 fängt die meisten katastrophalen Fehler ab (z.B. zu hohe Lernrate, Modell kollabiert), ohne false-positives bei normalem Rauschen. Nur strenger einstellen, wenn Sie eine stabile gemessene Baseline haben.", "wizard.eval.safety.label": "Llama-Guard-Sicherheitsbewertung", "wizard.eval.safety.hint": "Scannt Ausgaben in 14 Kategorien auf schädliche Inhalte. Für High-risk-Klassen erforderlich.", - "wizard.compliance.audit.label": "Append-only-Audit-Log", - "wizard.compliance.audit.hint": "Manipulationssicheres JSONL-Log jedes Trainings-Events. Für High-risk-Klassen erforderlich.", + "wizard.eval.safety.detail": "Llama Guard ist ein Sicherheitsklassifizierer, der Modellausgaben gegen 14 Schadenskategorien (Gewalt, illegale Handlungen, Selbstverletzung, sexueller Inhalt, etc.) bewertet. Nach dem Training sampelt ForgeLM adversariale Test-Prompts und prüft die Antworten des fine-getunten Modells. Schlüpfen zu viele am Sicherheitsschwellwert vorbei, löst Auto-Revert aus. Bei High-risk-Deployments immer aktiv.", "wizard.compliance.approval.label": "Human-Approval-Gate vor Deployment", "wizard.compliance.approval.hint": "Erfordert 'forgelm approve ', bevor das Modell das Staging verlässt. Für High-risk-Klassen erforderlich.", - "wizard.compliance.annex.label": "Annex-IV-Technische-Dokumentation-Export", - "wizard.compliance.annex.hint": "Generiert automatisch das EU-AI-Act-Annex-IV-Bündel (Trainingsdaten, Performance, Monitoring). Für High-risk-Klassen erforderlich.", + "wizard.compliance.approval.detail": "Artikel 14 des EU AI Act erfordert sinnvolle menschliche Aufsicht für High-risk-Systeme. Mit aktiviertem Gate landet das trainierte Modell in einem Staging-Verzeichnis und die Pipeline beendet mit Code 4 — Operatoren müssen 'forgelm approve ' (oder 'reject') ausführen, nachdem sie Modellkarte, Audit-Log und Benchmark-Ergebnisse geprüft haben. Das Audit-Log dokumentiert wer, wann und mit welchem Kommentar genehmigt hat.", + "wizard.compliance.always.title": "Immer ausgegeben, kein Toggle:", + "wizard.compliance.always.body": "ForgeLM schreibt für jeden Lauf ein manipulationssicheres Audit-Log (audit_log.jsonl) unabhängig vom YAML — das Log ist die Grundlage der EU-AI-Act-Nachverfolgbarkeit und kann nicht abgewählt werden. Das Annex-IV-Technische-Dokumentations-Bündel wird automatisch generiert, sobald der compliance.*-Metadatenblock vorhanden ist; das Ausfüllen der Felder unten erzeugt es bereits.", + "wizard.compliance.provider_name.label": "Anbietername", + "wizard.compliance.provider_name.hint": "Annex IV §1: Rechtsperson, die das System bereitstellt.", + "wizard.compliance.provider_name.placeholder":"Acme AI GmbH", + "wizard.compliance.system_name.label": "Systemname", + "wizard.compliance.system_name.hint": "Annex IV §1: menschenlesbarer Name dieses KI-Systems.", + "wizard.compliance.system_name.placeholder": "Kundensupport-Assistent v1", + "wizard.compliance.intended_purpose.label": "Vorgesehener Zweck", + "wizard.compliance.intended_purpose.hint": "Annex IV §1: erklärter Zweck des Systems in einfacher Sprache.", + "wizard.compliance.intended_purpose.placeholder":"Kundensupport-Tickets per Chat priorisieren", "wizard.step.review.title": "Konfiguration prüfen", "wizard.step.review.name": "Prüfen", "wizard.step.review.desc": "Hier ist das YAML. Kopieren, herunterladen und ausführen.", @@ -1847,7 +1915,20 @@ "wizard.review.download": ".yaml herunterladen", "wizard.review.reset": "Wizard zurücksetzen", "wizard.review.reset.confirm":"Alle Wizard-Antworten zurücksetzen und neu starten? Ihr YAML geht verloren.", - "wizard.review.run_label": "Dann ausführen:" + "wizard.review.run_label": "Dann ausführen:", + "quickstart.spotlight.eyebrow": "Überspringen Sie das YAML", + "quickstart.spotlight.title": "Verwenden Sie den In-Browser-Wizard, um Ihre Konfiguration in 3 Minuten zu erstellen.", + "quickstart.spotlight.lead": "Wählen Sie Anwendungsfall, Basismodell, Datensatz und EU-AI-Act-Risikostufe. Der Wizard schreibt eine funktionierende quickstart-generated.yaml, die Sie mit einem Befehl ausführen können — keine Fine-Tuning-Expertise nötig.", + "quickstart.spotlight.cta_primary": "Wizard öffnen", + "quickstart.spotlight.cta_secondary":"Oder folgen Sie der 5-Schritt-Anleitung", + "quickstart.spotlight.meta1": "7 geführte Schritte", + "quickstart.spotlight.meta2": "Live-YAML-Vorschau", + "quickstart.spotlight.meta3": "EU-AI-Act-Voreinstellungen", + "quickstart.spotlight.stage1.label": "7 Fragen beantworten", + "quickstart.spotlight.stage1.snippet":"Anwendungsfall · Modell · Daten · Risiko", + "quickstart.spotlight.stage2.label": "Gültiges YAML erhalten", + "quickstart.spotlight.stage3.label": "Ausführen", + "quickstart.spotlight.manual_intro": "Oder folgen Sie der manuellen 5-Schritt-Referenz unten" }); Object.assign(T.fr, { @@ -1942,27 +2023,30 @@ "wizard.dataset.pdf.desc": "ForgeLM les ingérera en JSONL avant l'entraînement (PII + secrets masqués).", "wizard.dataset.name.label": "Chemin ou nom du dataset", "wizard.dataset.name.hint": "Nom HuggingFace (org/dataset), chemin de fichier local (./data/train.jsonl) ou répertoire (./documents/).", - "wizard.dataset.format.label":"Format des données", - "wizard.dataset.format.prompt":"prompt + response (un seul tour)", - "wizard.dataset.format.messages":"messages (chat multi-tours)", - "wizard.dataset.prompt_field":"Nom du champ prompt", - "wizard.dataset.response_field":"Nom du champ response", + "wizard.dataset.autodetect.title": "Le format est auto-détecté.", + "wizard.dataset.autodetect.body": "ForgeLM lit directement les champs JSONL : les lignes avec un tableau messages deviennent du chat multi-tours ; les lignes avec prompt + response deviennent du suivi d'instructions. Les datasets mixtes ne sont pas pris en charge — le loader choisit la forme à partir de la première ligne.", "wizard.step.training.title": "Paramètres d'entraînement", "wizard.step.training.name": "Entraînement", "wizard.step.training.desc": "Les valeurs par défaut fonctionnent dans 90 % des cas. Ne tunez que si vous savez pourquoi.", "wizard.step.training.tutorial": "Les paramètres d'entraînement contrôlent COMMENT le modèle apprend. QLoRA réduit la mémoire en quantifiant les poids en 4 bits. Le rang LoRA (r) est la taille de l'adapter — plus grand = plus de capacité mais plus lent. Epochs est le nombre de boucles dans vos données. Le learning rate contrôle la taille du pas. La taille de batch dépend de la mémoire GPU.", "wizard.training.qlora.label": "Utiliser QLoRA (quantification 4 bits)", "wizard.training.qlora.hint": "Quantifie le modèle de base en 4 bits pour utiliser moins de VRAM. Recommandé pour la plupart des configurations.", + "wizard.training.qlora.detail": "QLoRA charge le modèle de base avec chaque poids quantifié à 4 bits (une représentation beaucoup plus petite que les 16 bits originaux), puis entraîne un petit adapter par-dessus. Les poids de base restent gelés, seul l'adapter a besoin de précision complète — c'est ainsi qu'un modèle 8B tient sur un GPU 12 Go. Désactivez seulement si vous avez un GPU à fort VRAM et besoin d'une qualité maximale.", "wizard.training.lora_r.label": "Rang LoRA (r)", "wizard.training.lora_r.hint": "Taille de l'adapter. 8 est équilibré ; 16-32 pour des tâches plus dures ; 64 si vous avez beaucoup de données.", + "wizard.training.lora_r.detail": "LoRA gèle le modèle de base et entraîne deux petites matrices qui approximent le changement souhaité. Le rang (r) est la taille de ces matrices — plus grand signifie que l'adapter peut exprimer des changements plus complexes mais l'entraînement est plus lent et utilise plus de mémoire. 8 suffit pour la plupart des fine-tunes chat/instructions ; passer à 16 ou 32 seulement si votre loss plafonne trop haut.", "wizard.training.lora_alpha.label":"LoRA alpha", "wizard.training.lora_alpha.hint": "Facteur d'échelle. Convention : alpha = 2 × r. L'assistant suit cela automatiquement.", + "wizard.training.lora_alpha.detail":"Alpha multiplie l'effet de l'adapter sur le modèle de base. La convention de la communauté est alpha = 2 × r — cela donne à l'adapter une influence stable indépendamment du rang. Beaucoup plus haut amplifie l'adapter (apprentissage plus rapide, plus de risque d'instabilité) ; beaucoup plus bas l'atténue (proche du modèle de base, apprentissage plus lent).", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "Combien de fois boucler sur vos données. 3 est un défaut équilibré.", + "wizard.training.epochs.detail": "Une epoch signifie que le trainer a vu chaque exemple du dataset une fois. Trop peu d'epochs = le modèle n'apprend pas ; trop d'epochs = il commence à mémoriser des exemples spécifiques (overfitting). 3 est l'équilibre pour la plupart des fine-tunes — n'augmentez que si votre dataset est très petit (<1K lignes) ou votre loss diminue encore à la fin.", "wizard.training.lr.label": "Learning rate", "wizard.training.lr.hint": "Taille de pas pour les mises à jour de poids. 1e-4 fonctionne pour la plupart des configurations LoRA.", + "wizard.training.lr.detail": "Le learning rate contrôle la taille du pas que l'optimizer fait après chaque batch. Trop haut et le modèle dépasse et ne converge jamais ; trop bas et l'entraînement est douloureusement lent. LoRA peut généralement supporter des learning rates plus grands que le full fine-tuning car seul l'adapter est mis à jour — 1e-4 est le point de départ standard.", "wizard.training.batch.label": "Batch size (par device)", "wizard.training.batch.hint": "Des batches plus grands entraînent plus vite mais demandent plus de VRAM. Commencez par 2 si vous n'êtes pas sûr.", + "wizard.training.batch.detail": "La taille de batch est combien d'exemples le GPU traite en parallèle. Des batches plus grands = entraînement plus rapide et gradients plus stables, mais utilisent plus de VRAM. Avec QLoRA sur un GPU 12 Go, vous pouvez généralement faire tenir batch=2 avec un modèle 8B ; en cas d'OOM, descendez à 1 et augmentez l'accumulation de gradient.", "wizard.step.compliance.title": "Évaluation & EU AI Act", "wizard.step.compliance.name": "Évaluation & conformité", "wizard.step.compliance.desc": "Gates qualité et artefacts réglementaires.", @@ -1980,16 +2064,27 @@ "wizard.risk.unacceptable.callout.body": "L'Article 5 interdit entièrement cette classe d'usage. L'assistant générera quand même un YAML, mais vous ne devriez pas déployer un système dans ce niveau. Reclassifiez ou arrêtez ici.", "wizard.eval.auto_revert.label": "Auto-revert sur régression de qualité", "wizard.eval.auto_revert.hint": "Revenir au modèle de base si le fine-tune obtient un score pire que le seuil configuré.", + "wizard.eval.auto_revert.detail": "Après la fin de l'entraînement, ForgeLM évalue le nouveau modèle. Si la qualité tombe sous le seuil (ou la sécurité a échoué, ou un benchmark a régressé), l'auto-revert supprime le modèle fine-tuné et restaure le modèle de base. C'est une garantie dure — vous ne pouvez pas livrer accidentellement un modèle pire.", "wizard.eval.max_loss.label": "Eval loss maximum acceptable", "wizard.eval.max_loss.hint": "Si l'eval loss post-entraînement dépasse cela, l'auto-revert se déclenche. 2.0 est un défaut équilibré.", + "wizard.eval.max_loss.detail": "L'eval loss mesure à quel point le modèle est surpris par des exemples non vus — plus bas est mieux. Le seuil 2.0 attrape la plupart des échecs catastrophiques (par ex. learning rate trop haut, modèle effondré) sans faux-positifs sur le bruit normal. Resserrez seulement si vous avez une baseline stable que vous avez mesurée.", "wizard.eval.safety.label": "Évaluation sécurité Llama Guard", "wizard.eval.safety.hint": "Scanne les sorties pour contenu nuisible sur 14 catégories. Requis pour les niveaux high-risk.", - "wizard.compliance.audit.label": "Audit log append-only", - "wizard.compliance.audit.hint": "Log JSONL inviolable de chaque événement d'entraînement. Requis pour les niveaux high-risk.", + "wizard.eval.safety.detail": "Llama Guard est un classifieur de sécurité qui note les sorties du modèle contre 14 catégories de nuisance (violence, actes illégaux, automutilation, contenu sexuel, etc.). Après l'entraînement, ForgeLM échantillonne des prompts de test adversariaux et vérifie les réponses du modèle fine-tuné. Si trop passent le seuil de sécurité, l'auto-revert se déclenche. Toujours actif pour les déploiements high-risk.", "wizard.compliance.approval.label": "Gate d'approbation humaine avant déploiement", "wizard.compliance.approval.hint": "Requiert 'forgelm approve ' avant que le modèle quitte le staging. Requis pour les niveaux high-risk.", - "wizard.compliance.annex.label": "Export documentation technique Annex IV", - "wizard.compliance.annex.hint": "Génère automatiquement le bundle Annex IV de l'EU AI Act (données d'entraînement, performance, monitoring). Requis pour les niveaux high-risk.", + "wizard.compliance.approval.detail": "L'Article 14 de l'EU AI Act exige une supervision humaine significative pour les systèmes high-risk. Avec ce gate activé, le modèle entraîné atterrit dans un répertoire staging et la pipeline sort avec le code 4 — les opérateurs doivent exécuter 'forgelm approve ' (ou 'reject') après avoir examiné la fiche modèle, l'audit log et les résultats de benchmark. L'audit log enregistre qui a approuvé, quand, et avec quel commentaire.", + "wizard.compliance.always.title": "Toujours émis, sans toggle :", + "wizard.compliance.always.body": "ForgeLM écrit un audit log inviolable (audit_log.jsonl) pour chaque exécution indépendamment du YAML — le log est la fondation de la traçabilité EU AI Act et ne peut pas être désactivé. Le bundle de documentation technique Annex IV est généré automatiquement quand le bloc compliance.* est présent ; remplir simplement les champs ci-dessous le produit.", + "wizard.compliance.provider_name.label": "Nom du fournisseur", + "wizard.compliance.provider_name.hint": "Annex IV §1 : entité juridique fournissant le système.", + "wizard.compliance.provider_name.placeholder":"Acme AI SAS", + "wizard.compliance.system_name.label": "Nom du système", + "wizard.compliance.system_name.hint": "Annex IV §1 : nom lisible de ce système IA.", + "wizard.compliance.system_name.placeholder": "Assistant Support Client v1", + "wizard.compliance.intended_purpose.label": "Objectif visé", + "wizard.compliance.intended_purpose.hint": "Annex IV §1 : objectif déclaré du système en langage simple.", + "wizard.compliance.intended_purpose.placeholder":"Trier les tickets de support client par chat", "wizard.step.review.title": "Revoir votre configuration", "wizard.step.review.name": "Revoir", "wizard.step.review.desc": "Voici le YAML. Copiez-le, téléchargez-le et exécutez.", @@ -1998,7 +2093,20 @@ "wizard.review.download": "Télécharger .yaml", "wizard.review.reset": "Réinitialiser l'assistant", "wizard.review.reset.confirm":"Réinitialiser toutes les réponses de l'assistant et recommencer ? Votre YAML sera perdu.", - "wizard.review.run_label": "Puis exécutez :" + "wizard.review.run_label": "Puis exécutez :", + "quickstart.spotlight.eyebrow": "Configurateur guidé", + "quickstart.spotlight.title": "Pas envie d'écrire le YAML à la main ?", + "quickstart.spotlight.lead": "L'assistant intégré au navigateur pose sept questions, écrit un YAML valide et vous laisse l'exécuter en une commande. Aucune installation, votre progression est enregistrée localement.", + "quickstart.spotlight.cta_primary": "Lancer l'assistant", + "quickstart.spotlight.cta_secondary":"Suivre les cinq étapes", + "quickstart.spotlight.meta1": "≈ 3 minutes", + "quickstart.spotlight.meta2": "6 langues", + "quickstart.spotlight.meta3": "Aucune installation requise", + "quickstart.spotlight.stage1.label": "Répondez à 7 questions", + "quickstart.spotlight.stage1.snippet": "cas d'usage · modèle · données · LoRA · risque", + "quickstart.spotlight.stage2.label": "Recevez un YAML valide", + "quickstart.spotlight.stage3.label": "Exécutez-le", + "quickstart.spotlight.manual_intro": "Ou parcourez les cinq étapes manuellement ↓" }); Object.assign(T.es, { @@ -2093,27 +2201,30 @@ "wizard.dataset.pdf.desc": "ForgeLM los ingerirá a JSONL antes del entrenamiento (PII + secretos enmascarados).", "wizard.dataset.name.label": "Ruta o nombre del dataset", "wizard.dataset.name.hint": "Nombre HuggingFace (org/dataset), ruta de archivo local (./data/train.jsonl) o directorio (./documents/).", - "wizard.dataset.format.label":"Formato de datos", - "wizard.dataset.format.prompt":"prompt + response (un solo turno)", - "wizard.dataset.format.messages":"messages (chat multi-turno)", - "wizard.dataset.prompt_field":"Nombre del campo prompt", - "wizard.dataset.response_field":"Nombre del campo response", + "wizard.dataset.autodetect.title": "Detección automática de la forma del dataset", + "wizard.dataset.autodetect.body": "ForgeLM detecta automáticamente la estructura de las filas de su dataset. Las filas con { \"messages\": [...] } se tratan como chat multi-turno. Las filas con { \"prompt\": ..., \"response\": ... } se tratan como pares instrucción → respuesta. No tiene que declarar nada — el trainer lo descubre.", "wizard.step.training.title": "Parámetros de entrenamiento", "wizard.step.training.name": "Entrenamiento", "wizard.step.training.desc": "Los valores por defecto funcionan en el 90 % de los casos. Ajuste solo si sabe por qué.", "wizard.step.training.tutorial": "Los parámetros de entrenamiento controlan CÓMO aprende el modelo. QLoRA reduce memoria cuantizando los pesos a 4 bits. El rango LoRA (r) es el tamaño del adapter — mayor significa más capacidad pero más lento. Epochs es cuántas veces recorreremos sus datos. La tasa de aprendizaje controla el tamaño del paso. El tamaño de batch depende de la memoria de su GPU.", "wizard.training.qlora.label": "Usar QLoRA (cuantización 4 bits)", "wizard.training.qlora.hint": "Cuantiza el modelo base a 4 bits para usar menos VRAM. Recomendado para la mayoría de configuraciones.", + "wizard.training.qlora.detail": "QLoRA mantiene los pesos del modelo base congelados a precisión de 4 bits y solo entrena un adaptador LoRA pequeño encima. La memoria cae 3-4× respecto a un fine-tune full-precision. Compensación: levemente más lento por paso, pero hace caber un modelo de 8B en una sola GPU de 12 GB. Déjelo activado a menos que tenga ≥ 24 GB de VRAM y persiga la última décima de calidad.", "wizard.training.lora_r.label": "Rango LoRA (r)", "wizard.training.lora_r.hint": "Tamaño del adapter. 8 es equilibrado; 16-32 para tareas más difíciles; 64 solo si tiene muchos datos.", + "wizard.training.lora_r.detail": "El rango LoRA (r) controla cuánta capacidad nueva añade el adaptador. Más alto = más cosas puede aprender el modelo, pero también más memoria y más riesgo de sobreajuste con datos pequeños. r=8 es seguro para la mayoría de tareas. Suba a 16-32 si fine-tunea sobre un dominio distinto (médico, legal, código). Solo r=64 si tiene > 50K ejemplos.", "wizard.training.lora_alpha.label":"LoRA alpha", "wizard.training.lora_alpha.hint": "Factor de escala. Convención: alpha = 2 × r. El asistente lo sigue automáticamente.", + "wizard.training.lora_alpha.detail":"LoRA alpha escala cuánto influye el adaptador en las salidas del modelo. La fuerza efectiva es alpha / r. La convención de la comunidad es alpha = 2 × r — el asistente lo mantiene en sincronía si edita r. Solo desvíese si está reproduciendo un paper específico que use otra proporción.", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "Cuántas veces recorrer sus datos. 3 es un valor por defecto equilibrado.", + "wizard.training.epochs.detail": "Una epoch = una pasada completa por sus datos de entrenamiento. Demasiado pocas y el modelo apenas aprende; demasiadas y se sobreajusta y memoriza. 3 funciona bien para la mayoría de datasets de instrucciones. Suba a 5-10 si tiene < 1K ejemplos. Use auto-revert (siguiente paso) para detectar la regresión por sobreajuste.", "wizard.training.lr.label": "Tasa de aprendizaje", "wizard.training.lr.hint": "Tamaño del paso para actualizaciones de pesos. 1e-4 funciona para la mayoría de configuraciones LoRA.", + "wizard.training.lr.detail": "La tasa de aprendizaje es lo grande que es cada paso de actualización de pesos. Demasiado alta y la pérdida diverge (NaN); demasiado baja y el entrenamiento estanca. 1e-4 (= 0.0001) es estándar para fine-tuning LoRA. SFT full-precision normalmente usa 2e-5. Ajuste solo si la pérdida no baja después de 1 epoch.", "wizard.training.batch.label": "Tamaño de batch (por dispositivo)", "wizard.training.batch.hint": "Batches más grandes entrenan más rápido pero necesitan más VRAM. Empiece con 2 si no está seguro.", + "wizard.training.batch.detail": "Tamaño de batch = cuántos ejemplos procesa la GPU a la vez. Más grande = entrenamiento más rápido, mejor uso de la GPU, pero VRAM linealmente más alta. Empiece con 2 en una GPU de 12 GB con QLoRA; suba a 4-8 si tiene 24 GB+. Si choca con OOM, baje el batch antes de bajar el rango LoRA.", "wizard.step.compliance.title": "Evaluación & EU AI Act", "wizard.step.compliance.name": "Evaluación & cumplimiento", "wizard.step.compliance.desc": "Puertas de calidad y artefactos regulatorios.", @@ -2131,16 +2242,27 @@ "wizard.risk.unacceptable.callout.body": "El Artículo 5 prohíbe completamente esta clase de uso. El asistente seguirá generando un YAML, pero no debe desplegar un sistema en este nivel. Reclasifique o pare aquí.", "wizard.eval.auto_revert.label": "Auto-revert en regresión de calidad", "wizard.eval.auto_revert.hint": "Volver al modelo base si el fine-tune obtiene peor puntuación que el umbral configurado.", + "wizard.eval.auto_revert.detail": "ForgeLM evalúa el modelo fine-tuned contra el base sobre un split held-out. Si el fine-tune retrocede más allá del umbral configurado, el toolkit elimina el adaptador y conserva el modelo base — protegiendo la producción de un fine-tune empeorado. Casi todo despliegue serio debería dejarlo activado.", "wizard.eval.max_loss.label": "Máximo eval loss aceptable", "wizard.eval.max_loss.hint": "Si el eval loss post-entrenamiento excede esto, se activa el auto-revert. 2.0 es un valor por defecto equilibrado.", + "wizard.eval.max_loss.detail": "Eval loss = lo bien que el modelo predice texto held-out. Más bajo es mejor; 2.0 es un techo razonable para la mayoría de datasets de instrucciones. Si su loss empieza típicamente alrededor de 1.5, baje el umbral a 1.8. Si entrena sobre un dominio raro donde el loss base ya es 2.5, súbalo en consecuencia.", "wizard.eval.safety.label": "Evaluación de seguridad Llama Guard", "wizard.eval.safety.hint": "Escanea las salidas en busca de contenido dañino en 14 categorías. Requerido para niveles high-risk.", - "wizard.compliance.audit.label": "Audit log append-only", - "wizard.compliance.audit.hint": "Log JSONL a prueba de manipulaciones de cada evento de entrenamiento. Requerido para niveles high-risk.", + "wizard.eval.safety.detail": "Llama Guard 3 es un clasificador de seguridad que puntúa salidas en 14 categorías de daño (violencia, autolesión, contenido sexual, malware, …). ForgeLM lo ejecuta sobre un set de evaluación y bloquea el despliegue si la tasa de violación es demasiado alta. Requerido por el EU AI Act para sistemas high-risk; recomendado para chatbots de cara al usuario en cualquier nivel.", + "wizard.compliance.always.title": "Audit log y bundle Annex IV: siempre incluidos", + "wizard.compliance.always.body": "ForgeLM siempre escribe un audit log append-only (audit_log.jsonl) — sin toggle, sin opt-out. El bundle Annex IV se genera automáticamente a partir de los metadatos compliance.* que rellena abajo. No tiene que pensar en ello en el YAML.", "wizard.compliance.approval.label": "Puerta de aprobación humana antes del despliegue", "wizard.compliance.approval.hint": "Requiere 'forgelm approve ' antes de que el modelo salga del staging. Requerido para niveles high-risk.", - "wizard.compliance.annex.label": "Export de documentación técnica Annex IV", - "wizard.compliance.annex.hint": "Genera automáticamente el bundle Annex IV del EU AI Act (datos de entrenamiento, rendimiento, monitoreo). Requerido para niveles high-risk.", + "wizard.compliance.approval.detail": "EU AI Act Artículo 14: la supervisión humana es obligatoria para sistemas high-risk. Con esto activado, ForgeLM produce el modelo fine-tuned pero lo retiene en staging hasta que un revisor humano ejecute 'forgelm approve '. Para casos de uso minimal-risk es opcional pero buena higiene.", + "wizard.compliance.provider_name.label": "Nombre del proveedor (entidad)", + "wizard.compliance.provider_name.hint": "Annex IV §1: entidad jurídica que provee el sistema.", + "wizard.compliance.provider_name.placeholder":"Acme AI S.L.", + "wizard.compliance.system_name.label": "Nombre del sistema", + "wizard.compliance.system_name.hint": "Annex IV §1: nombre legible humano de este sistema de IA.", + "wizard.compliance.system_name.placeholder": "Asistente de Soporte al Cliente v1", + "wizard.compliance.intended_purpose.label": "Propósito previsto", + "wizard.compliance.intended_purpose.hint": "Annex IV §1: objetivo declarado del sistema en lenguaje claro.", + "wizard.compliance.intended_purpose.placeholder":"Triar tickets de soporte al cliente vía chat", "wizard.step.review.title": "Revise su configuración", "wizard.step.review.name": "Revisar", "wizard.step.review.desc": "Aquí está el YAML. Cópielo, descárguelo y ejecútelo.", @@ -2149,7 +2271,20 @@ "wizard.review.download": "Descargar .yaml", "wizard.review.reset": "Reiniciar asistente", "wizard.review.reset.confirm":"¿Reiniciar todas las respuestas del asistente y empezar de nuevo? Su YAML se perderá.", - "wizard.review.run_label": "Luego ejecute:" + "wizard.review.run_label": "Luego ejecute:", + "quickstart.spotlight.eyebrow": "Configurador guiado", + "quickstart.spotlight.title": "¿Prefiere no escribir el YAML a mano?", + "quickstart.spotlight.lead": "El asistente integrado en el navegador hace siete preguntas, escribe un YAML válido y le deja ejecutarlo con un solo comando. Sin instalación, su progreso queda guardado localmente.", + "quickstart.spotlight.cta_primary": "Lanzar el asistente", + "quickstart.spotlight.cta_secondary":"Recorrer los cinco pasos", + "quickstart.spotlight.meta1": "≈ 3 minutos", + "quickstart.spotlight.meta2": "6 idiomas", + "quickstart.spotlight.meta3": "Sin instalación", + "quickstart.spotlight.stage1.label": "Responda 7 preguntas", + "quickstart.spotlight.stage1.snippet": "caso de uso · modelo · datos · LoRA · riesgo", + "quickstart.spotlight.stage2.label": "Obtenga un YAML válido", + "quickstart.spotlight.stage3.label": "Ejecútelo", + "quickstart.spotlight.manual_intro": "O recorra los cinco pasos manualmente ↓" }); Object.assign(T.zh, { @@ -2244,27 +2379,30 @@ "wizard.dataset.pdf.desc": "ForgeLM 将在训练前 ingest 它们到 JSONL(PII + secrets 已屏蔽)。", "wizard.dataset.name.label": "数据集路径或名称", "wizard.dataset.name.hint": "HuggingFace 名称(org/dataset)、本地文件路径(./data/train.jsonl)或目录(./documents/)。", - "wizard.dataset.format.label":"数据格式", - "wizard.dataset.format.prompt":"prompt + response(单轮)", - "wizard.dataset.format.messages":"messages(多轮聊天)", - "wizard.dataset.prompt_field":"Prompt 字段名", - "wizard.dataset.response_field":"Response 字段名", + "wizard.dataset.autodetect.title": "自动检测数据集形态", + "wizard.dataset.autodetect.body": "ForgeLM 自动检测数据集行的结构。包含 { \"messages\": [...] } 的行被视为多轮聊天。包含 { \"prompt\": ..., \"response\": ... } 的行被视为指令 → 响应对。你不需要声明任何东西 — trainer 会自己识别。", "wizard.step.training.title": "训练参数", "wizard.step.training.name": "训练", "wizard.step.training.desc": "默认值在 90% 的情况下能工作。仅在你知道为什么时调整。", "wizard.step.training.tutorial": "训练参数控制模型如何学习。QLoRA 通过将权重量化为 4 位来减少内存。LoRA rank(r)是 adapter 大小 — 更大意味着更多容量但更慢。Epochs 是我们循环数据的次数。学习率控制步长。批量大小取决于你的 GPU 内存。", "wizard.training.qlora.label": "使用 QLoRA(4 位量化)", "wizard.training.qlora.hint": "将基础模型量化为 4 位以使用更少的 VRAM。推荐用于大多数配置。", + "wizard.training.qlora.detail": "QLoRA 将基础模型权重冻结在 4 位精度,只训练上面的小型 LoRA adapter。相比全精度微调内存下降 3-4 倍。代价:每步略慢,但能让 8B 模型装进单张 12 GB GPU。除非你有 ≥ 24 GB VRAM 并追求最后一点质量,否则保持开启。", "wizard.training.lora_r.label": "LoRA rank (r)", "wizard.training.lora_r.hint": "Adapter 大小。8 是平衡的;16-32 用于更难的任务;64 仅在数据非常多时使用。", + "wizard.training.lora_r.detail": "LoRA rank(r)控制 adapter 增加多少新容量。越高 = 模型能学到更多内容,但内存也更多,小数据集上过拟合风险也更高。r=8 对大多数任务都安全。如果你在差异很大的领域(医疗、法律、代码)上微调,提高到 16-32。仅当你有 > 50K 样本时使用 r=64。", "wizard.training.lora_alpha.label":"LoRA alpha", "wizard.training.lora_alpha.hint": "缩放因子。约定:alpha = 2 × r。向导会自动跟踪这一点。", + "wizard.training.lora_alpha.detail":"LoRA alpha 缩放 adapter 对模型输出的影响程度。有效强度为 alpha / r。社区约定为 alpha = 2 × r — 你编辑 r 时向导会保持同步。仅在复现使用不同比例的特定论文时偏离。", "wizard.training.epochs.label": "Epochs", "wizard.training.epochs.hint": "循环数据的次数。3 是平衡的默认值。", + "wizard.training.epochs.detail": "一个 epoch = 在你的训练数据上完整跑一遍。太少模型几乎学不到东西;太多则会过拟合记住数据。3 对大多数指令数据集都行得通。如果数据 < 1K 样本提到 5-10。使用 auto-revert(下一步)捕捉过拟合回归。", "wizard.training.lr.label": "学习率", "wizard.training.lr.hint": "权重更新的步长。1e-4 适用于大多数 LoRA 配置。", + "wizard.training.lr.detail": "学习率即每次权重更新步长有多大。太高 loss 发散(NaN);太低训练停滞。1e-4(= 0.0001)是 LoRA 微调的标准值。全精度 SFT 通常用 2e-5。仅当 1 个 epoch 后 loss 没下降时调整。", "wizard.training.batch.label": "批量大小(每设备)", "wizard.training.batch.hint": "更大的批量训练更快但需要更多 VRAM。如果不确定,从 2 开始。", + "wizard.training.batch.detail": "批量大小 = GPU 一次处理的样本数。越大 = 训练越快、GPU 利用率越好,但 VRAM 线性增加。在 12 GB GPU 上用 QLoRA 从 2 开始;有 24 GB+ 提到 4-8。如果遇到 OOM,先降批量再降 LoRA rank。", "wizard.step.compliance.title": "评估 & EU AI Act", "wizard.step.compliance.name": "评估 & 合规", "wizard.step.compliance.desc": "质量 gate 和监管 artefact。", @@ -2286,12 +2424,23 @@ "wizard.eval.max_loss.hint": "如果训练后 eval loss 超过此值,则触发 auto-revert。2.0 是平衡的默认值。", "wizard.eval.safety.label": "Llama Guard 安全评估", "wizard.eval.safety.hint": "在 14 个类别中扫描输出的有害内容。high-risk 等级必需。", - "wizard.compliance.audit.label": "仅追加审计日志", - "wizard.compliance.audit.hint": "每个训练事件的防篡改 JSONL 日志。high-risk 等级必需。", + "wizard.eval.safety.detail": "Llama Guard 3 是一个安全分类器,在 14 个伤害类别(暴力、自残、性内容、恶意软件等)上对输出打分。ForgeLM 在评估集上运行它,如果违规率过高就阻止部署。EU AI Act 对 high-risk 系统要求;任何等级的面向用户聊天机器人都推荐使用。", + "wizard.eval.auto_revert.detail": "ForgeLM 在 held-out split 上将微调模型与基础模型对比评估。如果微调超出配置阈值出现回归,工具包会丢弃 adapter 并保留基础模型 — 保护生产环境免受变差的微调影响。几乎每个严肃的部署都应该保持开启。", + "wizard.eval.max_loss.detail": "Eval loss = 模型预测 held-out 文本的能力。越低越好;2.0 对大多数指令数据集是合理上限。如果你的 loss 通常在 1.5 左右开始,把阈值降到 1.8。如果你训练在基础 loss 已经是 2.5 的稀有领域上,就相应提高它。", + "wizard.compliance.always.title": "审计日志和 Annex IV 资料包:始终包含", + "wizard.compliance.always.body": "ForgeLM 始终写入仅追加审计日志(audit_log.jsonl) — 没有开关、没有 opt-out。Annex IV 资料包从你下方填写的 compliance.* 元数据自动生成。在 YAML 里你不必考虑它。", "wizard.compliance.approval.label": "部署前的人工批准 gate", "wizard.compliance.approval.hint": "在模型离开 staging 之前需要 'forgelm approve '。high-risk 等级必需。", - "wizard.compliance.annex.label": "Annex IV 技术文档导出", - "wizard.compliance.annex.hint": "自动生成 EU AI Act Annex IV 资料包(训练数据、性能、监控)。high-risk 等级必需。", + "wizard.compliance.approval.detail": "EU AI Act 第 14 条:high-risk 系统强制要求人工监督。开启时,ForgeLM 会产生微调模型但留在 staging 中,直到人工审查者运行 'forgelm approve '。对于 minimal-risk 用例是可选的,但属于良好实践。", + "wizard.compliance.provider_name.label": "供应商名称(实体)", + "wizard.compliance.provider_name.hint": "Annex IV §1:提供该系统的法律实体。", + "wizard.compliance.provider_name.placeholder":"Acme AI 有限公司", + "wizard.compliance.system_name.label": "系统名称", + "wizard.compliance.system_name.hint": "Annex IV §1:此 AI 系统的人类可读名称。", + "wizard.compliance.system_name.placeholder": "客户支持助手 v1", + "wizard.compliance.intended_purpose.label": "预期用途", + "wizard.compliance.intended_purpose.hint": "Annex IV §1:用平实语言陈述的系统目标。", + "wizard.compliance.intended_purpose.placeholder":"通过聊天分类客户支持工单", "wizard.step.review.title": "审查你的配置", "wizard.step.review.name": "审查", "wizard.step.review.desc": "这是 YAML。复制、下载并运行。", @@ -2300,7 +2449,20 @@ "wizard.review.download": "下载 .yaml", "wizard.review.reset": "重置向导", "wizard.review.reset.confirm":"重置所有向导答案并重新开始?你的 YAML 将丢失。", - "wizard.review.run_label": "然后运行:" + "wizard.review.run_label": "然后运行:", + "quickstart.spotlight.eyebrow": "引导式配置器", + "quickstart.spotlight.title": "不想手写 YAML?", + "quickstart.spotlight.lead": "浏览器内向导问七个问题,写出有效的 YAML,让你用一条命令运行。无需安装,进度本地保存。", + "quickstart.spotlight.cta_primary": "启动向导", + "quickstart.spotlight.cta_secondary":"按五步走", + "quickstart.spotlight.meta1": "约 3 分钟", + "quickstart.spotlight.meta2": "6 种语言", + "quickstart.spotlight.meta3": "无需安装", + "quickstart.spotlight.stage1.label": "回答 7 个问题", + "quickstart.spotlight.stage1.snippet": "用例 · 模型 · 数据 · LoRA · 风险", + "quickstart.spotlight.stage2.label": "得到一份有效 YAML", + "quickstart.spotlight.stage3.label": "运行它", + "quickstart.spotlight.manual_intro": "或者手动浏览五个步骤 ↓" }); /* ── Compliance page (compliance.html) ───────────────────── */ diff --git a/site/js/wizard.js b/site/js/wizard.js index 5a68d6cf..591078f4 100644 --- a/site/js/wizard.js +++ b/site/js/wizard.js @@ -87,7 +87,11 @@ /* ── State + persistence ──────────────────────────────── */ var STORAGE_KEY = 'forgelm.wizard.state'; - var STATE_VERSION = 1; + // Schema version 2 drops the data.format / prompt_field / response_field + // and the compliance audit_log / annex_iv_export toggles — those fields + // do not exist in forgelm/config.py (DataConfig + ComplianceMetadataConfig + // both use ``extra="forbid"``). Any v1 snapshot is discarded on load. + var STATE_VERSION = 2; function defaultState() { return { @@ -100,9 +104,11 @@ modelPreset: 'llama3-8b', datasetKind: 'huggingface', // 'huggingface' | 'local-jsonl' | 'local-pdf' datasetName: '', - promptField: 'prompt', - responseField: 'response', - datasetFormat: 'prompt-response', // 'prompt-response' | 'messages' + // ``data.format`` / ``data.prompt_field`` / ``data.response_field`` are + // intentionally absent — DataConfig auto-detects the row shape from + // the JSONL fields (``messages`` array → chat; ``prompt`` + ``response`` + // → instruction). Forcing them in the YAML would trigger + // ``extra="forbid"`` on DataConfig. qlora: true, loraR: 8, loraAlpha: 16, @@ -110,13 +116,22 @@ learningRate: '1e-4', batchSize: 2, gradientAccumulation: 2, - riskClassification: 'limited-risk', // 'minimal-risk' | 'limited-risk' | 'high-risk' | 'unacceptable' | 'unknown' + // EU AI Act risk + evaluation toggles. Audit log + Annex IV export + // are NOT YAML toggles — the audit log is always emitted, and the + // Annex IV bundle is produced whenever ``compliance.*`` metadata is + // populated. The "human approval" gate maps to + // ``evaluation.require_human_approval`` (Article 14). + riskClassification: 'limited-risk', autoRevert: true, maxAcceptableLoss: 2.0, safetyEval: true, humanApproval: false, - auditLog: true, - annexIvExport: true + // Optional Annex IV provider metadata (ComplianceMetadataConfig). + // Empty defaults are valid; the wizard exposes them for high-risk + // tiers where the EU AI Act requires named provider info. + providerName: '', + systemName: '', + intendedPurpose: '' }; } @@ -150,21 +165,21 @@ } /* ── High-risk auto-coercion (EU AI Act Art. 9-17 obligations) */ - // When the operator picks ``high-risk`` on Step 6 we force-enable - // the audit log, human approval gate, and Annex IV export — those - // are not optional under the regulation. Selecting back to a lower - // tier leaves the toggles where the operator left them; we only - // override on the upward transition. + // When the operator picks ``high-risk`` or ``unacceptable`` on Step 6 + // we force-enable the human approval gate + Llama Guard safety eval — + // those are not optional under Articles 9-17. The audit log is always + // emitted regardless of YAML (no toggle) and the Annex IV bundle is + // produced from the populated ``compliance.*`` metadata, so neither + // appears here. Selecting back to a lower tier leaves the toggles + // where the operator left them; we only override on the upward + // transition. + var STRICT_RISK_TIERS = ['high-risk', 'unacceptable']; + function isStrictRisk(state) { + return STRICT_RISK_TIERS.indexOf(state.riskClassification) !== -1; + } function coerceForRisk(state) { - if (state.riskClassification === 'high-risk') { - state.auditLog = true; - state.humanApproval = true; - state.annexIvExport = true; - state.safetyEval = true; - } else if (state.riskClassification === 'unacceptable') { - state.auditLog = true; + if (isStrictRisk(state)) { state.humanApproval = true; - state.annexIvExport = true; state.safetyEval = true; } } @@ -176,36 +191,31 @@ model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'huggingface', - datasetName: 'argilla/Capybara-Preferences', - datasetFormat: 'messages' + datasetName: 'argilla/Capybara-Preferences' }, 'code-copilot': { model: 'Qwen/Qwen2.5-Coder-7B-Instruct', modelPreset: 'custom', datasetKind: 'huggingface', - datasetName: 'bigcode/the-stack-smol-xs', - datasetFormat: 'prompt-response' + datasetName: 'bigcode/the-stack-smol-xs' }, 'domain-expert': { model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'local-pdf', - datasetName: './policies/', - datasetFormat: 'prompt-response' + datasetName: './policies/' }, 'grpo-math': { model: 'Qwen/Qwen2.5-7B-Instruct', modelPreset: 'qwen-7b', datasetKind: 'huggingface', - datasetName: 'openai/gsm8k', - datasetFormat: 'prompt-response' + datasetName: 'openai/gsm8k' }, 'medical-tr': { model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'huggingface', - datasetName: 'forgelm/medical-qa-tr', - datasetFormat: 'messages' + datasetName: 'forgelm/medical-qa-tr' }, 'custom': { /* leave the existing values intact — user wants explicit control */ @@ -235,8 +245,22 @@ // Build a syntax-highlighted YAML string. We intentionally avoid a // YAML library — the schema we emit is small enough that a manual // line-builder is auditable + dependency-free. The output is - // structurally aligned with ``config_template.yaml`` so it passes - // ``forgelm --config --dry-run`` cleanly. + // structurally aligned with ``forgelm/config.py`` (Pydantic models + // with ``extra="forbid"`` on every block), so it passes + // ``forgelm --config quickstart-generated.yaml --dry-run`` cleanly. + // + // Audit notes (deliberately NOT emitted, despite operator intuition): + // + // * ``data.format`` / ``data.prompt_field`` / ``data.response_field`` + // — DataConfig auto-detects the row shape from the JSONL fields + // themselves; emitting these triggers ``extra="forbid"``. + // * ``compliance.audit_log`` / ``compliance.annex_iv_export`` — + // ComplianceMetadataConfig has no such toggles. The audit log is + // always written; the Annex IV bundle is produced whenever the + // ``compliance.*`` metadata block is present. + // * Human-approval gate lives at ``evaluation.require_human_approval`` + // (Article 14), not under ``compliance``. + // * ``evaluation.safety.classifier`` (not ``classifier_path``). function buildYaml(state) { var lines = []; function comment(s) { lines.push('# ' + s); } @@ -268,32 +292,24 @@ header(0, 'lora'); k(1, 'r', String(state.loraR)); k(1, 'alpha', String(state.loraAlpha)); - k(1, 'dropout', '0.05'); + k(1, 'dropout', '0.1'); // schema default k(1, 'bias', '"none"'); k(1, 'target_modules', '["q_proj", "v_proj"]'); blank(); header(0, 'data'); - if (state.datasetKind === 'huggingface') { - k(1, 'dataset_name_or_path', JSON.stringify(state.datasetName || 'org/dataset-name')); - k(1, 'format', JSON.stringify(state.datasetFormat)); - if (state.datasetFormat === 'prompt-response') { - k(1, 'prompt_field', JSON.stringify(state.promptField)); - k(1, 'response_field', JSON.stringify(state.responseField)); - } - } else if (state.datasetKind === 'local-jsonl') { - k(1, 'dataset_name_or_path', JSON.stringify(state.datasetName || './data/train.jsonl')); - k(1, 'format', JSON.stringify(state.datasetFormat)); - if (state.datasetFormat === 'prompt-response') { - k(1, 'prompt_field', JSON.stringify(state.promptField)); - k(1, 'response_field', JSON.stringify(state.responseField)); - } - } else if (state.datasetKind === 'local-pdf') { + if (state.datasetKind === 'local-pdf') { comment(' PDFs need ingestion first — run before training:'); comment(' forgelm ingest ' + (state.datasetName || './documents/') + ' --recursive --output data/corpus.jsonl'); k(1, 'dataset_name_or_path', '"./data/corpus.jsonl"'); - k(1, 'format', JSON.stringify(state.datasetFormat)); + } else { + var defaultPath = state.datasetKind === 'huggingface' + ? 'org/dataset-name' + : './data/train.jsonl'; + k(1, 'dataset_name_or_path', JSON.stringify(state.datasetName || defaultPath)); } + comment(' DataConfig auto-detects row shape: { "messages": [...] } → chat;'); + comment(' { "prompt": ..., "response": ... } → instruction-following.'); blank(); header(0, 'training'); @@ -312,26 +328,39 @@ if (state.autoRevert) { k(1, 'max_acceptable_loss', String(state.maxAcceptableLoss)); } + // Article 14: require_human_approval stages the model under + // ``final_model.staging./`` and exits 4. The CLI then + // expects ``forgelm approve `` before promoting to + // ``final_model/``. + if (state.humanApproval) { + k(1, 'require_human_approval', 'true'); + } if (state.safetyEval) { header(1, 'safety'); k(2, 'enabled', 'true'); - k(2, 'classifier_path', '"meta-llama/Llama-Guard-3-8B"'); - k(2, 'min_safety_score', '0.85'); + k(2, 'classifier', '"meta-llama/Llama-Guard-3-8B"'); k(2, 'min_classifier_confidence', '0.7'); + k(2, 'max_safety_regression', '0.05'); } blank(); + // ComplianceMetadataConfig — Annex IV §1 provider + system info. + // Always emit risk_classification; provider/system fields stay + // empty unless the operator filled them in (high-risk tiers + // surface the inputs in Step 6). header(0, 'compliance'); k(1, 'risk_classification', JSON.stringify(state.riskClassification)); - k(1, 'audit_log', state.auditLog ? 'true' : 'false'); - k(1, 'annex_iv_export', state.annexIvExport ? 'true' : 'false'); - if (state.humanApproval) { - header(1, 'human_approval'); - k(2, 'enabled', 'true'); - k(2, 'staging_dir', '"./checkpoints/staging"'); - } + if (state.providerName) k(1, 'provider_name', JSON.stringify(state.providerName)); + if (state.systemName) k(1, 'system_name', JSON.stringify(state.systemName)); + if (state.intendedPurpose) k(1, 'intended_purpose', JSON.stringify(state.intendedPurpose)); blank(); + // Friendly closing comment so an operator reading the YAML cold + // knows the audit log + Annex IV bundle land on disk regardless + // of whether they show up as toggles here. + comment(' Audit log (audit_log.jsonl) is always emitted — no YAML toggle.'); + comment(' Annex IV bundle is produced from the compliance.* metadata above.'); + return lines.join('\n'); } @@ -552,6 +581,7 @@ if (state.step < STEPS.length - 1) { state.step += 1; render(); + pane.scrollTop = 0; } else { // Final step: "Done" closes the modal but keeps state. closeModal(); @@ -561,6 +591,7 @@ if (state.step > 0) { state.step -= 1; render(); + pane.scrollTop = 0; } } @@ -830,45 +861,15 @@ el('span', { class: 'wizard-row-hint', text: tr('wizard.dataset.name.hint') }) ])); - var formatSelect = el('select', { class: 'wizard-select' }); - [ - { id: 'prompt-response', labelKey: 'wizard.dataset.format.prompt' }, - { id: 'messages', labelKey: 'wizard.dataset.format.messages' } - ].forEach(function (opt) { - var option = el('option', { value: opt.id, text: tr(opt.labelKey) }); - if (state.datasetFormat === opt.id) option.selected = true; - formatSelect.appendChild(option); - }); - formatSelect.addEventListener('change', function () { - state.datasetFormat = formatSelect.value; - persist(); - rerender(); - }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ - el('label', { class: 'wizard-row-label', text: tr('wizard.dataset.format.label') }), - formatSelect - ])); - - if (state.datasetFormat === 'prompt-response') { - ['promptField', 'responseField'].forEach(function (field) { - var input = el('input', { - type: 'text', - class: 'wizard-input', - value: state[field] - }); - input.addEventListener('input', function () { - state[field] = input.value.trim() || (field === 'promptField' ? 'prompt' : 'response'); - persist(); - var yamlOutput = document.querySelector('[data-wizard-yaml-output]'); - if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); - }); - var labelKey = field === 'promptField' ? 'wizard.dataset.prompt_field' : 'wizard.dataset.response_field'; - pane.appendChild(el('div', { class: 'wizard-row' }, [ - el('label', { class: 'wizard-row-label', text: tr(labelKey) }), - input - ])); - }); - } + // Format auto-detection note (replaces the prior format / prompt_field + // / response_field manual UI). DataConfig in forgelm/config.py infers + // the row shape from the JSONL fields, so no schema field is exposed + // for it — the wizard previously emitted these as YAML keys, which + // ``extra="forbid"`` rejected at dry-run. + pane.appendChild(el('div', { + class: 'wizard-step-tutorial', + html: '' + tr('wizard.dataset.autodetect.title') + ' ' + tr('wizard.dataset.autodetect.body') + })); }; /* Step 5: Training params */ @@ -881,6 +882,16 @@ if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); } + // Append a per-field detail paragraph beneath a row when the + // operator is in beginner mode (state.detailsVisible). The paragraph + // expands what the hint glosses — 2-3 sentences explaining what the + // value MEANS, not just what range to pick. + function appendDetail(rowEl, detailKey) { + if (state.detailsVisible && detailKey) { + rowEl.appendChild(el('p', { class: 'wizard-row-detail', text: tr(detailKey) })); + } + } + // QLoRA toggle var qloraInput = el('input', { type: 'checkbox' }); qloraInput.checked = state.qlora; @@ -889,16 +900,18 @@ persist(); liveYaml(); }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var qloraRow = el('div', { class: 'wizard-row' }, [ el('label', { class: 'wizard-toggle' }, [ qloraInput, el('span', { class: 'wizard-toggle-label', text: tr('wizard.training.qlora.label') }) ]), el('span', { class: 'wizard-row-hint', text: tr('wizard.training.qlora.hint') }) - ])); + ]); + appendDetail(qloraRow, 'wizard.training.qlora.detail'); + pane.appendChild(qloraRow); - // LoRA r slider - function makeSlider(field, min, max, hintKey, labelKey) { + // LoRA r / alpha sliders + function makeSlider(field, min, max, hintKey, labelKey, detailKey) { var slider = el('input', { type: 'range', class: 'wizard-slider', @@ -914,14 +927,16 @@ persist(); liveYaml(); }); - return el('div', { class: 'wizard-row' }, [ + var row = el('div', { class: 'wizard-row' }, [ el('label', { class: 'wizard-row-label', text: tr(labelKey) }), el('div', { class: 'wizard-slider-row' }, [slider, valueOut]), el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) ]); + appendDetail(row, detailKey); + return row; } - pane.appendChild(makeSlider('loraR', 4, 64, 'wizard.training.lora_r.hint', 'wizard.training.lora_r.label')); - pane.appendChild(makeSlider('loraAlpha', 8, 128, 'wizard.training.lora_alpha.hint', 'wizard.training.lora_alpha.label')); + pane.appendChild(makeSlider('loraR', 4, 64, 'wizard.training.lora_r.hint', 'wizard.training.lora_r.label', 'wizard.training.lora_r.detail')); + pane.appendChild(makeSlider('loraAlpha', 8, 128, 'wizard.training.lora_alpha.hint', 'wizard.training.lora_alpha.label', 'wizard.training.lora_alpha.detail')); // Epochs var epochsInput = el('input', { @@ -939,11 +954,13 @@ liveYaml(); } }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var epochsRow = el('div', { class: 'wizard-row' }, [ el('label', { class: 'wizard-row-label', text: tr('wizard.training.epochs.label') }), epochsInput, el('span', { class: 'wizard-row-hint', text: tr('wizard.training.epochs.hint') }) - ])); + ]); + appendDetail(epochsRow, 'wizard.training.epochs.detail'); + pane.appendChild(epochsRow); // Learning rate var lrSelect = el('select', { class: 'wizard-select' }); @@ -957,11 +974,13 @@ persist(); liveYaml(); }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var lrRow = el('div', { class: 'wizard-row' }, [ el('label', { class: 'wizard-row-label', text: tr('wizard.training.lr.label') }), lrSelect, el('span', { class: 'wizard-row-hint', text: tr('wizard.training.lr.hint') }) - ])); + ]); + appendDetail(lrRow, 'wizard.training.lr.detail'); + pane.appendChild(lrRow); // Batch size var batchSelect = el('select', { class: 'wizard-select' }); @@ -975,11 +994,13 @@ persist(); liveYaml(); }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var batchRow = el('div', { class: 'wizard-row' }, [ el('label', { class: 'wizard-row-label', text: tr('wizard.training.batch.label') }), batchSelect, el('span', { class: 'wizard-row-hint', text: tr('wizard.training.batch.hint') }) - ])); + ]); + appendDetail(batchRow, 'wizard.training.batch.detail'); + pane.appendChild(batchRow); }; /* Step 6: Evaluation + EU AI Act compliance */ @@ -1030,8 +1051,10 @@ })); } - // Toggles - function makeToggle(field, labelKey, hintKey, locked) { + // Toggles. ``locked`` disables the input and adds a "required for risk + // tier" badge — used for fields the EU AI Act forces under high-risk + // / unacceptable tiers (Articles 9-17). + function makeToggle(field, labelKey, hintKey, locked, detailKey) { var input = el('input', { type: 'checkbox' }); input.checked = state[field]; if (locked) input.disabled = true; @@ -1048,13 +1071,17 @@ style: 'margin-left: 0.4rem;' })); } - return el('div', { class: 'wizard-row' }, [ + var children = [ el('label', { class: 'wizard-toggle' }, [input, labelEl]), el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) - ]); + ]; + if (detailKey && state.detailsVisible) { + children.push(el('p', { class: 'wizard-row-detail', text: tr(detailKey) })); + } + return el('div', { class: 'wizard-row' }, children); } - var highRisk = state.riskClassification === 'high-risk' || state.riskClassification === 'unacceptable'; - pane.appendChild(makeToggle('autoRevert', 'wizard.eval.auto_revert.label', 'wizard.eval.auto_revert.hint', false)); + var highRisk = isStrictRisk(state); + pane.appendChild(makeToggle('autoRevert', 'wizard.eval.auto_revert.label', 'wizard.eval.auto_revert.hint', false, 'wizard.eval.auto_revert.detail')); if (state.autoRevert) { var lossInput = el('input', { @@ -1072,17 +1099,60 @@ liveYaml(); } }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var lossRowChildren = [ el('label', { class: 'wizard-row-label', text: tr('wizard.eval.max_loss.label') }), lossInput, el('span', { class: 'wizard-row-hint', text: tr('wizard.eval.max_loss.hint') }) - ])); + ]; + if (state.detailsVisible) { + lossRowChildren.push(el('p', { class: 'wizard-row-detail', text: tr('wizard.eval.max_loss.detail') })); + } + pane.appendChild(el('div', { class: 'wizard-row' }, lossRowChildren)); } - pane.appendChild(makeToggle('safetyEval', 'wizard.eval.safety.label', 'wizard.eval.safety.hint', highRisk)); - pane.appendChild(makeToggle('auditLog', 'wizard.compliance.audit.label', 'wizard.compliance.audit.hint', highRisk)); - pane.appendChild(makeToggle('humanApproval', 'wizard.compliance.approval.label', 'wizard.compliance.approval.hint', highRisk)); - pane.appendChild(makeToggle('annexIvExport', 'wizard.compliance.annex.label', 'wizard.compliance.annex.hint', highRisk)); + pane.appendChild(makeToggle('safetyEval', 'wizard.eval.safety.label', 'wizard.eval.safety.hint', highRisk, 'wizard.eval.safety.detail')); + pane.appendChild(makeToggle('humanApproval', 'wizard.compliance.approval.label', 'wizard.compliance.approval.hint', highRisk, 'wizard.compliance.approval.detail')); + + // Informational block: the audit log + Annex IV bundle are not YAML + // toggles — they are produced as side-effects of the run. Surface + // this fact instead of pretending the operator has a control they + // don't actually have. + pane.appendChild(el('div', { + class: 'wizard-step-tutorial', + html: '' + tr('wizard.compliance.always.title') + ' ' + tr('wizard.compliance.always.body') + })); + + // Annex IV §1 provider metadata. Surface ALWAYS (any tier benefits + // from named provider info) but tag the inputs as "required for + // high-risk" so the operator knows the obligation rises with the + // tier. Empty values are valid; ComplianceMetadataConfig has + // string defaults of "". + function makeMetaInput(field, labelKey, hintKey, placeholderKey) { + var input = el('input', { + type: 'text', + class: 'wizard-input', + value: state[field] || '', + placeholder: tr(placeholderKey) + }); + input.addEventListener('input', function () { + state[field] = input.value; + persist(); + liveYaml(); + }); + return el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label' }, [ + tr(labelKey), + highRisk ? el('span', { class: 'badge', text: tr('wizard.locked.badge'), style: 'margin-left: 0.4rem;' }) : null + ]), + input, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]); + } + if (highRisk) { + pane.appendChild(makeMetaInput('providerName', 'wizard.compliance.provider_name.label', 'wizard.compliance.provider_name.hint', 'wizard.compliance.provider_name.placeholder')); + pane.appendChild(makeMetaInput('systemName', 'wizard.compliance.system_name.label', 'wizard.compliance.system_name.hint', 'wizard.compliance.system_name.placeholder')); + pane.appendChild(makeMetaInput('intendedPurpose', 'wizard.compliance.intended_purpose.label', 'wizard.compliance.intended_purpose.hint', 'wizard.compliance.intended_purpose.placeholder')); + } }; /* Step 7: Review + download */ diff --git a/site/quickstart.html b/site/quickstart.html index ccfc15ba..8a407e66 100644 --- a/site/quickstart.html +++ b/site/quickstart.html @@ -85,6 +85,67 @@

Five steps. + +
+
+ Skip the YAML +

Use the in-browser wizard to build your config in 3 minutes.

+

Pick your use case, base model, dataset, and EU AI Act risk tier. The wizard writes a working quickstart-generated.yaml you can run with one command — no fine-tuning expertise required.

+
+ + Or follow the 5-step guide +
+
+ + 7 guided steps + + Live YAML preview + + EU AI Act presets +
+
+ +
+ +

Or follow the manual 5-step reference below

+
From 1cc5a06dc7cfa32cff31b1d0ff19effa64e613fc Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 12:19:57 +0300 Subject: [PATCH 05/10] fix(site): wizard dialog needs definite height for scroll to fire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of "use-case / model / dataset steps won't scroll": - .wizard-dialog used max-height, not height. With only max-height, flex: 1 on .wizard-body resolves its flex-basis: 0% against an indefinite parent. Body then sizes to content. Step pane inside grows with body. overflow-y: auto on the pane never fires because there's nothing to overflow against. - The earlier min-height: 0 fix was necessary but not sufficient. min-height: 0 only matters once the parent itself has a definite size to constrain against — which max-height alone doesn't deliver in this flex context. Fix: replace `max-height: min(720px, calc(100vh - 4rem))` with `height: min(720px, calc(100vh - 4rem))`. Now the dialog is always exactly that tall, body has a definite 720px-ish frame, and the step pane's overflow-y: auto fires on every step (use-case, model, dataset, training, compliance — all now scroll). Also bumped .wizard-step-pane bottom padding from 1.4rem to 2rem (symmetric) and added scrollbar-gutter: stable so the last item in a scrolled pane has the same breathing room as the first and the layout doesn't reflow when scroll appears. Removed the now-redundant max-height: 100% from the pane (the grid track auto-sizes correctly once the dialog has a definite frame). Mobile path was already correct — `height: 100vh` on the dialog at @media (max-width: 900px) is already definite, so scroll worked on small viewports already. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/css/style.css | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/site/css/style.css b/site/css/style.css index c6e31167..1082a8e6 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -1591,7 +1591,18 @@ section { padding: 5rem 0; } .wizard-dialog { position: relative; width: min(1080px, 100%); - max-height: min(720px, calc(100vh - 4rem)); + /* Use a *definite* height (not max-height) so the inner flex/grid + children get a resolvable frame to overflow against. With only + ``max-height``, ``flex: 1`` on .wizard-body resolves its + ``flex-basis: 0%`` against an indefinite parent, the body sizes + to content, and the step pane's ``overflow-y: auto`` never fires. + This was the root cause of the "use-case / model / dataset + screens won't scroll" bug — those steps have just enough content + to overflow on smaller laptops, but the dialog was free to grow + so nothing clipped. Fixing the dialog at 720px (or 100vh − 4rem + on small screens) gives the body + pane a definite frame and + scroll fires reliably. */ + height: min(720px, calc(100vh - 4rem)); background: var(--bg-card); border: 1px solid var(--border-strong); border-radius: var(--radius-lg); @@ -1707,13 +1718,20 @@ section { padding: 5rem 0; } } .wizard-step-pane { - padding: 2rem 2rem 1.4rem; + /* Padding is symmetric (2rem all around) so the LAST item in a + scrolled pane has the same breathing room as the FIRST. + Previously the bottom was 1.4rem and the final hint paragraph + hugged the bottom edge — readable but visually cramped, and on + touch devices the scrollbar overlapped the last word. */ + padding: 2rem; overflow-y: auto; overflow-x: hidden; + /* ``min-height: 0`` plus the dialog's definite height (above) + makes ``overflow-y: auto`` fire on every step. */ min-height: 0; - max-height: 100%; - /* Step content can grow tall on Step 6 (compliance fields); the - pane scrolls independently of the YAML preview. */ + /* ``scrollbar-gutter: stable`` reserves space for the scrollbar so + the layout doesn't reflow when content grows past the fold. */ + scrollbar-gutter: stable; } .wizard-yaml-pane { From 708319bae0197ef9072cc2a6cb23e69846bc94d9 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 12:27:14 +0300 Subject: [PATCH 06/10] fix(site): wizard-body needs grid-template-rows: minmax(0, 1fr) for scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The actual bug under the wizard scroll issue, after two earlier attempts that were necessary but not sufficient: A grid container with no ``grid-template-rows`` creates an implicit ``auto`` row, and ``auto`` resolves to ``max-content``. So even though .wizard-body had a definite size from the flex parent (.wizard-dialog now has height: min(720px, ...)) and ``min-height: 0`` to allow shrinking, the grid row INSIDE was still auto-sizing to the step pane's content. The pane filled its track, the track was as tall as the content, and ``overflow-y: auto`` on the pane had nothing to overflow against — it scrolled relative to its own content-sized height, i.e. not at all. Fix: set ``grid-template-rows: minmax(0, 1fr)`` on .wizard-body. ``minmax(0, 1fr)`` pins the row to the body's definite height (min: 0 lets it shrink, 1fr fills available space), the step pane fills 1fr, and scroll fires whenever content exceeds the row height. Mobile rule updated for parity: ``grid-template-rows: minmax(0, 1fr) auto`` (was ``1fr auto``) so the step-pane row has the same min: 0 floor. This is the third commit chasing the same symptom and the comment block in style.css now spells out all three requirements (definite dialog height, min-height: 0 on body, minmax(0, 1fr) on the grid row) so this stops being a recurring bug. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/css/style.css | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/site/css/style.css b/site/css/style.css index 1082a8e6..7a1ac788 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -1703,15 +1703,33 @@ section { padding: 5rem 0; } } /* Body: 60/40 split — step pane + YAML preview. - ``min-height: 0`` is mandatory on flex/grid children that contain a - scrollable region — without it the child takes its content's intrinsic - height and overflow:auto never fires. This was the root cause of the - "modal won't scroll" bug; both the wizard-body grid container and the - step-pane grid item need it. */ + + Three things that all have to be true for the step pane to scroll: + + 1. .wizard-dialog has a *definite* height (set above as + ``height: min(720px, calc(100vh - 4rem))``). Without that, the + flex chain has no frame to overflow against. + + 2. .wizard-body has ``min-height: 0`` so it can shrink under + ``flex: 1`` instead of pinning to its grid content's intrinsic + height. + + 3. .wizard-body declares ``grid-template-rows: minmax(0, 1fr)``. + THIS is the one that bit us repeatedly: a grid container with + no explicit row sizing creates an implicit ``auto`` row, which + grows to max-content. So the grid row was sizing to the step + pane's content, the step pane was filling its track, and the + pane's ``overflow-y: auto`` had nothing to overflow against — + it scrolled relative to its own (content-sized) height, i.e. + not at all. ``minmax(0, 1fr)`` pins the row to the body's + definite height, the step pane fills 1fr, and scroll fires + whenever content exceeds the available row height. +*/ .wizard-body { flex: 1; display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr); gap: 0; min-height: 0; overflow: hidden; @@ -2182,7 +2200,10 @@ section { padding: 5rem 0; } } .wizard-body { grid-template-columns: 1fr; - grid-template-rows: 1fr auto; + /* ``minmax(0, 1fr)`` for the step-pane row — same reason as the + desktop rule above: without the ``min: 0`` floor a 1fr track + can refuse to shrink below its content. */ + grid-template-rows: minmax(0, 1fr) auto; } .wizard-step-pane { padding: 1.4rem; From eea260a6da2e1ac07d28cf2fdfe2cdc451e0210d Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 12:49:47 +0300 Subject: [PATCH 07/10] fix(site): light-theme contrast + a11y bundle for site chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bundles a UI/UX audit pass over the marketing site, focused on the class of bugs that hide content when the user toggles to light mode. Light-theme contrast — dark surfaces using page-text variables: - .terminal-bar, .terminal-body .cmd, .terminal-body .out - .code-block-header, .code-block-header .copy-btn - .wizard-yaml-header, .wizard-yaml-copy (default + hover) - .wizard-yaml-output .yaml-null - .hero-slider-prev / .hero-slider-next These selectors all paint onto a *theme-locked dark surface* (the code chrome stays dark in both light and dark page themes), so they need theme-locked tokens too — --code-comment / --code-text instead of --text-muted / --text-secondary / --text which flip to dim greys or near-black in light mode and disappear against the dark gradient. The visible symptom on the home page was the literal forgelm command becoming invisible in the hero terminal under light mode. Light-theme contrast — accent tokens below WCAG 1.4.3 AA on light: - inline code in body copy, .use-case-tag, .article-num, .guide-content td:first-child code Swap --ember-bright / --steel-bright (which pass on dark but drop below 4.5:1 on light card surfaces) for the corresponding -deep tokens via a [data-theme="light"] rule. Same semantic colour, AA compliance. A11y wiring (HTML + JS): - nav-hamburger gets aria-controls="primary-nav" pointing at the matching id on .nav-menu so screen readers announce the controlled region. - theme-toggle gets aria-pressed="false" initial state; main.js flips it to true whenever light mode is engaged so the button's state is announced correctly. - Three guide.search.label / guide.sidebar.label / guide.progress.label i18n keys added (EN + TR + DE/FR/ES/ZH) so the search input, sidebar nav and reading-progress bar carry proper aria-labels. Print: @media print hides the wizard-modal so a left-open wizard doesn't print over the page. All eight pages (index, quickstart, features, guide, compliance, contact, privacy, terms) carry the same nav set; chrome parity gate remains in EN-TR lockstep. Bilingual + anchor + CLI-help gates pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/compliance.html | 6 ++-- site/contact.html | 6 ++-- site/css/style.css | 74 ++++++++++++++++++++++++++++++++++------- site/features.html | 6 ++-- site/guide.html | 6 ++-- site/index.html | 6 ++-- site/js/main.js | 4 +++ site/js/translations.js | 18 ++++++++++ site/privacy.html | 6 ++-- site/quickstart.html | 6 ++-- site/terms.html | 6 ++-- 11 files changed, 108 insertions(+), 36 deletions(-) diff --git a/site/compliance.html b/site/compliance.html index 3e8775da..a449eec8 100644 --- a/site/compliance.html +++ b/site/compliance.html @@ -34,8 +34,8 @@ ForgeLM - -
- diff --git a/site/contact.html b/site/contact.html index 3f7db9cd..cf099b5f 100644 --- a/site/contact.html +++ b/site/contact.html @@ -34,8 +34,8 @@ ForgeLM - -
- diff --git a/site/css/style.css b/site/css/style.css index 7a1ac788..65b29280 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -716,7 +716,12 @@ section { padding: 5rem 0; } display: flex; align-items: center; justify-content: center; - background: rgba(20, 24, 35, 0.72); + /* Theme-aware: previously hardcoded a dark rgba background with + ``color: var(--text)``. In light mode that resolved to dark text + on dark bg, so the chevron disappeared. Mirror the theme by + using the same elevated-surface tokens the rest of the chrome + uses. */ + background: var(--bg-elev); border: 1px solid var(--border-strong); border-radius: 50%; color: var(--text); @@ -804,7 +809,11 @@ section { padding: 5rem 0; } border-bottom: 1px solid var(--code-border); font-family: var(--font-mono); font-size: 0.78rem; - color: var(--text-muted); + /* Theme-locked: this bar is a dark code surface in BOTH themes, + so the title text uses --code-comment (theme-locked grey) — not + --text-muted, which flips to a dim grey-on-white in light mode + and disappears against the dark gradient. */ + color: var(--code-comment); } .terminal-bar .dots { display: flex; @@ -829,13 +838,19 @@ section { padding: 5rem 0; } overflow-x: auto; } .terminal-body .prompt { color: var(--ember-bright); } -.terminal-body .cmd { color: var(--text); } +/* The terminal background is theme-locked dark (--code-bg stays dark + in both themes), so token colors here MUST use theme-locked code-* + variables — never page text vars (--text / --text-secondary / + --text-muted) which flip to dark in light mode and become + invisible on the dark terminal. The ``forgelm`` command itself + was disappearing in light mode for exactly this reason. */ +.terminal-body .cmd { color: var(--code-text); } .terminal-body .flag { color: var(--steel-bright); } .terminal-body .arg { color: #6ee7b7; } .terminal-body .comment { color: var(--code-comment); font-style: italic; } .terminal-body .ok { color: var(--success); } .terminal-body .warn { color: var(--warn); } -.terminal-body .out { color: var(--text-secondary); } +.terminal-body .out { color: var(--code-text); opacity: 0.78; } .terminal-body .blink { display: inline-block; width: 8px; height: 1.05em; @@ -1099,7 +1114,11 @@ section { padding: 5rem 0; } background: rgba(255,255,255,.03); font-family: var(--font-mono); font-size: 0.78rem; - color: var(--text-muted); + /* Theme-locked: the parent .code-block uses --code-bg (always dark + in both themes), so the language tag must use a theme-locked grey + too — --text-muted resolves to dim grey on light theme and + disappears here. */ + color: var(--code-comment); } .code-block-header .lang { display: inline-flex; @@ -1131,7 +1150,10 @@ section { padding: 5rem 0; } .copy-btn { background: none; border: 1px solid var(--code-border); - color: var(--text-muted); + /* Theme-locked: lives on the dark .code-block-header bar in both + themes. --code-comment is the in-code default grey — readable + against the dark gradient in both light and dark page themes. */ + color: var(--code-comment); padding: 0.35rem 0.7rem; border-radius: 4px; cursor: pointer; @@ -1768,7 +1790,10 @@ section { padding: 5rem 0; } border-bottom: 1px solid var(--code-border); font-family: var(--font-mono); font-size: 0.78rem; - color: var(--text-muted); + /* Theme-locked: dark gradient bar in BOTH themes — see .terminal-bar + above for the same constraint. --code-comment survives the light + toggle. */ + color: var(--code-comment); } .wizard-yaml-toggle { display: inline-flex; @@ -1800,16 +1825,19 @@ section { padding: 5rem 0; } background: transparent; border: 1px solid var(--code-border); border-radius: var(--radius-sm); - color: var(--text-muted); + /* Theme-locked: lives on the dark .wizard-yaml-header in both + themes. Hover stays on the dark surface so the button doesn't + flash light-on-light when the page is in light mode. */ + color: var(--code-comment); font: inherit; padding: 0.2rem 0.5rem; cursor: pointer; transition: background var(--transition), border-color var(--transition), color var(--transition); } .wizard-yaml-copy:hover { - background: var(--bg-elev); - border-color: var(--border-strong); - color: var(--text); + background: rgba(255, 255, 255, 0.06); + border-color: var(--code-text); + color: var(--code-text); } .wizard-yaml-copy.is-copied { border-color: var(--success); @@ -1838,7 +1866,7 @@ section { padding: 5rem 0; } .wizard-yaml-output .yaml-number { color: var(--code-number); } .wizard-yaml-output .yaml-bool { color: var(--code-fn); } .wizard-yaml-output .yaml-comment { color: var(--code-comment); font-style: italic; } -.wizard-yaml-output .yaml-null { color: var(--text-muted); font-style: italic; } +.wizard-yaml-output .yaml-null { color: var(--code-comment); font-style: italic; } /* Footer: prev / step name / next */ .wizard-footer { @@ -3241,3 +3269,25 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } [data-theme="light"] .guide-content thead { background: #f7f8fb; } [data-theme="light"] .guide-content tbody tr:hover { background: #fafbfd; } [data-theme="light"] .guide-content .mermaid { background: #fafbfd; } + +/* WCAG 1.4.3 (AA) contrast fixes — accent tokens that pass on dark + backgrounds (--ember-bright #fb923c, --steel-bright #38bdf8) drop + below 4.5:1 on the elevated light backgrounds (#f7f8fb / #fff / + tinted pills). Swap them for the corresponding ``-deep`` tokens in + light mode so inline code, use-case tags, article numbers and + table-first-column code keep their semantic colour but pass AA. */ +[data-theme="light"] code:not(pre code), +[data-theme="light"] .guide-content :not(pre) > code { + color: var(--ember-deep); +} +[data-theme="light"] .use-case-tag, +[data-theme="light"] .article-num, +[data-theme="light"] .guide-content td:first-child code { + color: var(--steel-deep); +} + +/* Hide the wizard modal in print output too — without this, a left- + open wizard would print over the page content. */ +@media print { + .wizard-modal { display: none !important; } +} diff --git a/site/features.html b/site/features.html index 6ff39048..e7205d94 100644 --- a/site/features.html +++ b/site/features.html @@ -34,8 +34,8 @@ ForgeLM - -

- diff --git a/site/guide.html b/site/guide.html index fe335d38..38565e8f 100644 --- a/site/guide.html +++ b/site/guide.html @@ -43,8 +43,8 @@ ForgeLM - -
- diff --git a/site/index.html b/site/index.html index 41d88fcc..6ae4e9dd 100644 --- a/site/index.html +++ b/site/index.html @@ -41,11 +41,11 @@ ForgeLM - -
- diff --git a/site/js/main.js b/site/js/main.js index fc8749d9..1533e5ca 100644 --- a/site/js/main.js +++ b/site/js/main.js @@ -133,6 +133,10 @@ } else { document.documentElement.removeAttribute('data-theme'); } + // Keep the toggle's ARIA state in sync so screen readers + // announce the current mode. ``aria-pressed=true`` = light mode + // is engaged; ``false`` = default dark. + if (btn) btn.setAttribute('aria-pressed', theme === 'light' ? 'true' : 'false'); } try { stored = localStorage.getItem(THEME_PREF_NAME); } catch (_) {} diff --git a/site/js/translations.js b/site/js/translations.js index 7ec3ee9d..e23961bf 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -189,6 +189,9 @@ "guide.title": "Guide", "guide.toc.title": "On this page", "guide.search.placeholder":"Search the guide…", + "guide.search.label": "Search the guide", + "guide.sidebar.label": "Guide sections", + "guide.progress.label": "Reading progress", "guide.search.empty": "No matches.", "guide.search.kbd_navigate":"to navigate", "guide.search.kbd_select":"to open", @@ -205,6 +208,9 @@ "guide.title": "Rehber", "guide.toc.title": "Bu sayfada", "guide.search.placeholder":"Rehberde ara…", + "guide.search.label": "Rehberde ara", + "guide.sidebar.label": "Rehber bölümleri", + "guide.progress.label": "Okuma ilerlemesi", "guide.search.empty": "Eşleşme yok.", "guide.search.kbd_navigate":"gezinmek için", "guide.search.kbd_select":"açmak için", @@ -221,6 +227,9 @@ "guide.title": "Handbuch", "guide.toc.title": "Auf dieser Seite", "guide.search.placeholder":"Im Handbuch suchen…", + "guide.search.label": "Im Handbuch suchen", + "guide.sidebar.label": "Handbuch-Abschnitte", + "guide.progress.label": "Lesefortschritt", "guide.search.empty": "Keine Treffer.", "guide.search.kbd_navigate":"zum Navigieren", "guide.search.kbd_select":"zum Öffnen", @@ -237,6 +246,9 @@ "guide.title": "Guide", "guide.toc.title": "Sur cette page", "guide.search.placeholder":"Rechercher dans le guide…", + "guide.search.label": "Rechercher dans le guide", + "guide.sidebar.label": "Sections du guide", + "guide.progress.label": "Progression de lecture", "guide.search.empty": "Aucun résultat.", "guide.search.kbd_navigate":"pour naviguer", "guide.search.kbd_select":"pour ouvrir", @@ -253,6 +265,9 @@ "guide.title": "Guía", "guide.toc.title": "En esta página", "guide.search.placeholder":"Buscar en la guía…", + "guide.search.label": "Buscar en la guía", + "guide.sidebar.label": "Secciones de la guía", + "guide.progress.label": "Progreso de lectura", "guide.search.empty": "Sin resultados.", "guide.search.kbd_navigate":"para navegar", "guide.search.kbd_select":"para abrir", @@ -269,6 +284,9 @@ "guide.title": "指南", "guide.toc.title": "本页内容", "guide.search.placeholder":"在指南中搜索…", + "guide.search.label": "在指南中搜索", + "guide.sidebar.label": "指南章节", + "guide.progress.label": "阅读进度", "guide.search.empty": "无匹配结果。", "guide.search.kbd_navigate":"导航", "guide.search.kbd_select":"打开", diff --git a/site/privacy.html b/site/privacy.html index f170dcb6..8727c18c 100644 --- a/site/privacy.html +++ b/site/privacy.html @@ -34,8 +34,8 @@ ForgeLM - -
- diff --git a/site/quickstart.html b/site/quickstart.html index 8a407e66..f4bc7376 100644 --- a/site/quickstart.html +++ b/site/quickstart.html @@ -34,8 +34,8 @@ ForgeLM - -
- diff --git a/site/terms.html b/site/terms.html index 51e7b3f0..316ee812 100644 --- a/site/terms.html +++ b/site/terms.html @@ -34,8 +34,8 @@ ForgeLM - -
- From 3db4778a3ee50f5d03d59fc641e546e08b924afa Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 14:55:07 +0300 Subject: [PATCH 08/10] feat(site/wizard): full ForgeLM YAML coverage + accordion UX + per-field validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wizard scope expansion from ~30% to full ForgeLM feature surface, plus the UX polish needed to keep 9 steps + 7 accordions navigable. WIZARD STRUCTURE — 8 → 9 steps: - New "Operations & advanced settings" step (#8) lifts notifications, monitoring, synthetic data, and retention out of the compliance step. Compliance was getting too tall to scan in one viewport; Operations now hosts the four accordions on its own. - Spotlight + step-counter strings updated 8 → 9 across all 6 locales. NEW BUILDYAML SURFACE (Bundles 4-7): - evaluation.benchmark — lm-evaluation-harness gate (tasks list + min_score). Conditionally emitted when benchmarkEnabled. - evaluation.llm_judge — LLM-as-Judge scoring (judge_model + judge_api_key_env + min_score). Conditionally emitted. - data.governance — Article 10 inputs (collection_method, annotation_process, known_biases, personal_data_included, dpia_completed) inside a dataset-step accordion. - webhook — Slack/Teams/Discord notifications. Now gated by an explicit ``webhookEnabled`` toggle: off → no webhook block in YAML, on → URL is required. URL accepts ``env:VAR_NAME`` for secrets; buildYaml branches on the prefix to emit ``url`` vs ``url_env``. - monitoring — Article 12+17 post-market hooks (endpoint, metrics_export, alert_on_drift). Auto-coerced for high-risk tiers. - synthetic — teacher → student data generation (teacher_model, seed_file, api_key_env). - retention — GDPR Art. 5+17 horizons (audit_log, staging, ephemeral, raw_documents) + enforce mode literal (log_only/warn_on_excess/block_on_excess). PER-FIELD VALIDATION (19 conditional-required inputs): - Always required: model.name_or_path (Custom preset only) + format check (org/repo); data.dataset_name_or_path. - Feature-enabled: webhook.url, monitoring.endpoint, synthetic teacher_model + seed_file + api_key_env, benchmark.tasks, judge.api_key_env (each required only when its parent toggle is on). - High-risk + unacceptable: compliance.{provider_name, system_name, intended_purpose} (Annex IV §1), risk_assessment.{intended_use, foreseeable_misuse, mitigation_measures} (Article 9), governance.{ collection_method, annotation_process, known_biases} (Article 10). Validation messages explain WHY (which choice triggered the rule) AND WHAT to do (fill the field or undo the choice). Example: "EU AI Act Annex IV §1 requires the provider's legal entity name for high-risk systems. Fill it in or lower the risk classification." All 19 messages translated across EN / TR / DE / FR / ES / ZH. DEFAULT-CLEAN UX: - attemptedSteps wiped on every loadState() so a fresh open never shows pre-painted errors from a previous session. - On Next-on-invalid: every accordion containing an invalid field auto-opens, the renderer paints inline errors + .is-invalid borders, and keyboard focus jumps to the first invalid input (preventScroll + smooth scrollIntoView so the input lands centred without a jarring jump). ACCORDION UX REWRITE: - SVG line icons replace the previous unicode arrow chevron. Per-accordion identity icons (governance=document, risk=shield, eval=check-circle, webhook=bell, monitoring=heartbeat, synthetic=sparkles, retention=clock). - Header restructured as a 3-column grid (icon | titles | chevron) so a long hint wraps cleanly without pushing the chevron off-axis. - Open state gets an ember-tinted border + soft shadow + ember icon fill. Chevron rotates 180°. - Focus-visible ring on the header button for keyboard users. - Closed = ``display: none`` on the body wrapper. (Two earlier attempts — grid-template-rows: 0fr → 1fr and max-height: 0 — both failed in real browsers; the body kept its min-content size and every closed accordion rendered as a tall empty frame.) - Override the global ``section { padding: 5rem 0 }`` rule on .wizard-accordion so the section element doesn't grow 80px of vertical padding inside the wizard pane (the actual cause of the "huge gray frame around closed accordions" bug). TR TRANSLATION QUALITY PASS (20 fixes): - Academic words simplified: paradigma → yöntem, muhakeme → akıl yürütme, kanonik → standart. - Mixed-language friction smoothed: VEYA → ya da, feedback → geri bildirim, "Uzunluk-normalize" → "Uzunluk-dengeli". - Grammar cleanups: policy'i → policy'yi (i-uyumu), log-probabilities'leri → log-probability'leri (avoid double plural). - Sentence structure tightened where the EN → TR translation strained. I18N COVERAGE: - 728 EN keys, 728 TR keys — active-tier in lockstep. - DE / FR / ES / ZH carry every wizard key (213 distinct refs including dynamic concat enumerations) so site_chrome_parity reports no new drift beyond the existing GDPR/ISO backlog. - 19 validation error messages in all 6 locales. VERIFIED: - ruff check + JS parse (translations.js + wizard.js) - Doc gates: anchor_resolution, bilingual_parity, cli_help_consistency, site_chrome_parity (EN↔TR lockstep), site_claims — all green. - Schema fuzz: a fully-loaded wizard YAML (every Bundle 4-7 surface populated, high-risk + Annex IV + risk_assessment + governance + webhook + monitoring + synthetic + retention) was fed to the real Pydantic ForgeConfig validator — passes cleanly. - 6 trainer types × multiple risk tiers fuzzed earlier — 20/20 valid. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/css/style.css | 154 ++++ site/js/translations.js | 1350 +++++++++++++++++++++++++++++- site/js/wizard.js | 1733 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 3154 insertions(+), 83 deletions(-) diff --git a/site/css/style.css b/site/css/style.css index 65b29280..288c7c60 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -2205,6 +2205,160 @@ section { padding: 5rem 0; } font: inherit; } +/* Collapsible accordion section inside a step pane. Used to group + advanced / optional fields (risk assessment, data governance, + webhooks, monitoring) without bloating the step counter. The header + is a button so keyboard users can Tab into it; aria-expanded reflects + the open/closed state for assistive tech. + + Layout: header is a 3-column grid (icon | titles | chevron). Title + + hint stack vertically inside the middle cell so a long hint wraps + cleanly without pushing the chevron off-axis. The body uses the + modern grid-template-rows 0fr→1fr trick so expand/collapse + animates smoothly without us manually measuring height. */ +.wizard-accordion { + /* Override the global ``section { padding: 5rem 0 }`` rule (line 193). + Without this, every accordion grew 80px of top + bottom padding, + leaving a tall empty frame around the header. The wizard's + accordions are layout primitives, not page sections — they need + to fit their content tightly. */ + padding: 0; + margin: 0.9rem 0; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-card); + overflow: hidden; + transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition); +} +.wizard-accordion:hover { + border-color: var(--border-strong); +} +.wizard-accordion.is-open { + border-color: rgba(249, 115, 22, 0.5); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(249, 115, 22, 0.08); +} + +.wizard-accordion-header { + width: 100%; + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 0.85rem; + padding: 0.85rem 1.05rem; + background: transparent; + border: none; + cursor: pointer; + font: inherit; + color: var(--text); + text-align: left; + transition: background var(--transition); +} +.wizard-accordion-header:hover { + background: var(--bg-elev); +} +.wizard-accordion-header:focus-visible { + outline: 2px solid var(--ember-bright); + outline-offset: -2px; +} + +/* Leading icon — sits in a soft tinted square. Closed = neutral grey; + open = ember-tinted to mirror the active border. */ +.wizard-accordion-icon { + width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + background: var(--bg-elev); + color: var(--text-secondary); + flex-shrink: 0; + transition: background var(--transition), color var(--transition); +} +.wizard-accordion-icon svg { + width: 16px; + height: 16px; +} +.wizard-accordion.is-open .wizard-accordion-icon { + background: rgba(249, 115, 22, 0.12); + color: var(--ember-bright); +} + +/* Title + hint stack — single column, tight gap so the two lines read + as one logical label. ``min-width: 0`` lets long hints truncate via + the wrapper rather than overflow the row. */ +.wizard-accordion-titles { + display: flex; + flex-direction: column; + gap: 0.18rem; + min-width: 0; +} +.wizard-accordion-title { + font-weight: 600; + font-size: 0.95rem; + line-height: 1.3; + color: var(--text); +} +.wizard-accordion-hint { + font-size: 0.8rem; + color: var(--text-muted); + font-weight: 400; + line-height: 1.45; +} + +.wizard-accordion-chevron { + width: 20px; + height: 20px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + transition: transform 240ms cubic-bezier(.4, 0, .2, 1), color var(--transition); +} +.wizard-accordion-chevron svg { + width: 16px; + height: 16px; +} +.wizard-accordion.is-open .wizard-accordion-chevron { + color: var(--ember-bright); + transform: rotate(180deg); +} + +/* Closed accordion = header only. ``display: none`` is the bluntest + tool but the most reliable: it removes the body-wrap from layout + entirely, so a closed accordion is exactly the height of its + header button. The two prior attempts (grid-template-rows: 0fr; + max-height: 0) both failed in real browsers — content kept + reporting min-content height despite overflow:hidden, leaving + closed accordions as tall empty frames. We forfeit the slide + animation in exchange for visual correctness. The chevron rotation + + open-state ember accent still telegraph state changes. */ +.wizard-accordion-body-wrap { + display: none; +} +.wizard-accordion.is-open .wizard-accordion-body-wrap { + display: block; +} +.wizard-accordion-body { + border-top: 1px solid var(--border); + padding: 1rem 1.05rem 0.9rem; +} + +/* Reduced-motion users get instant chevron / hover transitions. */ +@media (prefers-reduced-motion: reduce) { + .wizard-accordion, + .wizard-accordion-icon, + .wizard-accordion-chevron { + transition: none !important; + } +} +.wizard-input[rows] { /* textareas styled the same as inputs */ + font-family: var(--font-sans); + resize: vertical; + min-height: 60px; +} + /* Validation error pinned under an invalid input. */ .wizard-error { margin-top: 0.4rem; diff --git a/site/js/translations.js b/site/js/translations.js index e23961bf..c0370aec 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -1507,8 +1507,43 @@ "wizard.dataset.pdf.desc": "ForgeLM will ingest these to JSONL before training (PII + secrets masked).", "wizard.dataset.name.label": "Dataset path or name", "wizard.dataset.name.hint": "HuggingFace name (org/dataset), local file path (./data/train.jsonl), or directory (./documents/).", - "wizard.dataset.autodetect.title": "Format is auto-detected.", - "wizard.dataset.autodetect.body": "ForgeLM reads the JSONL fields directly: rows with a messages array become multi-turn chat; rows with prompt + response become instruction-following. Mixed datasets are not supported — the loader picks the shape from the first row.", + "wizard.dataset.shape.sft.title": "Format is auto-detected.", + "wizard.dataset.shape.sft.body": "ForgeLM reads the JSONL fields directly: rows with a messages array become multi-turn chat; rows with prompt + response become instruction-following. Mixed datasets are not supported — the loader picks the shape from the first row.", + "wizard.dataset.shape.preference.title": "Preference rows required.", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO need preference triples on every row: { \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }. The trainer learns to prefer chosen over rejected. Convert your data first if it isn't already in this shape — the runtime will error otherwise.", + "wizard.dataset.shape.kto.title": "Binary-feedback rows required.", + "wizard.dataset.shape.kto.body": "KTO needs single completions with a thumbs-up / thumbs-down label: { \"prompt\": ..., \"completion\": ..., \"label\": true|false }. No paired preferences are needed — KTO works with the kind of data a feedback widget actually produces.", + "wizard.dataset.shape.grpo.title": "GRPO rollout rows.", + "wizard.dataset.shape.grpo.body": "GRPO generates multiple responses per prompt and ranks them with a reward signal. Rows: { \"prompt\": ... }. For math-style problems, add an optional gold_answer field — the built-in regex-correctness reward will use it. Without a reward model the trainer falls back to format/length shaping.", + "wizard.step.trainer.title": "Choose your alignment paradigm", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "Six trainers, one selection. The use-case step picked a sensible default — change it here if you need a different alignment method.", + "wizard.step.trainer.tutorial": "Trainers differ in WHAT they optimise. SFT learns input → output mappings from labelled examples. DPO / ORPO / SimPO compare a chosen response to a rejected one and reinforce the difference. KTO works with single thumbs-up / thumbs-down feedback rather than paired preferences. GRPO samples many responses per prompt and ranks them with a reward — best for reasoning tasks. The trainer you pick determines what shape your dataset must be in.", + "wizard.trainer.recommended": "good default", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "Supervised", + "wizard.trainer.sft.desc": "Fine-tune on labelled input → output examples. The default for chat, instructions, summarisation, and BYOD domain expertise.", + "wizard.trainer.sft.shape": "rows: messages[] OR prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "Preference", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation. Learns from chosen/rejected pairs without an explicit reward model. Common for thumbs-up alignment after SFT.", + "wizard.trainer.dpo.shape": "rows: prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation. Like DPO but without a reference model — combines SFT + alignment in a single pass.", + "wizard.trainer.orpo.shape": "rows: prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "Length-normalised", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation. Reference-free, length-normalised reward — avoids the verbosity bias DPO sometimes shows.", + "wizard.trainer.simpo.shape": "rows: prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "Binary feedback", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation. Works with single thumbs-up / thumbs-down labels — perfect when you have feedback widgets but no paired preferences.", + "wizard.trainer.kto.shape": "rows: prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "Reasoning", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation. Samples multiple responses per prompt and ranks them with a reward signal. Best for math, code, and step-by-step reasoning.", + "wizard.trainer.grpo.shape": "rows: prompt (+ optional gold_answer)", "wizard.step.training.title": "Training parameters", "wizard.step.training.name": "Training", "wizard.step.training.desc": "Defaults work for 90% of cases. Tune only if you know why.", @@ -1531,10 +1566,64 @@ "wizard.training.batch.label": "Batch size (per device)", "wizard.training.batch.hint": "Bigger batches train faster but need more VRAM. Start at 2 if you're not sure.", "wizard.training.batch.detail": "Batch size is how many examples the GPU processes in parallel. Bigger batches mean faster training and more stable gradients, but they also use more VRAM. With QLoRA on a 12 GB GPU you can usually fit batch=2 with an 8B model; if you OOM, drop to 1 and increase gradient accumulation to compensate.", + "wizard.training.lora_method.label": "LoRA variant", + "wizard.training.lora_method.hint": "PiSSA needs more memory but converges faster; RSLoRA stabilises high ranks (r > 64); DoRA decomposes weights for slightly better quality.", + "wizard.training.lora_method.detail": "Standard LoRA is the safe default and fits all 8B-on-12-GB scenarios. DoRA splits the LoRA update into magnitude + direction — small quality lift at modest cost. PiSSA initialises adapters from the principal components of the base weights, which converges faster on small datasets. RSLoRA rescales the alpha factor for high ranks (r=64+) and is the right choice when standard LoRA training plateaus or NaNs.", + "wizard.training.lora_method.standard.title": "Standard", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA. Safe default for everything.", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "Magnitude + direction split. ~5% quality lift.", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "SVD-initialised adapters. Faster convergence on small data.", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rank-stabilised. Use when r > 64 or training is unstable.", + "wizard.training.galore.label": "Use GaLore (full-parameter training, no adapters)", + "wizard.training.galore.hint": "Replaces LoRA with gradient-projected full-parameter training. Lower peak VRAM but slower per step.", + "wizard.training.galore.detail": "GaLore trains every parameter via low-rank gradient projection — no adapter at all. Peak VRAM drops below QLoRA on large models because optimiser state is projected into a low-rank subspace. Compute per step is higher (the projection is computed every few steps), but the result is a fully fine-tuned model with no LoRA merge needed. Use when you have 24 GB+ and want a single deployable model rather than base + adapter.", + "wizard.training.galore_optim.label": "GaLore optimiser variant", + "wizard.training.galore_optim.hint": "_8bit halves optimiser-state VRAM; _layerwise cuts peak by recomputing per-layer.", + "wizard.training.galore_optim.detail": "GaLore swaps in for the standard optimizer. _8bit variants quantise the optimiser state to 8-bit (huge VRAM savings, no real quality loss). _layerwise variants compute the GaLore projection one layer at a time instead of all layers at once — slower but lets you train models that would otherwise OOM. Default galore_adamw_8bit is the right choice for most setups.", + "wizard.training.galore_optim.adamw": "AdamW (full precision)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-bit (recommended)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW layerwise", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-bit layerwise", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor layerwise", + "wizard.training.trainer_section.title": "{trainer} hyperparameters", + "wizard.training.trainer_section.body": "These knobs are specific to your selected trainer. Defaults are sensible — only adjust if you understand what they do and have data to justify the change.", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "Temperature controlling KL deviation from the reference model. 0.1 is the canonical default.", + "wizard.training.dpo_beta.detail": "DPO penalises the policy for drifting away from a reference model. Beta scales that penalty — lower beta lets the model change faster (and risk overfitting to preferences); higher beta keeps it close to the reference. The DPO paper recommends 0.1 as a starting point. Tune to 0.05–0.2 only after measuring the effect on a held-out preference set.", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Odds-ratio weight. 0.1 is the schema default; higher values penalise rejected outputs more aggressively.", + "wizard.training.orpo_beta.detail": "ORPO replaces DPO's reference model with an odds-ratio term in the SFT loss. Beta controls how strongly the rejected response's likelihood is penalised relative to the chosen one. 0.1 is the published default; bump to 0.2–0.3 only if you observe the model still preferring rejected-style responses.", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Margin term enforcing minimum reward difference between chosen and rejected. 0.5 is the published default.", + "wizard.training.simpo_gamma.detail": "Gamma sets a minimum margin between the chosen and rejected log-probabilities. The trainer is rewarded for keeping at least this much gap. Larger gamma = more aggressive separation, but the loss can stall if no preference pair exceeds the margin. Stick with 0.5 unless you've measured chosen/rejected gap distributions.", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "Reward scaling factor. Multiplies log-probs before the margin comparison. 2.0 is the published default.", + "wizard.training.simpo_beta.detail": "Beta scales the per-token reward signal before the gamma margin is applied. Higher beta amplifies the difference between chosen and rejected sequences (sharper preference learning) but can also amplify noise. The SimPO paper sets beta = 2.0 with gamma = 0.5; tune as a pair, not independently.", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "KL-divergence weight against the reference model. 0.1 default mirrors DPO.", + "wizard.training.kto_beta.detail": "KTO is closer to RLHF in spirit than DPO — it shapes the loss using prospect theory's loss-aversion asymmetry, scaled by beta. The default 0.1 is calibrated for typical thumbs-up / thumbs-down feedback ratios. Lower it if your dataset is heavily skewed toward one label.", + "wizard.training.grpo_num_generations.label": "Generations per prompt", + "wizard.training.grpo_num_generations.hint": "Number of responses GRPO samples per prompt before ranking. 4 is balanced; 8 is higher-quality but slower.", + "wizard.training.grpo_num_generations.detail": "GRPO ranks N sampled responses against each other to build the policy gradient. More samples = better-conditioned gradient and faster convergence per step, but each step costs N forward passes. 4 is the sweet spot for an 8B model on a single GPU; bump to 8 for math/reasoning tasks where ranking quality matters.", + "wizard.training.grpo_max_completion_length.label": "Max completion length", + "wizard.training.grpo_max_completion_length.hint": "Token cap on each generated response. 512 covers most reasoning chains; raise for long-form math.", + "wizard.training.grpo_max_completion_length.detail": "GRPO truncates generated rollouts at this token count. Shorter caps mean faster steps but can truncate reasoning mid-thought; longer caps explore more but waste compute on padding. 512 is a balanced default — for GSM8K-style math you can drop to 384, for olympiad-level problems push to 1024+.", + "wizard.training.grpo_reward_model.label": "Reward model (optional)", + "wizard.training.grpo_reward_model.hint": "HuggingFace Hub ID of a reward model to score generations. Leave blank to use the built-in format/length-shaping reward.", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "When you provide a reward-model ID, GRPO uses it to score each rollout — that's the standard recipe for RLHF-style alignment. Leave blank and ForgeLM falls back to the built-in combined_format_length_reward: a gradient-rich format/length signal that needs no extra model. For math problems, also adding a gold_answer field to your dataset triggers a regex-correctness reward on top.", "wizard.step.compliance.title": "Evaluation & EU AI Act", "wizard.step.compliance.name": "Evaluation & compliance", "wizard.step.compliance.desc": "Quality gates and regulatory artefacts.", "wizard.step.compliance.tutorial": "ForgeLM checks the fine-tuned model BEFORE you ship it. Auto-revert undoes the fine-tune if it's worse than the base. Llama Guard scans outputs for harmful content. The EU AI Act risk tier you pick determines which compliance artefacts get auto-generated and whether human approval is REQUIRED before deployment.", + "wizard.step.operations.title": "Operations & advanced settings", + "wizard.step.operations.name": "Operations", + "wizard.step.operations.desc": "Notifications, post-market monitoring, synthetic data generation, and retention horizons. All optional — skip the ones you don't need.", + "wizard.step.operations.tutorial": "These four blocks cover everything ForgeLM ships beyond the core training + compliance flow. Webhook posts a Slack/Teams/Discord alert when training starts, succeeds, or fails. Post-market monitoring (Article 12+17) wires drift detection into Prometheus / Datadog after deployment. Synthetic data generation lets a stronger teacher model produce extra training examples. Retention horizons set GDPR-compliant TTLs for the audit log and intermediate artefacts. None of these are required — leave them collapsed if your run doesn't need them.", "wizard.risk.label": "EU AI Act risk classification", "wizard.risk.hint": "Pick the tier your deployment falls under. High-risk and unacceptable trigger mandatory audit + human-approval obligations.", "wizard.risk.unknown": "Unknown — pre-classification placeholder", @@ -1569,6 +1658,114 @@ "wizard.compliance.intended_purpose.label": "Intended purpose", "wizard.compliance.intended_purpose.hint": "Annex IV §1: declared purpose of the system in plain language.", "wizard.compliance.intended_purpose.placeholder":"Triage customer support tickets via chat", + "wizard.risk_assessment.title": "Risk assessment (EU AI Act Article 9)", + "wizard.risk_assessment.hint": "Required for high-risk systems. Optional but recommended otherwise.", + "wizard.risk_assessment.intended_use.label": "Intended use", + "wizard.risk_assessment.intended_use.hint": "Article 9(2)(a): describe the system's intended purpose in plain language.", + "wizard.risk_assessment.intended_use.placeholder": "A customer support assistant that triages tickets and drafts replies.", + "wizard.risk_assessment.intended_use.detail": "This is the canonical statement of what your system is built to do — it anchors the rest of the risk assessment. Be specific: \"triage support tickets\" is better than \"help users\". Reviewers and regulators read this first to decide whether downstream mitigations are appropriate.", + "wizard.risk_assessment.foreseeable_misuse.label": "Foreseeable misuse (one per line)", + "wizard.risk_assessment.foreseeable_misuse.hint": "Article 9(2)(b): scenarios where the system could be misused.", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "Generating phishing emails\nSocial-engineering attacks\nBypassing safety guardrails via prompt injection", + "wizard.risk_assessment.foreseeable_misuse.detail": "List the misuse scenarios you can reasonably anticipate — not every theoretical attack, but the ones that would matter for your deployment context. The standard exercise: imagine a hostile but lazy attacker. What's the easiest way they would abuse this? Each line in the textarea becomes one item in the YAML list.", + "wizard.risk_assessment.mitigation_measures.label": "Mitigation measures (one per line)", + "wizard.risk_assessment.mitigation_measures.hint": "Article 9(2)(c): controls you put in place against the misuse risks above.", + "wizard.risk_assessment.mitigation_measures.placeholder": "Llama Guard safety filter on all outputs\nRate limiting per user\nHuman approval for high-risk responses", + "wizard.risk_assessment.mitigation_measures.detail": "For each foreseeable misuse, name a concrete mitigation. Reviewers compare this list against the misuse list — gaps are a red flag. Each line becomes one YAML list item. ForgeLM's built-in safety eval and human-approval gates are good first-line entries here.", + "wizard.risk_assessment.vulnerable_groups.label": "Considered impact on vulnerable groups", + "wizard.risk_assessment.vulnerable_groups.hint": "Article 9(2)(b): check if you've considered effects on children, minorities, etc.", + "wizard.risk_assessment.vulnerable_groups.detail": "Article 9 explicitly calls out vulnerable groups (children, the elderly, minorities, persons with disabilities). Tick this only after you've actually thought through how your system might affect them — false certification is worse than no certification.", + "wizard.eval_gates.title": "Quality gates (advanced)", + "wizard.eval_gates.hint": "Optional benchmark + LLM-as-Judge scoring on top of the basic loss/safety gates.", + "wizard.benchmark.enabled.label": "Run lm-evaluation-harness benchmark", + "wizard.benchmark.enabled.hint": "Score the fine-tuned model on academic tasks (ARC, HellaSwag, MMLU, etc.) after training.", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness is the de-facto standard for LLM benchmarking. Enabling this runs the listed tasks against your fine-tuned model and stores JSON results next to the checkpoint. Combined with auto_revert + min_score, you get a hard quality gate: the trainer rolls back if benchmark accuracy drops below your floor.", + "wizard.benchmark.tasks.label": "Tasks (one per line)", + "wizard.benchmark.tasks.hint": "Common: arc_easy, arc_challenge, hellaswag, mmlu, truthfulqa, winogrande, gsm8k.", + "wizard.benchmark.min_score.label": "Minimum average accuracy", + "wizard.benchmark.min_score.hint": "0.0–1.0. Below this, auto_revert undoes the fine-tune. Leave blank to skip the threshold gate.", + "wizard.benchmark.min_score.placeholder": "e.g. 0.55", + "wizard.judge.enabled.label": "LLM-as-Judge scoring", + "wizard.judge.enabled.hint": "Score generations with a stronger model (e.g. GPT-4o) on an evaluation prompt set.", + "wizard.judge.enabled.detail": "LLM-as-Judge prompts a stronger model to grade your fine-tuned model's outputs on a 1–10 scale. Better than benchmarks for chat / instruction quality where there's no single correct answer. Costs money per run (judge API calls), so usually configured for production gates rather than every dev run.", + "wizard.judge.model.label": "Judge model", + "wizard.judge.model.hint": "API model name (gpt-4o, claude-opus-4-7) or local model path. The judge needs to be substantially stronger than the model under test.", + "wizard.judge.api_key_env.label": "API key env var", + "wizard.judge.api_key_env.hint": "Environment variable name carrying the API key. Empty = local judge model (no API call).", + "wizard.judge.min_score.label": "Minimum average judge score", + "wizard.judge.min_score.hint": "1–10 scale. Below this triggers auto_revert. 5.0 is \"average\"; 7.0 is \"good\".", + "wizard.governance.title": "Data governance (Article 10)", + "wizard.governance.hint": "Document data provenance + biases. Required for high-risk deployments.", + "wizard.governance.collection_method.label": "Collection method", + "wizard.governance.collection_method.hint": "Article 10(2)(b): how the training data was sourced.", + "wizard.governance.collection_method.placeholder": "Scraped public web archives 2023–2025, deduplicated by URL hash.", + "wizard.governance.collection_method.detail": "Tell reviewers HOW your data ended up in the trainer. \"Customer chat logs from , exported with consent\" or \"Synthetic dialogues generated by GPT-4\". Vague answers (\"the internet\") fail Article 10 audits.", + "wizard.governance.annotation_process.label": "Annotation process", + "wizard.governance.annotation_process.hint": "Article 10(2)(b): labelling methodology — who labelled, with what guidelines.", + "wizard.governance.annotation_process.placeholder": "Two trained annotators labelled each example; conflicts resolved by senior review.", + "wizard.governance.annotation_process.detail": "If the data is labelled (preference pairs, classification tags, gold answers), describe how. Inter-annotator agreement, guidelines version, conflict resolution. For SFT on raw text, \"no annotation\" is a valid answer.", + "wizard.governance.known_biases.label": "Known biases", + "wizard.governance.known_biases.hint": "Article 10(2)(f): biases the operator is aware of in the data.", + "wizard.governance.known_biases.placeholder": "Predominantly English; underrepresented age groups <18 and >65; Western legal-system framings.", + "wizard.governance.known_biases.detail": "No dataset is perfectly balanced. Naming the imbalances upfront is the honest answer — and meeting it shows due diligence to reviewers. Under-represented demographics, language skew, time-period skew, source bias all qualify.", + "wizard.governance.personal_data.label": "Training data contains personal data", + "wizard.governance.personal_data.hint": "Article 10(5) + GDPR: tick if any examples include identifiable individuals.", + "wizard.governance.dpia_completed.label": "DPIA completed (GDPR Art. 35)", + "wizard.governance.dpia_completed.hint": "Data Protection Impact Assessment finished and documented for this dataset.", + "wizard.webhook.title": "Notifications (webhook)", + "wizard.webhook.hint": "Send Slack / Teams / Discord alerts on training events.", + "wizard.webhook.url.label": "Webhook URL or env var", + "wizard.webhook.url.hint": "Direct https URL or an env var name carrying it (recommended for secrets).", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... OR env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "You can paste the URL directly (fine for non-secret test channels) or use the env: prefix to point at an environment variable. The env-var route keeps the URL out of YAML and out of the audit log — required for production secrets.", + "wizard.webhook.enabled.label": "Send webhook notifications", + "wizard.webhook.enabled.hint": "When on, ForgeLM posts a webhook payload at the events you select. Off keeps the YAML free of any webhook block.", + "wizard.webhook.enabled.detail": "Notifications wire training events into your team chat (Slack / Teams / Discord) or any HTTPS endpoint that accepts JSON. Audit log entries record every dispatch — including failures — so you have an independent trail of what was sent. Leave off if you don't want the YAML to carry webhook config at all.", + "wizard.webhook.notify_start.label": "Notify on training start", + "wizard.webhook.notify_success.label": "Notify on success", + "wizard.webhook.notify_failure.label": "Notify on failure", + "wizard.monitoring.title": "Post-market monitoring (Art. 12+17)", + "wizard.monitoring.hint": "Wire training metrics into Prometheus / Datadog / a custom webhook for ongoing observability.", + "wizard.monitoring.enabled.label": "Enable monitoring hooks", + "wizard.monitoring.enabled.hint": "Required for high-risk systems under EU AI Act Articles 12 + 17.", + "wizard.monitoring.enabled.detail": "Once a high-risk system is deployed, the EU AI Act requires ongoing observation — drift detection, error reporting, periodic accuracy re-checks. ForgeLM doesn't run the monitoring itself, but it wires the right hooks into the training run so your existing observability stack picks them up.", + "wizard.monitoring.endpoint.label": "Monitoring endpoint", + "wizard.monitoring.endpoint.hint": "URL of your Prometheus push gateway, Datadog, or custom webhook receiver.", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics OR env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "Metrics format", + "wizard.monitoring.metrics_export.none": "None", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "Custom webhook", + "wizard.monitoring.alert_on_drift.label": "Alert on drift", + "wizard.monitoring.alert_on_drift.hint": "Emit a webhook alert when the drift detector flags a regression.", + "wizard.synthetic.title": "Synthetic data generation (advanced)", + "wizard.synthetic.hint": "Generate teacher → student training data using a stronger model.", + "wizard.synthetic.enabled.label": "Generate synthetic data", + "wizard.synthetic.enabled.hint": "Run a teacher model over your seed prompts to produce training examples.", + "wizard.synthetic.enabled.detail": "Synthetic data generation lets a stronger model (e.g. GPT-4) produce training examples for your smaller fine-tune target. Useful when real data is scarce or sensitive. ForgeLM tracks lineage in the audit log so the synthetic origin of each example is verifiable.", + "wizard.synthetic.teacher_model.label": "Teacher model", + "wizard.synthetic.teacher_model.hint": "API model name (gpt-4o, claude-opus-4-7) or local model path.", + "wizard.synthetic.seed_file.label": "Seed prompts file", + "wizard.synthetic.seed_file.hint": "JSONL or plain-text file of seed prompts. The teacher generates a response for each.", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Teacher API key env var", + "wizard.synthetic.api_key_env.hint": "Environment variable name carrying the teacher API key. Empty = local teacher model.", + "wizard.retention.title": "Retention horizons (GDPR Art. 5+17)", + "wizard.retention.hint": "How long ForgeLM keeps audit logs, staging models, and ephemeral artefacts.", + "wizard.retention.audit_log.label": "Audit log retention (days)", + "wizard.retention.audit_log.hint": "How long to keep audit_log.jsonl. Default 1825 (5 years) matches EU AI Act guidance.", + "wizard.retention.staging.label": "Staging horizon (days)", + "wizard.retention.staging.hint": "How long to keep rejected models in final_model.staging/. Default 7.", + "wizard.retention.ephemeral.label": "Ephemeral artefacts (days)", + "wizard.retention.ephemeral.hint": "How long to keep intermediate checkpoints, eval samples. Default 90.", + "wizard.retention.raw_documents.label": "Raw documents (days)", + "wizard.retention.raw_documents.hint": "How long to keep raw PDFs / DOCX after ingestion. Default 90.", + "wizard.retention.enforce.label": "Enforce automatic deletion", + "wizard.retention.enforce.hint": "When set, ForgeLM auto-deletes artefacts past their horizon. Off by default — enabling requires GDPR sign-off.", + "wizard.retention.enforce.log_only": "log_only — log violations to audit (default)", + "wizard.retention.enforce.warn": "warn_on_excess — log + stderr warning", + "wizard.retention.enforce.block": "block_on_excess — abort trainer (regulated CI)", "wizard.step.review.title": "Review your config", "wizard.step.review.name": "Review", "wizard.step.review.desc": "Here's the YAML. Copy it, download it, and run.", @@ -1578,16 +1775,35 @@ "wizard.review.reset": "Reset wizard", "wizard.review.reset.confirm":"Reset all wizard answers and start over? Your YAML will be lost.", "wizard.review.run_label": "Then run:", + "wizard.error.model.required": "You picked “Custom HuggingFace model” on the previous step, so this field is required. Enter the org/repo ID (e.g. meta-llama/Llama-3.1-8B-Instruct) or go back and pick one of the bundled presets.", + "wizard.error.model.format": "HuggingFace Hub IDs use the org/repo shape (e.g. meta-llama/Llama-3.1-8B-Instruct). A bare name without a slash won’t resolve at training time.", + "wizard.error.dataset.required": "Pick a dataset before continuing — without a path or HuggingFace name the trainer has nothing to learn from.", + "wizard.error.webhook.required": "You enabled notifications, so we need a webhook URL or env var to send them to. Either turn notifications off above or paste your Slack / Teams / Discord URL (or env:SLACK_WEBHOOK_URL for production secrets).", + "wizard.error.monitoring.endpoint.required": "You enabled post-market monitoring, so we need an endpoint to push metrics to. Either turn monitoring off above or paste your Prometheus / Datadog / custom-webhook URL (use env:VAR_NAME for secrets).", + "wizard.error.synthetic.teacher_model.required": "You enabled synthetic data generation, so we need a teacher model name (e.g. gpt-4o, claude-opus-4-7, or a local model path).", + "wizard.error.synthetic.seed_file.required": "You enabled synthetic data generation, so we need a seed prompts file — the teacher will produce a response for each prompt in it (e.g. ./data/seed_prompts.jsonl).", + "wizard.error.synthetic.api_key_env.required": "You enabled synthetic data generation, so we need the env var that carries the teacher API key — typically OPENAI_API_KEY or ANTHROPIC_API_KEY. Leave the synthetic toggle off if you don’t have one.", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1 requires the provider’s legal entity name for high-risk systems. Fill it in or lower the risk classification.", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1 requires a human-readable system name for high-risk deployments. Fill it in or lower the risk classification.", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1 requires a stated intended purpose for high-risk systems. Describe the purpose in plain language or lower the risk classification.", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act Article 9 requires the intended use to be documented for high-risk systems. Describe what the system is meant to do or lower the risk classification.", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act Article 9 requires foreseeable-misuse scenarios for high-risk systems. List them one per line or lower the risk classification.", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act Article 9 requires mitigation measures for high-risk systems. List the controls you’ve put in place (one per line) or lower the risk classification.", + "wizard.error.governance.collection_method.required": "EU AI Act Article 10 requires data-provenance documentation for high-risk systems. Describe how the training data was sourced or lower the risk classification.", + "wizard.error.governance.annotation_process.required": "EU AI Act Article 10 requires annotation-methodology documentation. Describe your labelling process (or “no annotation” for SFT on raw text) or lower the risk classification.", + "wizard.error.governance.known_biases.required": "EU AI Act Article 10 requires known dataset biases to be documented for high-risk systems. List the imbalances you’re aware of or lower the risk classification.", + "wizard.error.benchmark.tasks.required": "You enabled the benchmark gate, so we need at least one task. Add lm-evaluation-harness names like arc_easy, hellaswag, mmlu (one per line) or disable the benchmark above.", + "wizard.error.judge.api_key_env.required": "You enabled LLM-as-Judge, so we need the env var that carries the judge API key — typically OPENAI_API_KEY or ANTHROPIC_API_KEY.", "quickstart.spotlight.eyebrow": "Skip the YAML", "quickstart.spotlight.title": "Use the in-browser wizard to build your config in 3 minutes.", "quickstart.spotlight.lead": "Pick your use case, base model, dataset, and EU AI Act risk tier. The wizard writes a working quickstart-generated.yaml you can run with one command — no fine-tuning expertise required.", "quickstart.spotlight.cta_primary": "Open the wizard", "quickstart.spotlight.cta_secondary":"Or follow the 5-step guide", - "quickstart.spotlight.meta1": "7 guided steps", + "quickstart.spotlight.meta1": "9 guided steps", "quickstart.spotlight.meta2": "Live YAML preview", "quickstart.spotlight.meta3": "EU AI Act presets", - "quickstart.spotlight.stage1.label": "Answer 7 questions", - "quickstart.spotlight.stage1.snippet":"use case · model · data · risk", + "quickstart.spotlight.stage1.label": "Answer 9 questions", + "quickstart.spotlight.stage1.snippet":"use case · trainer · model · data · risk", "quickstart.spotlight.stage2.label": "Get a valid YAML", "quickstart.spotlight.stage3.label": "Run it", "quickstart.spotlight.manual_intro": "Or follow the manual 5-step reference below" @@ -1685,8 +1901,43 @@ "wizard.dataset.pdf.desc": "ForgeLM eğitimden önce JSONL'a ingest eder (PII + secrets maskeli).", "wizard.dataset.name.label": "Veri seti yolu veya adı", "wizard.dataset.name.hint": "HuggingFace adı (org/dataset), yerel dosya yolu (./data/train.jsonl) veya dizin (./documents/).", - "wizard.dataset.autodetect.title": "Format otomatik algılanır.", - "wizard.dataset.autodetect.body": "ForgeLM, JSONL alanlarını doğrudan okur: messages dizisi olan satırlar çok turlu sohbet olur; prompt + response olan satırlar talimat-takip olur. Karışık veri setleri desteklenmez — yükleyici şekli ilk satırdan seçer.", + "wizard.dataset.shape.sft.title": "SFT formatı otomatik algılanır.", + "wizard.dataset.shape.sft.body": "ForgeLM, JSONL alanlarını doğrudan okur: messages dizisi olan satırlar çok turlu sohbet olur; prompt + response olan satırlar talimat-takip olur. Karışık veri setleri desteklenmez — yükleyici şekli ilk satırdan seçer.", + "wizard.dataset.shape.preference.title": "Tercih satırları gerekir.", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO her satırda tercih üçlüsü ister: { \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }. Trainer chosen'ı rejected'a tercih etmeyi öğrenir. Veriniz bu şekilde değilse önce dönüştürün — runtime aksi halde hata verir.", + "wizard.dataset.shape.kto.title": "İkili-feedback satırları gerekir.", + "wizard.dataset.shape.kto.body": "KTO, beğeni / beğenmeme etiketli tek tamamlamalar ister: { \"prompt\": ..., \"completion\": ..., \"label\": true|false }. Eşli tercih gerekmez — KTO, bir geri bildirim widget'ının ürettiği veriyle çalışır.", + "wizard.dataset.shape.grpo.title": "GRPO rollout satırları.", + "wizard.dataset.shape.grpo.body": "GRPO her prompt için birden çok yanıt üretir ve bunları reward sinyaliyle sıralar. Satırlar: { \"prompt\": ... }. Matematik benzeri problemler için opsiyonel gold_answer alanı ekleyin — yerleşik regex-correctness reward bunu kullanır. Reward modeli olmadığında trainer format/length shaping'e düşer.", + "wizard.step.trainer.title": "Hizalama yöntemini seç", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "Altı trainer, tek seçim. Kullanım senaryosuna göre makul bir varsayılan seçildi — başka bir hizalama yöntemi gerekiyorsa burada değiştir.", + "wizard.step.trainer.tutorial": "Trainer'lar NEYİ optimize ettiklerine göre farklılaşır. SFT etiketli örneklerden input → output eşlemesi öğrenir. DPO / ORPO / SimPO seçili bir yanıtı reddedilen bir yanıtla karşılaştırır ve farkı pekiştirir. KTO eşli tercih yerine tekil beğeni / beğenmeme geri bildirimiyle çalışır. GRPO her prompt için birden çok yanıt örnekler ve bunları reward ile sıralar — akıl yürütme görevleri için en iyi seçim. Seçtiğin trainer, dataset'inin hangi formatta olması gerektiğini belirler.", + "wizard.trainer.recommended": "önerilen", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "Denetimli", + "wizard.trainer.sft.desc": "Etiketli input → output örneklerinde fine-tune. Sohbet, talimat, özet ve BYOD alan uzmanlığı için varsayılan.", + "wizard.trainer.sft.shape": "satır: messages[] ya da prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "Tercih", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation. Belirgin bir reward modeli olmadan chosen/rejected çiftlerinden öğrenir. SFT sonrası beğeni hizalaması için yaygın.", + "wizard.trainer.dpo.shape": "satır: prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation. DPO'ya benzer ama referans modeli olmadan — SFT + alignment'ı tek geçişte birleştirir.", + "wizard.trainer.orpo.shape": "satır: prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "Uzunluk-dengeli", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation. Reference-free, uzunluğa göre normalize edilmiş reward — DPO'nun bazen sergilediği verbosity bias'ından kaçınır.", + "wizard.trainer.simpo.shape": "satır: prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "İkili feedback", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation. Tekil beğeni / beğenmeme etiketleriyle çalışır — geri bildirim widget'ların var ama eşli tercih verin yoksa ideal.", + "wizard.trainer.kto.shape": "satır: prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "Akıl yürütme", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation. Her prompt için birden çok yanıt örnekler ve bunları reward sinyaliyle sıralar. Matematik, kod ve adım-adım akıl yürütme için en iyi.", + "wizard.trainer.grpo.shape": "satır: prompt (+ opsiyonel gold_answer)", "wizard.step.training.title": "Eğitim parametreleri", "wizard.step.training.name": "Eğitim", "wizard.step.training.desc": "Varsayılanlar vakaların %90'ı için iş görür. Sebebini biliyorsan elle ayarla.", @@ -1709,10 +1960,64 @@ "wizard.training.batch.label": "Batch size (cihaz başına)", "wizard.training.batch.hint": "Daha büyük batch'ler daha hızlı eğitir ama daha çok VRAM ister. Emin değilsen 2 ile başla.", "wizard.training.batch.detail": "Batch size, GPU'nun paralel olarak işlediği örnek sayısıdır. Daha büyük batch'ler daha hızlı eğitim ve daha kararlı gradyan demek, ama daha fazla VRAM kullanır. 12 GB GPU'da QLoRA ile 8B modelde genelde batch=2 sığar; OOM alırsan 1'e düşür ve gradient accumulation'ı artır.", + "wizard.training.lora_method.label": "LoRA varyantı", + "wizard.training.lora_method.hint": "PiSSA daha fazla bellek ister ama daha hızlı yakınsar; RSLoRA yüksek rank'leri (r > 64) stabilize eder; DoRA ağırlıkları ayrıştırarak biraz daha kalite verir.", + "wizard.training.lora_method.detail": "Standart LoRA güvenli varsayılan, 8B-12GB senaryolarına uyar. DoRA, LoRA güncellemesini büyüklük + yön olarak ayırır — modest maliyetle küçük kalite artışı. PiSSA adapter'leri base ağırlıklarının principal component'lerinden başlatır, küçük dataset'lerde daha hızlı yakınsar. RSLoRA yüksek rank'lerde (r=64+) alpha'yı yeniden ölçeklendirir; standart LoRA plato çizip NaN ürettiğinde doğru seçim.", + "wizard.training.lora_method.standard.title": "Standart", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA. Her şey için güvenli varsayılan.", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "Büyüklük + yön ayrımı. ~%5 kalite artışı.", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "SVD-başlatmalı adapter'ler. Küçük veride hızlı yakınsama.", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rank-stabilize. r > 64 veya eğitim kararsızken kullan.", + "wizard.training.galore.label": "GaLore kullan (tam-parametre eğitim, adapter yok)", + "wizard.training.galore.hint": "LoRA'yı gradient-projected tam-parametre eğitimle değiştirir. Daha düşük tepe VRAM ama adım başına daha yavaş.", + "wizard.training.galore.detail": "GaLore tüm parametreleri low-rank gradient projeksiyonla eğitir — hiç adapter yok. Optimizer state low-rank altuzaya projekte edildiği için büyük modellerde tepe VRAM QLoRA'nın altına düşer. Adım başına compute daha yüksek (projeksiyon birkaç adımda bir hesaplanır), ama sonuç tamamen fine-tune edilmiş bir modeldir, LoRA merge gerektirmez. 24 GB+ VRAM'in varsa ve base + adapter yerine tek deployable model istiyorsan kullan.", + "wizard.training.galore_optim.label": "GaLore optimizer varyantı", + "wizard.training.galore_optim.hint": "_8bit optimizer-state VRAM'ini yarıya indirir; _layerwise tepe VRAM'i katmanları tek tek hesaplayarak düşürür.", + "wizard.training.galore_optim.detail": "GaLore standart optimizer'ı değiştirir. _8bit varyantları optimizer state'i 8-bit'e kuantize eder (büyük VRAM tasarrufu, gerçek kalite kaybı yok). _layerwise varyantları GaLore projeksiyonunu tüm katmanlar yerine her seferinde tek katman için hesaplar — daha yavaş ama OOM olmadan büyük modeller eğitir. Çoğu kurulum için varsayılan galore_adamw_8bit doğru seçim.", + "wizard.training.galore_optim.adamw": "AdamW (tam hassasiyet)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-bit (önerilen)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW katman-bazlı", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-bit katman-bazlı", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor katman-bazlı", + "wizard.training.trainer_section.title": "{trainer} hiperparametreleri", + "wizard.training.trainer_section.body": "Bu ayarlar seçtiğin trainer'a özel. Varsayılanlar makul — sadece ne yaptığını anlayıp veriyle gerekçelendirebiliyorsan değiştir.", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "Referans modelden KL sapmasını kontrol eden sıcaklık. 0.1 standart varsayılan.", + "wizard.training.dpo_beta.detail": "DPO, policy'yi referans modelden uzaklaştığı için cezalandırır. Beta bu cezayı ölçeklendirir — düşük beta modelin daha hızlı değişmesine izin verir (ve tercihlere overfit etme riski artar); yüksek beta modeli referansa yakın tutar. DPO makalesi başlangıç noktası olarak 0.1 önerir. Held-out tercih setinde etkisini ölçtükten sonra 0.05–0.2 aralığında ayarla.", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Odds-ratio ağırlığı. 0.1 schema varsayılanı; daha yüksek değerler reddedilen çıktıları daha agresif cezalandırır.", + "wizard.training.orpo_beta.detail": "ORPO, DPO'nun referans modelini SFT loss'una bir odds-ratio terimiyle değiştirir. Beta, reddedilen yanıtın likelihood'unun chosen olana göre ne kadar güçlü cezalandırılacağını kontrol eder. 0.1 yayınlanan varsayılan; modelin hâlâ rejected-style yanıtları tercih ettiğini gözlemliyorsan 0.2–0.3'e çıkar.", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Chosen ve rejected arası minimum reward farkını zorunlu kılan margin. 0.5 yayınlanan varsayılan.", + "wizard.training.simpo_gamma.detail": "Gamma, chosen ve rejected log-probability'leri arasında minimum bir margin belirler. Trainer en az bu kadar boşluk tutmak için ödüllendirilir. Daha büyük gamma = daha agresif ayrım, ama hiçbir tercih çifti margin'i aşamazsa loss takılabilir. Chosen/rejected gap dağılımlarını ölçmediysen 0.5'te kal.", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "Reward ölçek faktörü. Margin karşılaştırması öncesi log-prob'ları çarpar. 2.0 yayınlanan varsayılan.", + "wizard.training.simpo_beta.detail": "Beta, gamma margin'i uygulanmadan önce token-başı reward sinyalini ölçeklendirir. Yüksek beta chosen ve rejected dizileri arası farkı büyütür (daha keskin tercih öğrenimi) ama gürültüyü de büyütebilir. SimPO makalesi beta = 2.0 ile gamma = 0.5 kullanır; çift olarak ayarla, ayrı ayrı değil.", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "Referans modele karşı KL-divergence ağırlığı. 0.1 varsayılanı DPO'yu yansıtır.", + "wizard.training.kto_beta.detail": "KTO yaklaşım olarak DPO'dan çok RLHF'e yakın — prospect theory'nin loss-aversion asimetrisini kullanarak loss'u şekillendirir, beta ile ölçeklendirir. Varsayılan 0.1 tipik beğeni / beğenmeme geri bildirim oranları için kalibre edilmiş. Veri setin bir etikete ağır basıyorsa düşür.", + "wizard.training.grpo_num_generations.label": "Prompt başına üretim sayısı", + "wizard.training.grpo_num_generations.hint": "GRPO'nun sıralamadan önce her prompt için ürettiği yanıt sayısı. 4 dengeli; 8 daha kaliteli ama yavaş.", + "wizard.training.grpo_num_generations.detail": "GRPO, policy gradient'ı kurmak için N örneklenmiş yanıtı birbirine karşı sıralar. Daha fazla örnek = adım başına daha iyi gradient ve daha hızlı yakınsama, ama her adım N forward pass maliyetinde. 8B model için tek GPU'da 4 sweet spot; matematik/akıl yürütmede sıralama kalitesi önemliyse 8'e çıkar.", + "wizard.training.grpo_max_completion_length.label": "Maksimum completion uzunluğu", + "wizard.training.grpo_max_completion_length.hint": "Üretilen her yanıt için token limiti. 512 muhakeme zincirlerinin çoğunu kapsar; uzun matematik için artır.", + "wizard.training.grpo_max_completion_length.detail": "GRPO üretilen rollout'ları bu token sayısında keser. Kısa limitler daha hızlı adım demektir ama akıl yürütmeyi yarıda kesebilir; uzun limitler daha çok keşfeder ama padding'e compute harcar. 512 dengeli varsayılan — GSM8K-style matematik için 384'e düşürebilirsin, olimpiyat seviyesi için 1024+ çıkar.", + "wizard.training.grpo_reward_model.label": "Reward model (opsiyonel)", + "wizard.training.grpo_reward_model.hint": "Üretimleri puanlayacak bir reward modelinin HuggingFace Hub ID'si. Yerleşik format/length-shaping reward için boş bırak.", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "Bir reward-model ID verdiğinde GRPO her rollout'u puanlamak için onu kullanır — bu standart RLHF-style alignment reçetesidir. Boş bırakırsan ForgeLM yerleşik combined_format_length_reward'ı devreye sokar: ek model gerektirmeyen, gradient-zengin format/length sinyali. Matematik problemleri için dataset'e gold_answer alanı eklemek üstüne bir regex-correctness reward da tetikler.", "wizard.step.compliance.title": "Değerlendirme & EU AI Act", "wizard.step.compliance.name": "Değerlendirme & uyumluluk", "wizard.step.compliance.desc": "Kalite kapıları ve regülasyon artefaktları.", "wizard.step.compliance.tutorial": "ForgeLM, fine-tune edilmiş modeli teslim etmeden ÖNCE kontrol eder. Auto-revert, fine-tune base'den kötüyse geri alır. Llama Guard çıktılarda zararlı içerik tarar. Seçtiğin EU AI Act risk seviyesi hangi uyumluluk artefaktlarının otomatik üretileceğini ve dağıtım öncesi insan onayının ZORUNLU olup olmayacağını belirler.", + "wizard.step.operations.title": "Operasyon ve gelişmiş ayarlar", + "wizard.step.operations.name": "Operasyon", + "wizard.step.operations.desc": "Bildirimler, post-market monitoring, sentetik veri üretimi ve saklama ufukları. Hepsi opsiyonel — ihtiyacın olmayanları atla.", + "wizard.step.operations.tutorial": "Bu dört blok, ForgeLM'in temel eğitim + compliance akışının ötesinde sağladığı her şeyi kapsar. Webhook, eğitim başladığında / başarılı olduğunda / hata verdiğinde Slack/Teams/Discord uyarısı gönderir. Post-market monitoring (Madde 12+17) deploy sonrasında drift detection'ı Prometheus / Datadog'a bağlar. Sentetik veri üretimi, daha güçlü bir teacher modelin ek eğitim örnekleri üretmesini sağlar. Saklama ufukları audit log'u ve ara artefact'ları için GDPR uyumlu TTL'ler ayarlar. Hiçbiri zorunlu değil — eğitimin ihtiyaç duymuyorsa kapalı bırak.", "wizard.risk.label": "EU AI Act risk sınıflandırması", "wizard.risk.hint": "Dağıtımın hangi seviyeye girdiğini seç. High-risk ve unacceptable, zorunlu denetim + insan onay yükümlülüklerini tetikler.", "wizard.risk.unknown": "Unknown — ön sınıflandırma yer tutucusu", @@ -1747,6 +2052,114 @@ "wizard.compliance.intended_purpose.label": "Amaçlanan kullanım", "wizard.compliance.intended_purpose.hint": "Annex IV §1: sistemin amaçlanan kullanımının düz dilde açıklaması.", "wizard.compliance.intended_purpose.placeholder":"Sohbet üzerinden müşteri destek talep önceliklendirme", + "wizard.risk_assessment.title": "Risk değerlendirmesi (EU AI Act Madde 9)", + "wizard.risk_assessment.hint": "High-risk sistemler için zorunlu. Diğer durumlarda opsiyonel ama önerilir.", + "wizard.risk_assessment.intended_use.label": "Amaçlanan kullanım", + "wizard.risk_assessment.intended_use.hint": "Madde 9(2)(a): sistemin amaçlanan kullanımını sade bir dille tarif et.", + "wizard.risk_assessment.intended_use.placeholder": "Müşteri destek talepleri sınıflandıran ve yanıt taslakları üreten bir asistan.", + "wizard.risk_assessment.intended_use.detail": "Bu, sisteminin ne için inşa edildiğine dair kanonik ifadendir — risk değerlendirmesinin geri kalanını çapalar. Spesifik ol: \"destek taleplerini sınıflandır\" \"kullanıcıya yardım et\"ten daha iyi. Denetleyiciler ve regülatörler aşağı akış mitigasyonlarının uygun olup olmadığına karar vermek için önce bunu okur.", + "wizard.risk_assessment.foreseeable_misuse.label": "Öngörülebilir kötüye kullanım (her satıra bir tane)", + "wizard.risk_assessment.foreseeable_misuse.hint": "Madde 9(2)(b): sistemin kötüye kullanılabileceği senaryolar.", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "Phishing e-postaları üretme\nSosyal mühendislik saldırıları\nPrompt injection ile güvenlik bariyerlerini aşma", + "wizard.risk_assessment.foreseeable_misuse.detail": "Makul ölçüde öngörebileceğin kötüye kullanım senaryolarını listele — her teorik saldırı değil, deployment bağlamında önemli olanları. Standart egzersiz: düşman ama tembel bir saldırgan hayal et. Bunu kötüye kullanmanın en kolay yolu nedir? Textarea'daki her satır YAML listesinde bir öğeye dönüşür.", + "wizard.risk_assessment.mitigation_measures.label": "Mitigasyon önlemleri (her satıra bir tane)", + "wizard.risk_assessment.mitigation_measures.hint": "Madde 9(2)(c): yukarıdaki kötüye kullanım risklerine karşı aldığın kontroller.", + "wizard.risk_assessment.mitigation_measures.placeholder": "Tüm çıktılarda Llama Guard güvenlik filtresi\nKullanıcı başına rate limit\nYüksek-riskli yanıtlar için insan onayı", + "wizard.risk_assessment.mitigation_measures.detail": "Her öngörülebilir kötüye kullanım için somut bir mitigasyon belirt. Denetleyiciler bu listeyi kötüye kullanım listesiyle karşılaştırır — boşluklar kırmızı bayraktır. Her satır bir YAML liste öğesine dönüşür. ForgeLM'in yerleşik safety eval ve insan-onayı gate'leri buraya iyi ilk-savunma girişleri.", + "wizard.risk_assessment.vulnerable_groups.label": "Hassas gruplar üzerindeki etki düşünüldü", + "wizard.risk_assessment.vulnerable_groups.hint": "Madde 9(2)(b): çocuklar, azınlıklar vb. üzerinde etkileri düşünüp düşünmediğini işaretle.", + "wizard.risk_assessment.vulnerable_groups.detail": "Madde 9 açıkça hassas grupları (çocuklar, yaşlılar, azınlıklar, engelli bireyler) çağrılır. Bunu yalnızca sisteminin onları nasıl etkileyebileceğini gerçekten düşündükten sonra işaretle — yanlış sertifikasyon hiç sertifikasyondan daha kötüdür.", + "wizard.eval_gates.title": "Kalite kapıları (gelişmiş)", + "wizard.eval_gates.hint": "Temel loss/safety kapılarına ek olarak isteğe bağlı benchmark + LLM-as-Judge puanlama.", + "wizard.benchmark.enabled.label": "lm-evaluation-harness benchmark çalıştır", + "wizard.benchmark.enabled.hint": "Eğitim sonrası fine-tuned modeli akademik görevlerde (ARC, HellaSwag, MMLU vb.) puanla.", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness LLM benchmarking için de-facto standart. Bunu açmak listelenen görevleri fine-tuned modeline karşı çalıştırır ve JSON sonuçları checkpoint yanına kaydeder. auto_revert + min_score ile birleştiğinde sert kalite kapısı elde edersin: benchmark doğruluğu eşiğin altına düşerse trainer geri alır.", + "wizard.benchmark.tasks.label": "Görevler (her satıra bir tane)", + "wizard.benchmark.tasks.hint": "Yaygın: arc_easy, arc_challenge, hellaswag, mmlu, truthfulqa, winogrande, gsm8k.", + "wizard.benchmark.min_score.label": "Minimum ortalama doğruluk", + "wizard.benchmark.min_score.hint": "0.0–1.0. Bunun altında auto_revert fine-tune'ı geri alır. Eşik kapısını atlamak için boş bırak.", + "wizard.benchmark.min_score.placeholder": "örn. 0.55", + "wizard.judge.enabled.label": "LLM-as-Judge puanlama", + "wizard.judge.enabled.hint": "Üretimleri daha güçlü bir modelle (örn. GPT-4o) bir değerlendirme prompt setinde puanla.", + "wizard.judge.enabled.detail": "LLM-as-Judge, daha güçlü bir modele fine-tuned modelinin çıktılarını 1–10 skalasında not vermesi için prompt yollar. Tek doğru cevabı olmayan sohbet/talimat kalitesi için benchmark'lardan daha iyi. Run başına para harcar (judge API çağrıları), bu yüzden genelde her dev run yerine production kapıları için yapılandırılır.", + "wizard.judge.model.label": "Judge model", + "wizard.judge.model.hint": "API model adı (gpt-4o, claude-opus-4-7) veya yerel model yolu. Judge, test edilen modelden belirgin şekilde daha güçlü olmalı.", + "wizard.judge.api_key_env.label": "API key env değişkeni", + "wizard.judge.api_key_env.hint": "API anahtarını taşıyan ortam değişkeni adı. Boş = yerel judge modeli (API çağrısı yok).", + "wizard.judge.min_score.label": "Minimum ortalama judge skoru", + "wizard.judge.min_score.hint": "1–10 skala. Bunun altı auto_revert tetikler. 5.0 \"ortalama\"; 7.0 \"iyi\".", + "wizard.governance.title": "Veri yönetişimi (Madde 10)", + "wizard.governance.hint": "Veri kaynağı + biases'ı belgele. High-risk dağıtımlar için zorunlu.", + "wizard.governance.collection_method.label": "Toplama yöntemi", + "wizard.governance.collection_method.hint": "Madde 10(2)(b): eğitim verisinin nasıl elde edildiği.", + "wizard.governance.collection_method.placeholder": "2023–2025 public web arşivlerinden scrape edildi, URL hash ile dedupe edildi.", + "wizard.governance.collection_method.detail": "Denetleyicilere verinin trainer'a NASIL ulaştığını söyle. \"Müşteri sohbet kayıtları, izinle export edildi\" veya \"GPT-4 tarafından üretilen sentetik diyaloglar\". Belirsiz cevaplar (\"internet\") Madde 10 denetimlerinden kalır.", + "wizard.governance.annotation_process.label": "Etiketleme süreci", + "wizard.governance.annotation_process.hint": "Madde 10(2)(b): etiketleme metodolojisi — kim etiketledi, hangi rehberle.", + "wizard.governance.annotation_process.placeholder": "İki eğitimli annotator her örneği etiketledi; çatışmalar kıdemli inceleme ile çözüldü.", + "wizard.governance.annotation_process.detail": "Veri etiketliyse (tercih çiftleri, sınıflandırma etiketleri, gold cevaplar), nasıl olduğunu tarif et. Annotator-lar arası uyum, rehber sürümü, çatışma çözümü. Ham metinde SFT için \"etiket yok\" geçerli yanıt.", + "wizard.governance.known_biases.label": "Bilinen önyargılar", + "wizard.governance.known_biases.hint": "Madde 10(2)(f): operatörün veride farkında olduğu önyargılar.", + "wizard.governance.known_biases.placeholder": "Ağırlıklı İngilizce; <18 ve >65 yaş grupları az temsil ediliyor; Batı hukuk-sistemi çerçevelemeleri.", + "wizard.governance.known_biases.detail": "Hiçbir veri seti mükemmel dengeli değil. Dengesizlikleri önden adlandırmak dürüst yanıt — ve onu karşılamak denetleyicilere due diligence gösterir. Az temsil edilen demografi, dil skew'i, zaman-dönemi skew'i, kaynak yanlılığı hepsi sayılır.", + "wizard.governance.personal_data.label": "Eğitim verisi kişisel veri içeriyor", + "wizard.governance.personal_data.hint": "Madde 10(5) + GDPR: herhangi bir örnek tanımlanabilir bireyler içeriyorsa işaretle.", + "wizard.governance.dpia_completed.label": "DPIA tamamlandı (GDPR Madde 35)", + "wizard.governance.dpia_completed.hint": "Bu dataset için Veri Koruma Etki Değerlendirmesi tamamlanmış ve belgelenmiş.", + "wizard.webhook.title": "Bildirimler (webhook)", + "wizard.webhook.hint": "Eğitim olaylarında Slack / Teams / Discord uyarısı gönder.", + "wizard.webhook.url.label": "Webhook URL veya env değişkeni", + "wizard.webhook.url.hint": "Doğrudan https URL veya onu taşıyan env değişkeni adı (gizliler için önerilen).", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... VEYA env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "URL'yi doğrudan yapıştırabilirsin (gizli olmayan test kanalları için iyi) ya da bir ortam değişkenine işaret etmek için env: önekini kullan. Env-var yolu URL'yi YAML'den ve audit log'tan dışarıda tutar — production gizliler için zorunlu.", + "wizard.webhook.enabled.label": "Webhook bildirimleri gönder", + "wizard.webhook.enabled.hint": "Açıkken, ForgeLM seçtiğin olaylarda bir webhook payload gönderir. Kapalıyken YAML herhangi bir webhook bloğu içermez.", + "wizard.webhook.enabled.detail": "Bildirimler eğitim olaylarını ekip sohbetine (Slack / Teams / Discord) veya JSON kabul eden herhangi bir HTTPS endpoint'ine bağlar. Audit log her gönderimi — hatalar dahil — kaydeder, böylece neyin gönderildiğine dair bağımsız bir iz kalır. YAML'in webhook config taşımasını hiç istemiyorsan kapalı bırak.", + "wizard.webhook.notify_start.label": "Eğitim başında bildir", + "wizard.webhook.notify_success.label": "Başarıda bildir", + "wizard.webhook.notify_failure.label": "Hatada bildir", + "wizard.monitoring.title": "Post-market monitoring (Madde 12+17)", + "wizard.monitoring.hint": "Eğitim metriklerini sürekli observability için Prometheus / Datadog / özel webhook'a bağla.", + "wizard.monitoring.enabled.label": "Monitoring hook'ları aç", + "wizard.monitoring.enabled.hint": "High-risk sistemler için EU AI Act Madde 12 + 17 zorunlu kılar.", + "wizard.monitoring.enabled.detail": "High-risk bir sistem deploy edildikten sonra EU AI Act sürekli gözlem zorunlu kılar — drift detection, hata raporlama, periyodik doğruluk yeniden-kontrolleri. ForgeLM monitoring'i kendisi çalıştırmaz ama doğru hook'ları training run'a yerleştirir; mevcut observability stack'in alır.", + "wizard.monitoring.endpoint.label": "Monitoring endpoint", + "wizard.monitoring.endpoint.hint": "Prometheus push gateway, Datadog veya özel webhook receiver URL'si.", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics VEYA env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "Metrik formatı", + "wizard.monitoring.metrics_export.none": "Yok", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "Özel webhook", + "wizard.monitoring.alert_on_drift.label": "Drift'te uyar", + "wizard.monitoring.alert_on_drift.hint": "Drift detector regression bayrağı koyduğunda webhook uyarısı gönder.", + "wizard.synthetic.title": "Sentetik veri üretimi (gelişmiş)", + "wizard.synthetic.hint": "Daha güçlü bir model kullanarak teacher → student eğitim verisi üret.", + "wizard.synthetic.enabled.label": "Sentetik veri üret", + "wizard.synthetic.enabled.hint": "Seed prompt'lar üzerinde teacher modeli çalıştırarak eğitim örnekleri üret.", + "wizard.synthetic.enabled.detail": "Sentetik veri üretimi, daha güçlü bir modelin (örn. GPT-4) küçük fine-tune hedefi için eğitim örnekleri üretmesini sağlar. Gerçek veri kıt veya hassas olduğunda yararlı. ForgeLM lineage'ı audit log'da takip eder; her örneğin sentetik kökeni doğrulanabilir.", + "wizard.synthetic.teacher_model.label": "Teacher modeli", + "wizard.synthetic.teacher_model.hint": "API model adı (gpt-4o, claude-opus-4-7) veya yerel model yolu.", + "wizard.synthetic.seed_file.label": "Seed prompt dosyası", + "wizard.synthetic.seed_file.hint": "JSONL veya düz metin seed prompt dosyası. Teacher her biri için yanıt üretir.", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Teacher API key env değişkeni", + "wizard.synthetic.api_key_env.hint": "Teacher API anahtarını taşıyan ortam değişkeni adı. Boş = yerel teacher modeli.", + "wizard.retention.title": "Saklama ufukları (GDPR Madde 5+17)", + "wizard.retention.hint": "ForgeLM audit log, staging model ve geçici artefact'ları ne kadar tutar.", + "wizard.retention.audit_log.label": "Audit log saklama (gün)", + "wizard.retention.audit_log.hint": "audit_log.jsonl ne kadar tutulsun. Varsayılan 1825 (5 yıl) EU AI Act rehberiyle uyumlu.", + "wizard.retention.staging.label": "Staging ufku (gün)", + "wizard.retention.staging.hint": "final_model.staging/ içinde reddedilen modeller ne kadar tutulsun. Varsayılan 7.", + "wizard.retention.ephemeral.label": "Geçici artefact'lar (gün)", + "wizard.retention.ephemeral.hint": "Ara checkpoint'ler, eval örnekleri ne kadar tutulsun. Varsayılan 90.", + "wizard.retention.raw_documents.label": "Ham dokümanlar (gün)", + "wizard.retention.raw_documents.hint": "Ingestion sonrası ham PDF / DOCX ne kadar tutulsun. Varsayılan 90.", + "wizard.retention.enforce.label": "Otomatik silmeyi zorunlu kıl", + "wizard.retention.enforce.hint": "Açıkken ForgeLM ufku aşan artefact'ları otomatik siler. Varsayılan kapalı — açmak GDPR onayı gerektirir.", + "wizard.retention.enforce.log_only": "log_only — ihlalleri audit'e logla (varsayılan)", + "wizard.retention.enforce.warn": "warn_on_excess — log + stderr uyarısı", + "wizard.retention.enforce.block": "block_on_excess — trainer'ı durdur (regüle CI)", "wizard.step.review.title": "Config'ini gözden geçir", "wizard.step.review.name": "Gözden geçir", "wizard.step.review.desc": "İşte YAML. Kopyala, indir ve çalıştır.", @@ -1756,16 +2169,35 @@ "wizard.review.reset": "Sihirbazı sıfırla", "wizard.review.reset.confirm":"Tüm sihirbaz cevapları sıfırlansın ve baştan başlansın mı? YAML'ın kaybolacak.", "wizard.review.run_label": "Sonra çalıştır:", + "wizard.error.model.required": "Önceki adımda “Custom HuggingFace model” seçtin, bu yüzden bu alan zorunlu. org/repo formatında bir ID gir (örn. meta-llama/Llama-3.1-8B-Instruct) ya da geri dönüp hazır bir preset seç.", + "wizard.error.model.format": "HuggingFace Hub ID’leri org/repo formatında olmalı (örn. meta-llama/Llama-3.1-8B-Instruct). Slash içermeyen düz bir isim eğitim sırasında bulunamaz.", + "wizard.error.dataset.required": "Devam etmeden önce bir dataset seç — yol veya HuggingFace ismi olmadan trainer’ın öğreneceği bir şey yok.", + "wizard.error.webhook.required": "Bildirimleri açtın, yani göndereceğimiz bir webhook URL’si veya env değişkeni gerekli. Ya yukarıdan bildirimleri kapat ya da Slack / Teams / Discord URL’ini yapıştır (production gizliler için env:SLACK_WEBHOOK_URL kullan).", + "wizard.error.monitoring.endpoint.required": "Post-market monitoring’i açtın, yani metrikleri yollayacağımız bir endpoint gerekli. Ya yukarıdan monitoring’i kapat ya da Prometheus / Datadog / özel-webhook URL’ini yapıştır (gizliler için env:DEĞİŞKEN_ADI).", + "wizard.error.synthetic.teacher_model.required": "Sentetik veri üretimini açtın, yani bir teacher model adı gerekli (örn. gpt-4o, claude-opus-4-7 veya yerel model yolu).", + "wizard.error.synthetic.seed_file.required": "Sentetik veri üretimini açtın, yani bir seed prompts dosyası gerekli — teacher her prompt için yanıt üretir (örn. ./data/seed_prompts.jsonl).", + "wizard.error.synthetic.api_key_env.required": "Sentetik veri üretimini açtın, yani teacher API anahtarını taşıyan env değişkeni gerekli — genellikle OPENAI_API_KEY veya ANTHROPIC_API_KEY. Bir tane yoksa sentetik anahtarını kapalı bırak.", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1, high-risk sistemler için sağlayıcı tüzel kişi adını zorunlu kılar. Doldur ya da risk sınıflandırmasını düşür.", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1, high-risk dağıtımlar için insan-okunur sistem adını zorunlu kılar. Doldur ya da risk sınıflandırmasını düşür.", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1, high-risk sistemler için belirtilmiş bir amaç gerektirir. Sade dille amacı tarif et ya da risk sınıflandırmasını düşür.", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act Madde 9, high-risk sistemler için kullanım amacının belgelenmesini gerektirir. Sistemin ne yapması gerektiğini tarif et ya da risk sınıflandırmasını düşür.", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act Madde 9, high-risk sistemler için öngörülebilir kötüye-kullanım senaryolarını gerektirir. Her satıra bir tane olarak listele ya da risk sınıflandırmasını düşür.", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act Madde 9, high-risk sistemler için risk azaltma önlemlerini gerektirir. Devreye aldığın kontrolleri (her satıra bir tane) listele ya da risk sınıflandırmasını düşür.", + "wizard.error.governance.collection_method.required": "EU AI Act Madde 10, high-risk sistemler için veri-kaynak belgesi gerektirir. Eğitim verisinin nasıl elde edildiğini tarif et ya da risk sınıflandırmasını düşür.", + "wizard.error.governance.annotation_process.required": "EU AI Act Madde 10, etiketleme metodolojisi belgesi gerektirir. Etiketleme sürecini tarif et (ham metin SFT’i için “etiket yok” geçerli) ya da risk sınıflandırmasını düşür.", + "wizard.error.governance.known_biases.required": "EU AI Act Madde 10, high-risk sistemler için bilinen dataset önyargılarının belgelenmesini gerektirir. Farkında olduğun dengesizlikleri listele ya da risk sınıflandırmasını düşür.", + "wizard.error.benchmark.tasks.required": "Benchmark gate’ini açtın, yani en az bir görev gerekli. lm-evaluation-harness isimleri olarak arc_easy, hellaswag, mmlu gibi görevler ekle (her satıra bir tane) ya da yukarıdan benchmark’ı kapat.", + "wizard.error.judge.api_key_env.required": "LLM-as-Judge’i açtın, yani judge API anahtarını taşıyan env değişkeni gerekli — genellikle OPENAI_API_KEY veya ANTHROPIC_API_KEY.", "quickstart.spotlight.eyebrow": "YAML'ı atla", "quickstart.spotlight.title": "Tarayıcı içi sihirbazla config'ini 3 dakikada oluştur.", "quickstart.spotlight.lead": "Kullanım senaryonu, base modelini, veri setini ve EU AI Act risk seviyesini seç. Sihirbaz, tek komutla çalıştırabileceğin bir quickstart-generated.yaml yazar — fine-tuning uzmanlığı gerekmez.", "quickstart.spotlight.cta_primary": "Sihirbazı aç", "quickstart.spotlight.cta_secondary":"Ya da 5 adımlık rehberi takip et", - "quickstart.spotlight.meta1": "7 rehberli adım", + "quickstart.spotlight.meta1": "9 rehberli adım", "quickstart.spotlight.meta2": "Canlı YAML önizleme", "quickstart.spotlight.meta3": "EU AI Act ön ayarları", - "quickstart.spotlight.stage1.label": "7 soruyu yanıtla", - "quickstart.spotlight.stage1.snippet":"kullanım · model · veri · risk", + "quickstart.spotlight.stage1.label": "9 soruyu yanıtla", + "quickstart.spotlight.stage1.snippet":"kullanım · trainer · model · veri · risk", "quickstart.spotlight.stage2.label": "Geçerli YAML al", "quickstart.spotlight.stage3.label": "Çalıştır", "quickstart.spotlight.manual_intro": "Ya da aşağıdaki manuel 5-adım referansını takip et" @@ -1863,8 +2295,43 @@ "wizard.dataset.pdf.desc": "ForgeLM ingestiert sie vor dem Training zu JSONL (PII + Secrets maskiert).", "wizard.dataset.name.label": "Datensatzpfad oder -name", "wizard.dataset.name.hint": "HuggingFace-Name (org/dataset), lokaler Dateipfad (./data/train.jsonl) oder Verzeichnis (./documents/).", - "wizard.dataset.autodetect.title": "Format wird automatisch erkannt.", - "wizard.dataset.autodetect.body": "ForgeLM liest die JSONL-Felder direkt: Zeilen mit einem messages-Array werden zu mehrstufigem Chat; Zeilen mit prompt + response werden zu Anweisungs-Tuning. Gemischte Datensätze werden nicht unterstützt — der Loader wählt die Form aus der ersten Zeile.", + "wizard.dataset.shape.sft.title": "Format wird automatisch erkannt.", + "wizard.dataset.shape.sft.body": "ForgeLM liest die JSONL-Felder direkt: Zeilen mit einem messages-Array werden zu mehrstufigem Chat; Zeilen mit prompt + response werden zu Anweisungs-Tuning. Gemischte Datensätze werden nicht unterstützt — der Loader wählt die Form aus der ersten Zeile.", + "wizard.dataset.shape.preference.title": "Präferenz-Zeilen erforderlich.", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO benötigen Präferenz-Tripel pro Zeile: { \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }. Der Trainer lernt, chosen gegenüber rejected zu bevorzugen. Konvertieren Sie Ihre Daten zuerst, falls sie noch nicht in dieser Form vorliegen — sonst gibt die Laufzeit einen Fehler.", + "wizard.dataset.shape.kto.title": "Binärfeedback-Zeilen erforderlich.", + "wizard.dataset.shape.kto.body": "KTO benötigt einzelne Completions mit Daumen-hoch / Daumen-runter Label: { \"prompt\": ..., \"completion\": ..., \"label\": true|false }. Keine gepaarten Präferenzen nötig — KTO arbeitet mit der Art von Daten, die ein Feedback-Widget tatsächlich produziert.", + "wizard.dataset.shape.grpo.title": "GRPO-Rollout-Zeilen.", + "wizard.dataset.shape.grpo.body": "GRPO erzeugt mehrere Antworten pro Prompt und sortiert sie mit einem Reward-Signal. Zeilen: { \"prompt\": ... }. Für Mathe-artige Probleme können Sie ein optionales gold_answer-Feld hinzufügen — der eingebaute Regex-Correctness-Reward verwendet es. Ohne Reward-Modell fällt der Trainer auf Format/Länge-Shaping zurück.", + "wizard.step.trainer.title": "Wählen Sie das Alignment-Paradigma", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "Sechs Trainer, eine Auswahl. Der Anwendungsfall-Schritt hat einen sinnvollen Standard gewählt — ändern Sie ihn hier, wenn Sie eine andere Alignment-Methode brauchen.", + "wizard.step.trainer.tutorial": "Trainer unterscheiden sich darin, WAS sie optimieren. SFT lernt Input → Output-Mappings aus gelabelten Beispielen. DPO / ORPO / SimPO vergleichen eine gewählte Antwort mit einer abgelehnten und verstärken den Unterschied. KTO arbeitet mit einzelnem Daumen-hoch / Daumen-runter Feedback statt gepaarten Präferenzen. GRPO sampelt viele Antworten pro Prompt und sortiert sie mit einem Reward — am besten für Reasoning-Aufgaben. Der gewählte Trainer bestimmt die Form Ihres Datasets.", + "wizard.trainer.recommended": "guter Standard", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "Überwacht", + "wizard.trainer.sft.desc": "Fine-Tuning mit gelabelten Input → Output-Beispielen. Standard für Chat, Anweisungen, Zusammenfassung und BYOD-Domänen-Expertise.", + "wizard.trainer.sft.shape": "Zeilen: messages[] ODER prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "Präferenz", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation. Lernt aus chosen/rejected-Paaren ohne explizites Reward-Modell. Häufig für Daumen-hoch-Alignment nach SFT.", + "wizard.trainer.dpo.shape": "Zeilen: prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation. Wie DPO, aber ohne Referenzmodell — kombiniert SFT + Alignment in einem Durchgang.", + "wizard.trainer.orpo.shape": "Zeilen: prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "Längen-normalisiert", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation. Reference-free, längen-normalisierter Reward — vermeidet die Verbosity-Bias, die DPO manchmal zeigt.", + "wizard.trainer.simpo.shape": "Zeilen: prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "Binärfeedback", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation. Arbeitet mit einzelnen Daumen-hoch / Daumen-runter-Labels — perfekt, wenn Sie Feedback-Widgets aber keine gepaarten Präferenzen haben.", + "wizard.trainer.kto.shape": "Zeilen: prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "Reasoning", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation. Sampelt mehrere Antworten pro Prompt und sortiert sie mit einem Reward-Signal. Am besten für Mathe, Code und Schritt-für-Schritt-Reasoning.", + "wizard.trainer.grpo.shape": "Zeilen: prompt (+ optional gold_answer)", "wizard.step.training.title": "Trainingsparameter", "wizard.step.training.name": "Training", "wizard.step.training.desc": "Standardwerte funktionieren in 90 % der Fälle. Nur tunen, wenn Sie wissen, warum.", @@ -1887,10 +2354,64 @@ "wizard.training.batch.label": "Batch-Size (pro Gerät)", "wizard.training.batch.hint": "Größere Batches trainieren schneller, brauchen aber mehr VRAM. Beginnen Sie mit 2, wenn Sie unsicher sind.", "wizard.training.batch.detail": "Batch-Size ist, wie viele Beispiele die GPU parallel verarbeitet. Größere Batches bedeuten schnelleres Training und stabilere Gradienten, brauchen aber mehr VRAM. Mit QLoRA auf einer 12-GB-GPU passt meist Batch=2 mit einem 8B-Modell; bei OOM auf 1 reduzieren und Gradient Accumulation erhöhen.", + "wizard.training.lora_method.label": "LoRA-Variante", + "wizard.training.lora_method.hint": "PiSSA braucht mehr Speicher, konvergiert aber schneller; RSLoRA stabilisiert hohe Ränge (r > 64); DoRA zerlegt Gewichte für etwas bessere Qualität.", + "wizard.training.lora_method.detail": "Standard LoRA ist der sichere Default und passt für alle 8B-auf-12-GB-Szenarien. DoRA zerlegt das LoRA-Update in Magnitude + Richtung — kleine Qualitätssteigerung zu moderaten Kosten. PiSSA initialisiert Adapter aus den Hauptkomponenten der Basis-Gewichte, konvergiert auf kleinen Datasets schneller. RSLoRA reskaliert den Alpha-Faktor für hohe Ränge (r=64+); richtig wenn Standard-LoRA-Training stagniert oder NaN produziert.", + "wizard.training.lora_method.standard.title": "Standard", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA. Sicherer Default für alles.", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "Magnitude + Richtung trennen. ~5% Qualitätsplus.", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "SVD-initialisierte Adapter. Schnellere Konvergenz auf kleinen Daten.", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rang-stabilisiert. Verwenden bei r > 64 oder instabilem Training.", + "wizard.training.galore.label": "GaLore verwenden (Voll-Parameter-Training, keine Adapter)", + "wizard.training.galore.hint": "Ersetzt LoRA durch gradient-projiziertes Voll-Parameter-Training. Niedrigerer Peak-VRAM aber langsamer pro Schritt.", + "wizard.training.galore.detail": "GaLore trainiert jeden Parameter via Low-Rank-Gradient-Projektion — gar kein Adapter. Peak-VRAM fällt unter QLoRA bei großen Modellen, weil der Optimizer-State in einen Low-Rank-Unterraum projiziert wird. Compute pro Schritt ist höher (Projektion wird alle paar Schritte neu berechnet), aber das Ergebnis ist ein voll fine-getunes Modell ohne LoRA-Merge. Verwenden bei 24 GB+ und wenn ein einzelnes deploybares Modell statt Basis + Adapter gewünscht ist.", + "wizard.training.galore_optim.label": "GaLore-Optimizer-Variante", + "wizard.training.galore_optim.hint": "_8bit halbiert Optimizer-State-VRAM; _layerwise senkt Peak durch Pro-Schicht-Recomputation.", + "wizard.training.galore_optim.detail": "GaLore ersetzt den Standard-Optimizer. _8bit-Varianten quantisieren den Optimizer-State auf 8-Bit (riesige VRAM-Ersparnis, keine echte Qualitätseinbuße). _layerwise-Varianten berechnen die GaLore-Projektion schichtweise statt für alle Schichten gleichzeitig — langsamer, aber erlaubt das Training von Modellen, die sonst OOM werfen. Default galore_adamw_8bit ist die richtige Wahl für die meisten Setups.", + "wizard.training.galore_optim.adamw": "AdamW (volle Präzision)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-Bit (empfohlen)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW schichtweise", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-Bit schichtweise", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor schichtweise", + "wizard.training.trainer_section.title": "{trainer}-Hyperparameter", + "wizard.training.trainer_section.body": "Diese Einstellungen sind spezifisch für den gewählten Trainer. Defaults sind sinnvoll — passen Sie nur an, wenn Sie verstehen, was sie tun, und Daten zur Begründung haben.", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "Temperatur, die KL-Abweichung vom Referenzmodell kontrolliert. 0.1 ist der kanonische Default.", + "wizard.training.dpo_beta.detail": "DPO bestraft die Policy für Abweichung vom Referenzmodell. Beta skaliert diese Strafe — niedriges Beta erlaubt schnellere Änderung (und Überanpassungsrisiko); hohes Beta hält das Modell nahe der Referenz. Das DPO-Paper empfiehlt 0.1 als Startpunkt. Tunen Sie 0.05–0.2 erst nach Messung auf einem held-out Präferenz-Set.", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Odds-Ratio-Gewicht. 0.1 ist Schema-Default; höhere Werte bestrafen abgelehnte Outputs aggressiver.", + "wizard.training.orpo_beta.detail": "ORPO ersetzt DPOs Referenzmodell durch einen Odds-Ratio-Term in der SFT-Loss. Beta steuert, wie stark die Likelihood der abgelehnten Antwort relativ zur gewählten bestraft wird. 0.1 ist der publizierte Default; auf 0.2–0.3 erhöhen, falls das Modell weiterhin rejected-style Antworten bevorzugt.", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Margin-Term, der Mindest-Reward-Differenz zwischen chosen und rejected erzwingt. 0.5 ist publizierter Default.", + "wizard.training.simpo_gamma.detail": "Gamma setzt eine Mindest-Marge zwischen den Log-Wahrscheinlichkeiten von chosen und rejected. Größeres Gamma = aggressivere Trennung, aber Loss kann stagnieren, wenn kein Präferenzpaar die Marge übersteigt. Bleiben Sie bei 0.5, sofern Sie chosen/rejected-Gap-Verteilungen nicht gemessen haben.", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "Reward-Skalierungsfaktor. Multipliziert Log-Probs vor dem Margin-Vergleich. 2.0 publizierter Default.", + "wizard.training.simpo_beta.detail": "Beta skaliert das Token-Reward-Signal vor Anwendung der Gamma-Marge. Hohes Beta verstärkt die Differenz zwischen chosen und rejected (schärferes Präferenz-Lernen), kann aber auch Rauschen verstärken. SimPO-Paper setzt beta = 2.0 mit gamma = 0.5; als Paar tunen, nicht unabhängig.", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "KL-Divergenz-Gewicht gegen das Referenzmodell. 0.1 Default spiegelt DPO.", + "wizard.training.kto_beta.detail": "KTO ist im Geiste näher an RLHF als DPO — formt die Loss mit der Loss-Aversion-Asymmetrie der Prospect-Theorie, skaliert mit Beta. Default 0.1 ist auf typische Daumen-hoch / Daumen-runter Feedback-Verhältnisse kalibriert. Senken bei stark einseitigem Dataset.", + "wizard.training.grpo_num_generations.label": "Generierungen pro Prompt", + "wizard.training.grpo_num_generations.hint": "Anzahl Antworten, die GRPO pro Prompt vor dem Ranking sampelt. 4 ist ausgewogen; 8 ist hochwertiger aber langsamer.", + "wizard.training.grpo_num_generations.detail": "GRPO ranked N gesampelte Antworten gegeneinander zum Aufbau des Policy-Gradients. Mehr Samples = besser-konditionierter Gradient und schnellere Konvergenz pro Schritt, aber jeder Schritt kostet N Forward-Passes. 4 ist Sweet Spot für 8B-Modell auf einer GPU; auf 8 erhöhen für Mathe/Reasoning, wo Ranking-Qualität zählt.", + "wizard.training.grpo_max_completion_length.label": "Max Completion-Länge", + "wizard.training.grpo_max_completion_length.hint": "Token-Limit pro generierter Antwort. 512 deckt die meisten Reasoning-Ketten; für lange Mathe erhöhen.", + "wizard.training.grpo_max_completion_length.detail": "GRPO schneidet generierte Rollouts bei diesem Token-Count ab. Kürzere Limits = schnellere Schritte, können aber Reasoning mittendrin abschneiden; längere Limits explorieren mehr, verschwenden aber Compute auf Padding. 512 ist ausgewogener Default — für GSM8K-Mathe auf 384 senken, für Olympiade-Niveau 1024+ pushen.", + "wizard.training.grpo_reward_model.label": "Reward-Modell (optional)", + "wizard.training.grpo_reward_model.hint": "HuggingFace-Hub-ID eines Reward-Modells zur Bewertung von Generierungen. Leer lassen für eingebauten Format/Länge-Shaping-Reward.", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "Wenn Sie eine Reward-Modell-ID angeben, verwendet GRPO sie zur Bewertung jedes Rollouts — das ist das Standard-Rezept für RLHF-Style-Alignment. Leer lassen, und ForgeLM fällt auf den eingebauten combined_format_length_reward zurück: ein gradient-reiches Format/Länge-Signal, das kein zusätzliches Modell braucht. Für Mathe-Probleme triggert das Hinzufügen eines gold_answer-Felds zum Dataset zusätzlich einen Regex-Correctness-Reward.", "wizard.step.compliance.title": "Evaluierung & EU AI Act", "wizard.step.compliance.name": "Evaluierung & Compliance", "wizard.step.compliance.desc": "Qualitäts-Gates und regulatorische Artefakte.", "wizard.step.compliance.tutorial": "ForgeLM prüft das fine-getunte Modell, BEVOR Sie es ausliefern. Auto-Revert macht das Fine-Tune rückgängig, wenn es schlechter als das Basismodell ist. Llama Guard scannt Ausgaben auf schädliche Inhalte. Die EU-AI-Act-Risikoklasse, die Sie wählen, bestimmt, welche Compliance-Artefakte automatisch generiert werden und ob menschliche Freigabe vor dem Deployment ERFORDERLICH ist.", + "wizard.step.operations.title": "Betrieb & erweiterte Einstellungen", + "wizard.step.operations.name": "Betrieb", + "wizard.step.operations.desc": "Benachrichtigungen, Post-Market-Monitoring, synthetische Datengenerierung und Retentions-Horizonte. Alle optional — überspringen, was Sie nicht brauchen.", + "wizard.step.operations.tutorial": "Diese vier Blöcke decken alles ab, was ForgeLM jenseits des Kern-Training + Compliance-Flows liefert. Webhook sendet Slack/Teams/Discord-Alerts bei Trainingsstart/Erfolg/Fehler. Post-Market-Monitoring (Artikel 12+17) verdrahtet Drift-Detection nach Deployment in Prometheus/Datadog. Synthetische Datengenerierung lässt ein stärkeres Teacher-Modell zusätzliche Trainingsbeispiele produzieren. Retentions-Horizonte setzen DSGVO-konforme TTLs für Audit-Log und ephemere Artefakte. Nichts davon ist Pflicht — eingeklappt lassen wenn Ihr Run sie nicht braucht.", "wizard.risk.label": "EU-AI-Act-Risikoklassifizierung", "wizard.risk.hint": "Wählen Sie die Klasse, unter die Ihr Deployment fällt. High-risk und unacceptable lösen verpflichtende Audit + Human-Approval-Pflichten aus.", "wizard.risk.unknown": "Unknown — Vorklassifizierungs-Platzhalter", @@ -1925,6 +2446,114 @@ "wizard.compliance.intended_purpose.label": "Vorgesehener Zweck", "wizard.compliance.intended_purpose.hint": "Annex IV §1: erklärter Zweck des Systems in einfacher Sprache.", "wizard.compliance.intended_purpose.placeholder":"Kundensupport-Tickets per Chat priorisieren", + "wizard.risk_assessment.title": "Risikobewertung (EU AI Act Artikel 9)", + "wizard.risk_assessment.hint": "Pflicht für High-risk-Systeme. Optional aber empfohlen sonst.", + "wizard.risk_assessment.intended_use.label": "Beabsichtigte Verwendung", + "wizard.risk_assessment.intended_use.hint": "Artikel 9(2)(a): Beschreiben Sie den vorgesehenen Zweck des Systems in einfacher Sprache.", + "wizard.risk_assessment.intended_use.placeholder": "Ein Kundensupport-Assistent, der Tickets triagiert und Antworten entwirft.", + "wizard.risk_assessment.intended_use.detail": "Das ist die kanonische Aussage, wofür Ihr System gebaut ist — sie verankert den Rest der Risikobewertung. Seien Sie spezifisch: \"Support-Tickets triagieren\" ist besser als \"Benutzer helfen\". Prüfer und Regulierer lesen das zuerst, um zu entscheiden, ob nachgelagerte Mitigationen angemessen sind.", + "wizard.risk_assessment.foreseeable_misuse.label": "Vorhersehbarer Missbrauch (eines pro Zeile)", + "wizard.risk_assessment.foreseeable_misuse.hint": "Artikel 9(2)(b): Szenarien, in denen das System missbraucht werden könnte.", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "Phishing-E-Mails generieren\nSocial-Engineering-Angriffe\nSafety Guardrails via Prompt Injection umgehen", + "wizard.risk_assessment.foreseeable_misuse.detail": "Listen Sie die Missbrauchsszenarien, die Sie vernünftigerweise antizipieren können — nicht jeden theoretischen Angriff, sondern die, die in Ihrem Deployment-Kontext relevant sind. Standard-Übung: stellen Sie sich einen feindseligen aber faulen Angreifer vor. Was ist der einfachste Weg, das zu missbrauchen? Jede Zeile wird zu einem YAML-Listeneintrag.", + "wizard.risk_assessment.mitigation_measures.label": "Mitigationsmaßnahmen (eine pro Zeile)", + "wizard.risk_assessment.mitigation_measures.hint": "Artikel 9(2)(c): Kontrollen, die Sie gegen die obigen Missbrauchsrisiken einrichten.", + "wizard.risk_assessment.mitigation_measures.placeholder": "Llama Guard Safety-Filter auf allen Outputs\nRate-Limiting pro Nutzer\nMenschliche Freigabe für High-risk-Antworten", + "wizard.risk_assessment.mitigation_measures.detail": "Für jeden vorhersehbaren Missbrauch nennen Sie eine konkrete Mitigation. Prüfer vergleichen diese Liste mit der Missbrauchsliste — Lücken sind eine rote Flagge. Jede Zeile wird zu einem YAML-Listeneintrag. ForgeLMs eingebauter Safety-Eval- und Menschliche-Freigabe-Gate sind gute Erstlinien-Einträge.", + "wizard.risk_assessment.vulnerable_groups.label": "Auswirkung auf gefährdete Gruppen berücksichtigt", + "wizard.risk_assessment.vulnerable_groups.hint": "Artikel 9(2)(b): bestätigen Sie, ob Sie Wirkungen auf Kinder, Minderheiten etc. bedacht haben.", + "wizard.risk_assessment.vulnerable_groups.detail": "Artikel 9 nennt gefährdete Gruppen explizit (Kinder, Ältere, Minderheiten, Menschen mit Behinderungen). Setzen Sie das Häkchen nur nachdem Sie tatsächlich durchdacht haben, wie Ihr System sie betreffen könnte — falsche Zertifizierung ist schlimmer als keine.", + "wizard.eval_gates.title": "Quality Gates (erweitert)", + "wizard.eval_gates.hint": "Optionales Benchmark + LLM-as-Judge zusätzlich zu Loss/Safety-Gates.", + "wizard.benchmark.enabled.label": "lm-evaluation-harness Benchmark ausführen", + "wizard.benchmark.enabled.hint": "Bewerte das fine-getunte Modell auf akademischen Tasks nach dem Training.", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness ist der De-facto-Standard für LLM-Benchmarking. Aktivieren führt die aufgelisteten Tasks gegen Ihr fine-getuntes Modell aus und speichert JSON-Ergebnisse neben dem Checkpoint. Mit auto_revert + min_score erhalten Sie ein hartes Quality Gate.", + "wizard.benchmark.tasks.label": "Tasks (eines pro Zeile)", + "wizard.benchmark.tasks.hint": "Üblich: arc_easy, arc_challenge, hellaswag, mmlu, truthfulqa, winogrande, gsm8k.", + "wizard.benchmark.min_score.label": "Minimale Durchschnittsgenauigkeit", + "wizard.benchmark.min_score.hint": "0.0–1.0. Darunter rollt auto_revert zurück. Leer lassen um Schwelle zu überspringen.", + "wizard.benchmark.min_score.placeholder": "z.B. 0.55", + "wizard.judge.enabled.label": "LLM-as-Judge-Bewertung", + "wizard.judge.enabled.hint": "Bewerte Generationen mit einem stärkeren Modell (z.B. GPT-4o).", + "wizard.judge.enabled.detail": "LLM-as-Judge promptet ein stärkeres Modell, die Outputs auf einer 1–10-Skala zu bewerten. Besser als Benchmarks für Chat-/Instruktionsqualität ohne einzige richtige Antwort.", + "wizard.judge.model.label": "Judge-Modell", + "wizard.judge.model.hint": "API-Modellname (gpt-4o, claude-opus-4-7) oder lokaler Pfad. Judge muss deutlich stärker sein als das Testmodell.", + "wizard.judge.api_key_env.label": "API-Key Env-Variable", + "wizard.judge.api_key_env.hint": "Env-Variable mit dem API-Key. Leer = lokales Judge-Modell.", + "wizard.judge.min_score.label": "Minimaler Durchschnitts-Judge-Score", + "wizard.judge.min_score.hint": "1–10. Darunter triggert auto_revert. 5.0 ist \"Durchschnitt\"; 7.0 ist \"gut\".", + "wizard.governance.title": "Datengouvernanz (Artikel 10)", + "wizard.governance.hint": "Dokumentieren Sie Datenherkunft + Bias. Pflicht für High-risk-Deployments.", + "wizard.governance.collection_method.label": "Erhebungsmethode", + "wizard.governance.collection_method.hint": "Artikel 10(2)(b): Wie wurden Trainingsdaten beschafft.", + "wizard.governance.collection_method.placeholder": "Public Web-Archive 2023–2025 gescraped, URL-Hash dedupliziert.", + "wizard.governance.collection_method.detail": "Sagen Sie Prüfern WIE Ihre Daten in den Trainer kamen. Vage Antworten (\"das Internet\") fallen bei Artikel-10-Audits durch.", + "wizard.governance.annotation_process.label": "Annotationsprozess", + "wizard.governance.annotation_process.hint": "Artikel 10(2)(b): Labelling-Methodik — wer labelte mit welchen Richtlinien.", + "wizard.governance.annotation_process.placeholder": "Zwei trainierte Annotatoren labelten jedes Beispiel; Konflikte durch Senior-Review.", + "wizard.governance.annotation_process.detail": "Wenn die Daten gelabelt sind, beschreiben Sie wie. Annotator-Übereinstimmung, Richtlinien-Version, Konfliktlösung.", + "wizard.governance.known_biases.label": "Bekannte Verzerrungen", + "wizard.governance.known_biases.hint": "Artikel 10(2)(f): bekannte Verzerrungen in den Daten.", + "wizard.governance.known_biases.placeholder": "Überwiegend Englisch; Altersgruppen <18 und >65 unterrepräsentiert.", + "wizard.governance.known_biases.detail": "Kein Datensatz ist perfekt ausgewogen. Die Ungleichgewichte vorab zu nennen ist die ehrliche Antwort.", + "wizard.governance.personal_data.label": "Trainingsdaten enthalten personenbezogene Daten", + "wizard.governance.personal_data.hint": "Artikel 10(5) + DSGVO: ankreuzen wenn Beispiele identifizierbare Personen enthalten.", + "wizard.governance.dpia_completed.label": "DSFA abgeschlossen (DSGVO Art. 35)", + "wizard.governance.dpia_completed.hint": "Datenschutz-Folgenabschätzung für diesen Datensatz fertig und dokumentiert.", + "wizard.webhook.title": "Benachrichtigungen (Webhook)", + "wizard.webhook.hint": "Slack/Teams/Discord-Alerts bei Trainings-Events senden.", + "wizard.webhook.url.label": "Webhook-URL oder Env-Variable", + "wizard.webhook.url.hint": "Direkte HTTPS-URL oder Env-Variable die sie trägt (für Secrets empfohlen).", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... ODER env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "URL kann direkt eingefügt werden, oder via env:-Präfix auf eine Umgebungsvariable verweisen. Env-Variante hält die URL aus YAML + Audit-Log.", + "wizard.webhook.enabled.label": "Webhook-Benachrichtigungen senden", + "wizard.webhook.enabled.hint": "Wenn aktiv, sendet ForgeLM eine Webhook-Payload bei den ausgewählten Events. Aus = die YAML enthält keinen Webhook-Block.", + "wizard.webhook.enabled.detail": "Benachrichtigungen leiten Trainings-Events in Ihren Team-Chat (Slack / Teams / Discord) oder einen HTTPS-Endpoint, der JSON akzeptiert. Audit-Log-Einträge dokumentieren jeden Versand — auch Fehler — als unabhängige Spur. Aus lassen, wenn die YAML keinerlei Webhook-Config tragen soll.", + "wizard.webhook.notify_start.label": "Bei Trainingsstart benachrichtigen", + "wizard.webhook.notify_success.label": "Bei Erfolg benachrichtigen", + "wizard.webhook.notify_failure.label": "Bei Fehler benachrichtigen", + "wizard.monitoring.title": "Post-Market-Monitoring (Art. 12+17)", + "wizard.monitoring.hint": "Trainings-Metriken in Prometheus/Datadog/Custom-Webhook für laufende Observability.", + "wizard.monitoring.enabled.label": "Monitoring-Hooks aktivieren", + "wizard.monitoring.enabled.hint": "Pflicht für High-risk-Systeme nach EU AI Act Artikel 12 + 17.", + "wizard.monitoring.enabled.detail": "Nach Deployment eines High-risk-Systems verlangt der EU AI Act laufende Beobachtung — Drift-Detection, Fehler-Reporting, periodische Genauigkeitschecks.", + "wizard.monitoring.endpoint.label": "Monitoring-Endpoint", + "wizard.monitoring.endpoint.hint": "URL Ihres Prometheus-Push-Gateways, Datadogs oder Custom-Webhook-Receivers.", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics ODER env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "Metrik-Format", + "wizard.monitoring.metrics_export.none": "Keine", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "Custom Webhook", + "wizard.monitoring.alert_on_drift.label": "Bei Drift alerten", + "wizard.monitoring.alert_on_drift.hint": "Webhook-Alert wenn Drift-Detector eine Regression markiert.", + "wizard.synthetic.title": "Synthetische Datengenerierung (erweitert)", + "wizard.synthetic.hint": "Teacher → Student Trainingsdaten mit stärkerem Modell generieren.", + "wizard.synthetic.enabled.label": "Synthetische Daten generieren", + "wizard.synthetic.enabled.hint": "Lass ein Teacher-Modell über Ihre Seed-Prompts Trainings-Beispiele produzieren.", + "wizard.synthetic.enabled.detail": "Synthetische Datengenerierung lässt ein stärkeres Modell (z.B. GPT-4) Trainings-Beispiele für Ihr kleineres Fine-tune-Ziel produzieren.", + "wizard.synthetic.teacher_model.label": "Teacher-Modell", + "wizard.synthetic.teacher_model.hint": "API-Modellname (gpt-4o, claude-opus-4-7) oder lokaler Pfad.", + "wizard.synthetic.seed_file.label": "Seed-Prompts-Datei", + "wizard.synthetic.seed_file.hint": "JSONL oder Plain-Text Seed-Prompts. Teacher generiert Antworten dafür.", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Teacher API-Key Env-Variable", + "wizard.synthetic.api_key_env.hint": "Env-Variable mit dem Teacher-API-Key. Leer = lokales Teacher-Modell.", + "wizard.retention.title": "Aufbewahrungsfristen (DSGVO Art. 5+17)", + "wizard.retention.hint": "Wie lange ForgeLM Audit-Logs, Staging-Modelle, ephemere Artefakte aufbewahrt.", + "wizard.retention.audit_log.label": "Audit-Log Aufbewahrung (Tage)", + "wizard.retention.audit_log.hint": "Wie lange audit_log.jsonl behalten. Default 1825 (5 Jahre) entspricht EU AI Act-Vorgabe.", + "wizard.retention.staging.label": "Staging-Horizont (Tage)", + "wizard.retention.staging.hint": "Wie lange abgelehnte Modelle in final_model.staging/ behalten. Default 7.", + "wizard.retention.ephemeral.label": "Ephemere Artefakte (Tage)", + "wizard.retention.ephemeral.hint": "Wie lange Zwischen-Checkpoints, Eval-Samples behalten. Default 90.", + "wizard.retention.raw_documents.label": "Rohdokumente (Tage)", + "wizard.retention.raw_documents.hint": "Wie lange rohe PDFs/DOCX nach Ingestion behalten. Default 90.", + "wizard.retention.enforce.label": "Automatische Löschung erzwingen", + "wizard.retention.enforce.hint": "Wenn aktiv löscht ForgeLM Artefakte nach ihrem Horizont automatisch. Default aus.", + "wizard.retention.enforce.log_only": "log_only — Verstöße ins Audit loggen (Default)", + "wizard.retention.enforce.warn": "warn_on_excess — log + stderr-Warnung", + "wizard.retention.enforce.block": "block_on_excess — Trainer abbrechen (regulierte CI)", "wizard.step.review.title": "Konfiguration prüfen", "wizard.step.review.name": "Prüfen", "wizard.step.review.desc": "Hier ist das YAML. Kopieren, herunterladen und ausführen.", @@ -1934,16 +2563,35 @@ "wizard.review.reset": "Wizard zurücksetzen", "wizard.review.reset.confirm":"Alle Wizard-Antworten zurücksetzen und neu starten? Ihr YAML geht verloren.", "wizard.review.run_label": "Dann ausführen:", + "wizard.error.model.required": "Sie haben im vorherigen Schritt „Custom HuggingFace-Modell“ gewählt — deshalb ist dieses Feld Pflicht. Geben Sie eine org/repo-ID ein (z. B. meta-llama/Llama-3.1-8B-Instruct) oder gehen Sie zurück und wählen Sie ein Preset.", + "wizard.error.model.format": "HuggingFace-Hub-IDs verwenden das Format org/repo (z. B. meta-llama/Llama-3.1-8B-Instruct). Ein Name ohne Schrägstrich wird zur Trainingszeit nicht aufgelöst.", + "wizard.error.dataset.required": "Wählen Sie ein Dataset, bevor Sie fortfahren — ohne Pfad oder HuggingFace-Namen hat der Trainer nichts zum Lernen.", + "wizard.error.webhook.required": "Sie haben Benachrichtigungen aktiviert — wir brauchen also eine Webhook-URL oder Env-Variable. Schalten Sie Benachrichtigungen oben aus oder fügen Sie Ihre Slack-/Teams-/Discord-URL ein (für Produktion env:SLACK_WEBHOOK_URL).", + "wizard.error.monitoring.endpoint.required": "Sie haben Post-Market-Monitoring aktiviert — wir brauchen einen Endpoint für Metriken. Schalten Sie Monitoring oben aus oder fügen Sie Ihre Prometheus-/Datadog-/Custom-Webhook-URL ein (für Secrets env:VAR_NAME).", + "wizard.error.synthetic.teacher_model.required": "Sie haben synthetische Datengenerierung aktiviert — wir brauchen einen Teacher-Modellnamen (z. B. gpt-4o, claude-opus-4-7 oder lokaler Pfad).", + "wizard.error.synthetic.seed_file.required": "Sie haben synthetische Datengenerierung aktiviert — wir brauchen eine Seed-Prompts-Datei (z. B. ./data/seed_prompts.jsonl); der Teacher generiert pro Prompt eine Antwort.", + "wizard.error.synthetic.api_key_env.required": "Sie haben synthetische Datengenerierung aktiviert — wir brauchen die Env-Variable mit dem Teacher-API-Key (üblich: OPENAI_API_KEY oder ANTHROPIC_API_KEY).", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1 fordert den juristischen Anbieternamen für High-risk-Systeme. Eintragen oder Risikoklassifizierung senken.", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1 fordert einen menschenlesbaren Systemnamen für High-risk-Deployments. Eintragen oder Risikoklassifizierung senken.", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1 fordert einen erklärten Verwendungszweck für High-risk-Systeme. In einfacher Sprache beschreiben oder Risikoklassifizierung senken.", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act Artikel 9 fordert die Dokumentation der bestimmungsgemäßen Verwendung für High-risk-Systeme. Beschreiben Sie, was das System tun soll, oder senken Sie die Risikoklassifizierung.", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act Artikel 9 fordert vorhersehbare Missbrauchsszenarien für High-risk-Systeme. Eines pro Zeile auflisten oder Risikoklassifizierung senken.", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act Artikel 9 fordert Risikominderungsmaßnahmen für High-risk-Systeme. Maßnahmen (eine pro Zeile) auflisten oder Risikoklassifizierung senken.", + "wizard.error.governance.collection_method.required": "EU AI Act Artikel 10 fordert Datenherkunfts-Dokumentation für High-risk-Systeme. Beschreiben Sie, wie die Trainingsdaten beschafft wurden, oder senken Sie die Risikoklassifizierung.", + "wizard.error.governance.annotation_process.required": "EU AI Act Artikel 10 fordert Annotations-Methodik-Dokumentation. Labelling-Prozess beschreiben („keine Annotation“ für SFT auf Rohtext gilt) oder Risikoklassifizierung senken.", + "wizard.error.governance.known_biases.required": "EU AI Act Artikel 10 fordert dokumentierte Datensatzverzerrungen für High-risk-Systeme. Bekannte Ungleichgewichte auflisten oder Risikoklassifizierung senken.", + "wizard.error.benchmark.tasks.required": "Sie haben das Benchmark-Gate aktiviert — wir brauchen mindestens eine Aufgabe. lm-evaluation-harness-Namen wie arc_easy, hellaswag, mmlu hinzufügen (eine pro Zeile) oder Benchmark oben deaktivieren.", + "wizard.error.judge.api_key_env.required": "Sie haben LLM-as-Judge aktiviert — wir brauchen die Env-Variable mit dem Judge-API-Key (üblich: OPENAI_API_KEY oder ANTHROPIC_API_KEY).", "quickstart.spotlight.eyebrow": "Überspringen Sie das YAML", "quickstart.spotlight.title": "Verwenden Sie den In-Browser-Wizard, um Ihre Konfiguration in 3 Minuten zu erstellen.", "quickstart.spotlight.lead": "Wählen Sie Anwendungsfall, Basismodell, Datensatz und EU-AI-Act-Risikostufe. Der Wizard schreibt eine funktionierende quickstart-generated.yaml, die Sie mit einem Befehl ausführen können — keine Fine-Tuning-Expertise nötig.", "quickstart.spotlight.cta_primary": "Wizard öffnen", "quickstart.spotlight.cta_secondary":"Oder folgen Sie der 5-Schritt-Anleitung", - "quickstart.spotlight.meta1": "7 geführte Schritte", + "quickstart.spotlight.meta1": "9 geführte Schritte", "quickstart.spotlight.meta2": "Live-YAML-Vorschau", "quickstart.spotlight.meta3": "EU-AI-Act-Voreinstellungen", - "quickstart.spotlight.stage1.label": "7 Fragen beantworten", - "quickstart.spotlight.stage1.snippet":"Anwendungsfall · Modell · Daten · Risiko", + "quickstart.spotlight.stage1.label": "9 Fragen beantworten", + "quickstart.spotlight.stage1.snippet":"Anwendungsfall · Trainer · Modell · Daten · Risiko", "quickstart.spotlight.stage2.label": "Gültiges YAML erhalten", "quickstart.spotlight.stage3.label": "Ausführen", "quickstart.spotlight.manual_intro": "Oder folgen Sie der manuellen 5-Schritt-Referenz unten" @@ -2041,8 +2689,43 @@ "wizard.dataset.pdf.desc": "ForgeLM les ingérera en JSONL avant l'entraînement (PII + secrets masqués).", "wizard.dataset.name.label": "Chemin ou nom du dataset", "wizard.dataset.name.hint": "Nom HuggingFace (org/dataset), chemin de fichier local (./data/train.jsonl) ou répertoire (./documents/).", - "wizard.dataset.autodetect.title": "Le format est auto-détecté.", - "wizard.dataset.autodetect.body": "ForgeLM lit directement les champs JSONL : les lignes avec un tableau messages deviennent du chat multi-tours ; les lignes avec prompt + response deviennent du suivi d'instructions. Les datasets mixtes ne sont pas pris en charge — le loader choisit la forme à partir de la première ligne.", + "wizard.dataset.shape.sft.title": "Le format est auto-détecté.", + "wizard.dataset.shape.sft.body": "ForgeLM lit directement les champs JSONL : les lignes avec un tableau messages deviennent du chat multi-tours ; les lignes avec prompt + response deviennent du suivi d'instructions. Les datasets mixtes ne sont pas pris en charge — le loader choisit la forme à partir de la première ligne.", + "wizard.dataset.shape.preference.title": "Lignes de préférence requises.", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO requièrent des triples de préférence sur chaque ligne : { \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }. Le trainer apprend à préférer chosen à rejected. Convertissez vos données d'abord si elles ne sont pas déjà sous cette forme — sinon le runtime émet une erreur.", + "wizard.dataset.shape.kto.title": "Lignes de feedback binaire requises.", + "wizard.dataset.shape.kto.body": "KTO requiert des completions individuelles avec un label pouce-haut / pouce-bas : { \"prompt\": ..., \"completion\": ..., \"label\": true|false }. Aucune préférence appariée nécessaire — KTO fonctionne avec le type de données qu'un widget de feedback produit réellement.", + "wizard.dataset.shape.grpo.title": "Lignes de rollout GRPO.", + "wizard.dataset.shape.grpo.body": "GRPO génère plusieurs réponses par prompt et les classe avec un signal de reward. Lignes : { \"prompt\": ... }. Pour les problèmes de type maths, ajoutez un champ gold_answer optionnel — le reward regex-correctness intégré l'utilise. Sans modèle de reward, le trainer revient au shaping format/longueur.", + "wizard.step.trainer.title": "Choisissez votre paradigme d'alignement", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "Six trainers, une sélection. L'étape cas d'usage a choisi un défaut sensé — changez-le ici si vous avez besoin d'une autre méthode d'alignement.", + "wizard.step.trainer.tutorial": "Les trainers diffèrent par CE qu'ils optimisent. SFT apprend des mappings input → output à partir d'exemples étiquetés. DPO / ORPO / SimPO comparent une réponse choisie à une réponse rejetée et renforcent la différence. KTO fonctionne avec un feedback pouce-haut / pouce-bas individuel plutôt que des préférences appariées. GRPO échantillonne plusieurs réponses par prompt et les classe avec un reward — meilleur pour les tâches de raisonnement. Le trainer choisi détermine la forme requise de votre dataset.", + "wizard.trainer.recommended": "bon défaut", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "Supervisé", + "wizard.trainer.sft.desc": "Fine-tune sur des exemples input → output étiquetés. Le défaut pour chat, instructions, résumé et expertise de domaine BYOD.", + "wizard.trainer.sft.shape": "lignes : messages[] OU prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "Préférence", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation. Apprend de paires chosen/rejected sans modèle de reward explicite. Courant pour l'alignement pouce-haut après SFT.", + "wizard.trainer.dpo.shape": "lignes : prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation. Comme DPO mais sans modèle de référence — combine SFT + alignment en une seule passe.", + "wizard.trainer.orpo.shape": "lignes : prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "Normalisé en longueur", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation. Reference-free, reward normalisé en longueur — évite le biais de verbosité que DPO montre parfois.", + "wizard.trainer.simpo.shape": "lignes : prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "Feedback binaire", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation. Fonctionne avec des labels pouce-haut / pouce-bas individuels — parfait quand vous avez des widgets de feedback mais pas de préférences appariées.", + "wizard.trainer.kto.shape": "lignes : prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "Raisonnement", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation. Échantillonne plusieurs réponses par prompt et les classe avec un signal de reward. Meilleur pour maths, code et raisonnement étape par étape.", + "wizard.trainer.grpo.shape": "lignes : prompt (+ gold_answer optionnel)", "wizard.step.training.title": "Paramètres d'entraînement", "wizard.step.training.name": "Entraînement", "wizard.step.training.desc": "Les valeurs par défaut fonctionnent dans 90 % des cas. Ne tunez que si vous savez pourquoi.", @@ -2065,10 +2748,64 @@ "wizard.training.batch.label": "Batch size (par device)", "wizard.training.batch.hint": "Des batches plus grands entraînent plus vite mais demandent plus de VRAM. Commencez par 2 si vous n'êtes pas sûr.", "wizard.training.batch.detail": "La taille de batch est combien d'exemples le GPU traite en parallèle. Des batches plus grands = entraînement plus rapide et gradients plus stables, mais utilisent plus de VRAM. Avec QLoRA sur un GPU 12 Go, vous pouvez généralement faire tenir batch=2 avec un modèle 8B ; en cas d'OOM, descendez à 1 et augmentez l'accumulation de gradient.", + "wizard.training.lora_method.label": "Variante LoRA", + "wizard.training.lora_method.hint": "PiSSA demande plus de mémoire mais converge plus vite ; RSLoRA stabilise les rangs élevés (r > 64) ; DoRA décompose les poids pour une qualité légèrement meilleure.", + "wizard.training.lora_method.detail": "LoRA standard est le défaut sûr et convient à tous les scénarios 8B-sur-12-GB. DoRA divise la mise à jour LoRA en magnitude + direction — petit gain de qualité à coût modéré. PiSSA initialise les adapters depuis les composantes principales des poids de base, converge plus vite sur petits datasets. RSLoRA rescale le facteur alpha pour rangs élevés (r=64+) ; bon choix quand l'entraînement LoRA standard stagne ou produit NaN.", + "wizard.training.lora_method.standard.title": "Standard", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA. Défaut sûr pour tout.", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "Séparation magnitude + direction. ~5% de gain qualité.", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "Adapters initialisés SVD. Convergence rapide sur petites données.", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rang-stabilisé. Utiliser quand r > 64 ou entraînement instable.", + "wizard.training.galore.label": "Utiliser GaLore (entraînement plein-paramètre, sans adapter)", + "wizard.training.galore.hint": "Remplace LoRA par entraînement plein-paramètre projeté par gradient. VRAM crête plus basse mais plus lent par étape.", + "wizard.training.galore.detail": "GaLore entraîne chaque paramètre via projection de gradient low-rank — pas d'adapter du tout. La VRAM crête tombe sous QLoRA sur grands modèles car l'état optimizer est projeté dans un sous-espace low-rank. Compute par étape est plus élevé (projection recalculée toutes les quelques étapes), mais le résultat est un modèle entièrement fine-tuné sans merge LoRA. Utiliser avec 24 GB+ et besoin d'un seul modèle déployable plutôt que base + adapter.", + "wizard.training.galore_optim.label": "Variante optimizer GaLore", + "wizard.training.galore_optim.hint": "_8bit divise par 2 la VRAM optimizer-state ; _layerwise réduit la crête par recomputation par couche.", + "wizard.training.galore_optim.detail": "GaLore remplace l'optimizer standard. Les variantes _8bit quantifient l'état optimizer en 8-bit (énormes économies VRAM, pas de réelle perte de qualité). Les variantes _layerwise calculent la projection GaLore une couche à la fois plutôt que toutes en même temps — plus lent mais permet d'entraîner des modèles qui sinon OOM. Default galore_adamw_8bit est le bon choix pour la plupart des configurations.", + "wizard.training.galore_optim.adamw": "AdamW (pleine précision)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-bit (recommandé)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW par couche", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-bit par couche", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor par couche", + "wizard.training.trainer_section.title": "Hyperparamètres {trainer}", + "wizard.training.trainer_section.body": "Ces réglages sont spécifiques au trainer sélectionné. Les défauts sont sensés — n'ajustez que si vous comprenez ce qu'ils font et avez des données pour le justifier.", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "Température contrôlant la déviation KL du modèle de référence. 0.1 est le défaut canonique.", + "wizard.training.dpo_beta.detail": "DPO pénalise la policy pour s'éloigner d'un modèle de référence. Beta échelonne cette pénalité — beta bas laisse le modèle changer plus vite (et risque de surapprentissage aux préférences) ; beta haut le maintient proche de la référence. Le papier DPO recommande 0.1 comme point de départ. Tunez 0.05–0.2 seulement après mesure sur un set de préférence held-out.", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Poids odds-ratio. 0.1 défaut schema ; valeurs plus hautes pénalisent les outputs rejetés plus agressivement.", + "wizard.training.orpo_beta.detail": "ORPO remplace le modèle de référence DPO par un terme odds-ratio dans la loss SFT. Beta contrôle la force de pénalisation de la likelihood de la réponse rejetée relative à la chosen. 0.1 est le défaut publié ; passez à 0.2–0.3 si le modèle continue à préférer les réponses rejected-style.", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Terme de marge imposant une différence minimum de reward entre chosen et rejected. 0.5 défaut publié.", + "wizard.training.simpo_gamma.detail": "Gamma fixe une marge minimum entre les log-probabilités de chosen et rejected. Gamma plus grand = séparation plus agressive, mais la loss peut stagner si aucune paire de préférence ne dépasse la marge. Restez à 0.5 sauf mesure des distributions de gap chosen/rejected.", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "Facteur d'échelle de reward. Multiplie les log-probs avant la comparaison de marge. 2.0 défaut publié.", + "wizard.training.simpo_beta.detail": "Beta échelonne le signal de reward par token avant l'application de la marge gamma. Beta haut amplifie la différence entre séquences chosen et rejected (apprentissage de préférence plus net) mais peut aussi amplifier le bruit. Le papier SimPO fixe beta = 2.0 avec gamma = 0.5 ; tunez en paire, pas indépendamment.", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "Poids de divergence KL contre le modèle de référence. 0.1 défaut reflète DPO.", + "wizard.training.kto_beta.detail": "KTO est plus proche en esprit de RLHF que de DPO — façonne la loss avec l'asymétrie d'aversion à la perte de la prospect theory, échelonnée par beta. Le défaut 0.1 est calibré pour les ratios typiques de feedback pouce-haut / pouce-bas. Baissez si votre dataset est lourdement biaisé vers un label.", + "wizard.training.grpo_num_generations.label": "Générations par prompt", + "wizard.training.grpo_num_generations.hint": "Nombre de réponses que GRPO échantillonne par prompt avant le classement. 4 équilibré ; 8 plus qualitatif mais plus lent.", + "wizard.training.grpo_num_generations.detail": "GRPO classe N réponses échantillonnées les unes contre les autres pour construire le gradient policy. Plus d'échantillons = gradient mieux conditionné et convergence plus rapide par étape, mais chaque étape coûte N forward passes. 4 est le sweet spot pour modèle 8B sur GPU unique ; passez à 8 pour maths/raisonnement où la qualité du classement compte.", + "wizard.training.grpo_max_completion_length.label": "Longueur max de completion", + "wizard.training.grpo_max_completion_length.hint": "Plafond de tokens par réponse générée. 512 couvre la plupart des chaînes de raisonnement ; augmentez pour maths longs.", + "wizard.training.grpo_max_completion_length.detail": "GRPO tronque les rollouts générés à ce nombre de tokens. Plafonds plus courts = étapes plus rapides mais peuvent tronquer le raisonnement en cours ; plafonds plus longs explorent plus mais gaspillent du compute en padding. 512 défaut équilibré — pour maths style GSM8K vous pouvez descendre à 384, pour problèmes niveau olympiade poussez à 1024+.", + "wizard.training.grpo_reward_model.label": "Modèle de reward (optionnel)", + "wizard.training.grpo_reward_model.hint": "ID HuggingFace Hub d'un modèle de reward pour scorer les générations. Laissez vide pour utiliser le reward format/longueur-shaping intégré.", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "Quand vous fournissez un ID de modèle de reward, GRPO l'utilise pour scorer chaque rollout — c'est la recette standard pour l'alignement RLHF-style. Laissez vide et ForgeLM revient au combined_format_length_reward intégré : un signal format/longueur gradient-riche qui ne nécessite pas de modèle supplémentaire. Pour problèmes maths, ajouter aussi un champ gold_answer au dataset déclenche un reward regex-correctness en plus.", "wizard.step.compliance.title": "Évaluation & EU AI Act", "wizard.step.compliance.name": "Évaluation & conformité", "wizard.step.compliance.desc": "Gates qualité et artefacts réglementaires.", "wizard.step.compliance.tutorial": "ForgeLM vérifie le modèle fine-tuné AVANT que vous le livriez. L'auto-revert annule le fine-tune s'il est pire que le modèle de base. Llama Guard scanne les sorties pour du contenu nuisible. Le niveau de risque EU AI Act que vous choisissez détermine quels artefacts de conformité sont auto-générés et si l'approbation humaine est REQUISE avant déploiement.", + "wizard.step.operations.title": "Opérations et paramètres avancés", + "wizard.step.operations.name": "Opérations", + "wizard.step.operations.desc": "Notifications, surveillance post-marché, génération de données synthétiques et horizons de rétention. Tous optionnels — passez ceux dont vous n'avez pas besoin.", + "wizard.step.operations.tutorial": "Ces quatre blocs couvrent tout ce que ForgeLM offre au-delà du flux core entraînement + compliance. Webhook envoie une alerte Slack/Teams/Discord au démarrage / succès / échec. La surveillance post-marché (Articles 12+17) connecte la détection de dérive à Prometheus / Datadog après déploiement. La génération synthétique laisse un modèle teacher plus fort produire des exemples supplémentaires. Les horizons de rétention fixent des TTL conformes RGPD pour audit log et artefacts éphémères. Aucun n'est obligatoire — laissez replié ce dont votre run n'a pas besoin.", "wizard.risk.label": "Classification de risque EU AI Act", "wizard.risk.hint": "Choisissez le niveau dans lequel votre déploiement tombe. High-risk et unacceptable déclenchent des obligations d'audit + approbation humaine obligatoires.", "wizard.risk.unknown": "Unknown — placeholder de pré-classification", @@ -2103,6 +2840,114 @@ "wizard.compliance.intended_purpose.label": "Objectif visé", "wizard.compliance.intended_purpose.hint": "Annex IV §1 : objectif déclaré du système en langage simple.", "wizard.compliance.intended_purpose.placeholder":"Trier les tickets de support client par chat", + "wizard.risk_assessment.title": "Évaluation des risques (EU AI Act Article 9)", + "wizard.risk_assessment.hint": "Obligatoire pour les systèmes high-risk. Optionnel mais recommandé sinon.", + "wizard.risk_assessment.intended_use.label": "Utilisation prévue", + "wizard.risk_assessment.intended_use.hint": "Article 9(2)(a) : décrivez l'objectif prévu du système en langage simple.", + "wizard.risk_assessment.intended_use.placeholder": "Un assistant support client qui trie les tickets et rédige les réponses.", + "wizard.risk_assessment.intended_use.detail": "C'est l'énoncé canonique de ce que votre système est conçu pour faire — il ancre le reste de l'évaluation des risques. Soyez précis : \"trier les tickets support\" est mieux que \"aider les utilisateurs\". Les évaluateurs et régulateurs le lisent en premier pour décider si les mitigations en aval sont appropriées.", + "wizard.risk_assessment.foreseeable_misuse.label": "Mauvais usage prévisible (un par ligne)", + "wizard.risk_assessment.foreseeable_misuse.hint": "Article 9(2)(b) : scénarios où le système pourrait être détourné.", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "Générer des emails de phishing\nAttaques d'ingénierie sociale\nContourner les garde-fous de sécurité via prompt injection", + "wizard.risk_assessment.foreseeable_misuse.detail": "Listez les scénarios de mauvais usage que vous pouvez raisonnablement anticiper — pas chaque attaque théorique, mais ceux pertinents pour votre contexte de déploiement. Exercice standard : imaginez un attaquant hostile mais paresseux. Quelle est la façon la plus facile d'abuser de cela ? Chaque ligne devient un élément de liste YAML.", + "wizard.risk_assessment.mitigation_measures.label": "Mesures de mitigation (une par ligne)", + "wizard.risk_assessment.mitigation_measures.hint": "Article 9(2)(c) : contrôles que vous mettez en place contre les risques de mauvais usage ci-dessus.", + "wizard.risk_assessment.mitigation_measures.placeholder": "Filtre sécurité Llama Guard sur toutes les sorties\nRate limiting par utilisateur\nValidation humaine pour réponses à risque élevé", + "wizard.risk_assessment.mitigation_measures.detail": "Pour chaque mauvais usage prévisible, nommez une mitigation concrète. Les évaluateurs comparent cette liste à la liste de mauvais usage — les lacunes sont un drapeau rouge. Chaque ligne devient un élément de liste YAML. Les portails safety eval et validation humaine intégrés à ForgeLM sont de bons éléments de première ligne ici.", + "wizard.risk_assessment.vulnerable_groups.label": "Impact sur les groupes vulnérables considéré", + "wizard.risk_assessment.vulnerable_groups.hint": "Article 9(2)(b) : confirmez si vous avez considéré les effets sur enfants, minorités, etc.", + "wizard.risk_assessment.vulnerable_groups.detail": "L'Article 9 mentionne explicitement les groupes vulnérables (enfants, personnes âgées, minorités, personnes handicapées). Cochez seulement après avoir réellement réfléchi à comment votre système pourrait les affecter — une fausse certification est pire qu'aucune.", + "wizard.eval_gates.title": "Portails qualité (avancé)", + "wizard.eval_gates.hint": "Benchmark optionnel + LLM-as-Judge en plus des portails loss/safety basiques.", + "wizard.benchmark.enabled.label": "Lancer benchmark lm-evaluation-harness", + "wizard.benchmark.enabled.hint": "Évaluez le modèle fine-tuné sur tâches académiques après l'entraînement.", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness est le standard de facto pour benchmarking LLM. Activer lance les tâches listées contre votre modèle fine-tuné et stocke résultats JSON.", + "wizard.benchmark.tasks.label": "Tâches (une par ligne)", + "wizard.benchmark.tasks.hint": "Communes : arc_easy, arc_challenge, hellaswag, mmlu, truthfulqa, winogrande, gsm8k.", + "wizard.benchmark.min_score.label": "Précision moyenne minimale", + "wizard.benchmark.min_score.hint": "0.0–1.0. En dessous, auto_revert annule le fine-tune. Vide = pas de portail seuil.", + "wizard.benchmark.min_score.placeholder": "ex. 0.55", + "wizard.judge.enabled.label": "Notation LLM-as-Judge", + "wizard.judge.enabled.hint": "Notez les générations avec un modèle plus fort (ex. GPT-4o).", + "wizard.judge.enabled.detail": "LLM-as-Judge demande à un modèle plus fort de noter les sorties sur échelle 1–10. Mieux que benchmarks pour qualité chat/instructions sans réponse unique.", + "wizard.judge.model.label": "Modèle juge", + "wizard.judge.model.hint": "Nom modèle API (gpt-4o, claude-opus-4-7) ou chemin local. Le juge doit être nettement plus fort.", + "wizard.judge.api_key_env.label": "Variable env clé API", + "wizard.judge.api_key_env.hint": "Nom variable env portant la clé API. Vide = modèle juge local.", + "wizard.judge.min_score.label": "Score moyen juge minimum", + "wizard.judge.min_score.hint": "Échelle 1–10. En dessous déclenche auto_revert. 5.0 \"moyen\" ; 7.0 \"bon\".", + "wizard.governance.title": "Gouvernance des données (Article 10)", + "wizard.governance.hint": "Documentez provenance + biais des données. Obligatoire pour déploiements high-risk.", + "wizard.governance.collection_method.label": "Méthode de collecte", + "wizard.governance.collection_method.hint": "Article 10(2)(b) : comment les données d'entraînement ont été obtenues.", + "wizard.governance.collection_method.placeholder": "Archives web publiques 2023–2025 scrapées, dédupliquées par hash URL.", + "wizard.governance.collection_method.detail": "Dites aux évaluateurs COMMENT vos données sont arrivées dans le trainer. Réponses vagues (\"internet\") échouent aux audits Article 10.", + "wizard.governance.annotation_process.label": "Processus d'annotation", + "wizard.governance.annotation_process.hint": "Article 10(2)(b) : méthodologie d'étiquetage — qui, avec quelles directives.", + "wizard.governance.annotation_process.placeholder": "Deux annotateurs entraînés étiquetèrent chaque exemple ; conflits résolus par revue senior.", + "wizard.governance.annotation_process.detail": "Si données étiquetées, décrivez comment. Accord inter-annotateurs, version directives, résolution de conflits.", + "wizard.governance.known_biases.label": "Biais connus", + "wizard.governance.known_biases.hint": "Article 10(2)(f) : biais que l'opérateur connaît dans les données.", + "wizard.governance.known_biases.placeholder": "Principalement anglais ; tranches d'âge <18 et >65 sous-représentées.", + "wizard.governance.known_biases.detail": "Aucun dataset n'est parfaitement équilibré. Nommer les déséquilibres en amont est la réponse honnête.", + "wizard.governance.personal_data.label": "Données d'entraînement contiennent données personnelles", + "wizard.governance.personal_data.hint": "Article 10(5) + RGPD : cocher si exemples incluent personnes identifiables.", + "wizard.governance.dpia_completed.label": "AIPD complétée (RGPD Art. 35)", + "wizard.governance.dpia_completed.hint": "Analyse d'Impact Protection Données terminée et documentée pour ce dataset.", + "wizard.webhook.title": "Notifications (webhook)", + "wizard.webhook.hint": "Envoyez alerts Slack/Teams/Discord sur événements d'entraînement.", + "wizard.webhook.url.label": "URL webhook ou variable env", + "wizard.webhook.url.hint": "URL HTTPS directe ou variable env la portant (recommandé pour secrets).", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... OU env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "Vous pouvez coller URL directement, ou utiliser préfixe env: pour pointer une variable env.", + "wizard.webhook.enabled.label": "Envoyer des notifications webhook", + "wizard.webhook.enabled.hint": "Quand activé, ForgeLM publie un payload webhook aux événements sélectionnés. Désactivé = la YAML ne contient aucun bloc webhook.", + "wizard.webhook.enabled.detail": "Les notifications connectent les événements d'entraînement à votre chat d'équipe (Slack / Teams / Discord) ou tout endpoint HTTPS acceptant du JSON. Les entrées de l'audit log enregistrent chaque envoi — y compris les échecs — pour une piste indépendante. Laissez désactivé si vous ne voulez aucune config webhook dans la YAML.", + "wizard.webhook.notify_start.label": "Notifier au démarrage", + "wizard.webhook.notify_success.label": "Notifier au succès", + "wizard.webhook.notify_failure.label": "Notifier à l'échec", + "wizard.monitoring.title": "Surveillance post-marché (Art. 12+17)", + "wizard.monitoring.hint": "Branchez métriques d'entraînement à Prometheus/Datadog/webhook custom.", + "wizard.monitoring.enabled.label": "Activer hooks monitoring", + "wizard.monitoring.enabled.hint": "Obligatoire pour systèmes high-risk selon EU AI Act Articles 12 + 17.", + "wizard.monitoring.enabled.detail": "Une fois système high-risk déployé, EU AI Act exige observation continue — détection de drift, reporting d'erreurs.", + "wizard.monitoring.endpoint.label": "Endpoint monitoring", + "wizard.monitoring.endpoint.hint": "URL de votre Prometheus push gateway, Datadog ou récepteur webhook custom.", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics OU env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "Format métriques", + "wizard.monitoring.metrics_export.none": "Aucun", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "Webhook custom", + "wizard.monitoring.alert_on_drift.label": "Alerte sur drift", + "wizard.monitoring.alert_on_drift.hint": "Émettre alert webhook quand détecteur drift signale régression.", + "wizard.synthetic.title": "Génération données synthétiques (avancé)", + "wizard.synthetic.hint": "Générez données entraînement teacher → student avec modèle plus fort.", + "wizard.synthetic.enabled.label": "Générer données synthétiques", + "wizard.synthetic.enabled.hint": "Faites tourner modèle teacher sur seed prompts pour produire exemples.", + "wizard.synthetic.enabled.detail": "Génération synthétique permet à modèle plus fort (ex. GPT-4) de produire exemples pour cible fine-tune plus petite.", + "wizard.synthetic.teacher_model.label": "Modèle teacher", + "wizard.synthetic.teacher_model.hint": "Nom modèle API (gpt-4o, claude-opus-4-7) ou chemin local.", + "wizard.synthetic.seed_file.label": "Fichier seed prompts", + "wizard.synthetic.seed_file.hint": "JSONL ou texte simple de seed prompts. Teacher génère réponse pour chacun.", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Variable env clé API teacher", + "wizard.synthetic.api_key_env.hint": "Nom variable env portant clé API teacher. Vide = modèle teacher local.", + "wizard.retention.title": "Horizons de rétention (RGPD Art. 5+17)", + "wizard.retention.hint": "Combien de temps ForgeLM garde audit logs, modèles staging, artefacts éphémères.", + "wizard.retention.audit_log.label": "Rétention audit log (jours)", + "wizard.retention.audit_log.hint": "Combien de temps garder audit_log.jsonl. Default 1825 (5 ans) match EU AI Act.", + "wizard.retention.staging.label": "Horizon staging (jours)", + "wizard.retention.staging.hint": "Combien de temps garder modèles rejetés dans final_model.staging/. Default 7.", + "wizard.retention.ephemeral.label": "Artefacts éphémères (jours)", + "wizard.retention.ephemeral.hint": "Combien de temps garder checkpoints intermédiaires, samples eval. Default 90.", + "wizard.retention.raw_documents.label": "Documents bruts (jours)", + "wizard.retention.raw_documents.hint": "Combien de temps garder PDFs/DOCX bruts après ingestion. Default 90.", + "wizard.retention.enforce.label": "Forcer suppression automatique", + "wizard.retention.enforce.hint": "Si actif, ForgeLM auto-supprime artefacts après horizon. Off par default.", + "wizard.retention.enforce.log_only": "log_only — logger violations à l'audit (défaut)", + "wizard.retention.enforce.warn": "warn_on_excess — log + avertissement stderr", + "wizard.retention.enforce.block": "block_on_excess — abandonner trainer (CI régulée)", "wizard.step.review.title": "Revoir votre configuration", "wizard.step.review.name": "Revoir", "wizard.step.review.desc": "Voici le YAML. Copiez-le, téléchargez-le et exécutez.", @@ -2112,6 +2957,25 @@ "wizard.review.reset": "Réinitialiser l'assistant", "wizard.review.reset.confirm":"Réinitialiser toutes les réponses de l'assistant et recommencer ? Votre YAML sera perdu.", "wizard.review.run_label": "Puis exécutez :", + "wizard.error.model.required": "Vous avez choisi « Modèle HuggingFace personnalisé » à l’étape précédente — ce champ est donc requis. Saisissez un ID org/repo (ex. meta-llama/Llama-3.1-8B-Instruct) ou revenez en arrière et choisissez un preset.", + "wizard.error.model.format": "Les ID HuggingFace Hub utilisent le format org/repo (ex. meta-llama/Llama-3.1-8B-Instruct). Un nom sans barre oblique ne sera pas résolu lors de l’entraînement.", + "wizard.error.dataset.required": "Choisissez un dataset avant de continuer — sans chemin ni nom HuggingFace, le trainer n’a rien à apprendre.", + "wizard.error.webhook.required": "Vous avez activé les notifications — il nous faut donc une URL webhook ou une variable d’env. Désactivez les notifications ci-dessus ou collez votre URL Slack / Teams / Discord (pour la production utilisez env:SLACK_WEBHOOK_URL).", + "wizard.error.monitoring.endpoint.required": "Vous avez activé la surveillance post-marché — il nous faut un endpoint pour les métriques. Désactivez la surveillance ci-dessus ou collez votre URL Prometheus / Datadog / webhook custom (pour les secrets utilisez env:VAR_NAME).", + "wizard.error.synthetic.teacher_model.required": "Vous avez activé la génération de données synthétiques — il nous faut un nom de modèle teacher (ex. gpt-4o, claude-opus-4-7 ou chemin local).", + "wizard.error.synthetic.seed_file.required": "Vous avez activé la génération de données synthétiques — il nous faut un fichier de seed prompts (ex. ./data/seed_prompts.jsonl) ; le teacher produira une réponse par prompt.", + "wizard.error.synthetic.api_key_env.required": "Vous avez activé la génération de données synthétiques — il nous faut la variable d’env qui porte la clé API du teacher (généralement OPENAI_API_KEY ou ANTHROPIC_API_KEY).", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1 exige le nom de l’entité juridique du fournisseur pour les systèmes high-risk. Remplissez-le ou abaissez la classification de risque.", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1 exige un nom de système lisible pour les déploiements high-risk. Remplissez-le ou abaissez la classification de risque.", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1 exige une finalité déclarée pour les systèmes high-risk. Décrivez la finalité en langage simple ou abaissez la classification de risque.", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act Article 9 exige que l’usage prévu soit documenté pour les systèmes high-risk. Décrivez ce que le système est censé faire ou abaissez la classification de risque.", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act Article 9 exige des scénarios de mésusage prévisible pour les systèmes high-risk. Listez-les un par ligne ou abaissez la classification de risque.", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act Article 9 exige des mesures d’atténuation pour les systèmes high-risk. Listez les contrôles mis en place (un par ligne) ou abaissez la classification de risque.", + "wizard.error.governance.collection_method.required": "EU AI Act Article 10 exige une documentation sur la provenance des données pour les systèmes high-risk. Décrivez comment les données ont été obtenues ou abaissez la classification de risque.", + "wizard.error.governance.annotation_process.required": "EU AI Act Article 10 exige la documentation de la méthodologie d’annotation. Décrivez votre processus d’étiquetage (« pas d’annotation » est valide pour SFT sur texte brut) ou abaissez la classification de risque.", + "wizard.error.governance.known_biases.required": "EU AI Act Article 10 exige que les biais connus du dataset soient documentés pour les systèmes high-risk. Listez les déséquilibres dont vous avez connaissance ou abaissez la classification de risque.", + "wizard.error.benchmark.tasks.required": "Vous avez activé le benchmark — il nous faut au moins une tâche. Ajoutez des noms lm-evaluation-harness comme arc_easy, hellaswag, mmlu (un par ligne) ou désactivez le benchmark ci-dessus.", + "wizard.error.judge.api_key_env.required": "Vous avez activé LLM-as-Judge — il nous faut la variable d’env qui porte la clé API du juge (généralement OPENAI_API_KEY ou ANTHROPIC_API_KEY).", "quickstart.spotlight.eyebrow": "Configurateur guidé", "quickstart.spotlight.title": "Pas envie d'écrire le YAML à la main ?", "quickstart.spotlight.lead": "L'assistant intégré au navigateur pose sept questions, écrit un YAML valide et vous laisse l'exécuter en une commande. Aucune installation, votre progression est enregistrée localement.", @@ -2120,8 +2984,8 @@ "quickstart.spotlight.meta1": "≈ 3 minutes", "quickstart.spotlight.meta2": "6 langues", "quickstart.spotlight.meta3": "Aucune installation requise", - "quickstart.spotlight.stage1.label": "Répondez à 7 questions", - "quickstart.spotlight.stage1.snippet": "cas d'usage · modèle · données · LoRA · risque", + "quickstart.spotlight.stage1.label": "Répondez à 9 questions", + "quickstart.spotlight.stage1.snippet": "cas d'usage · trainer · modèle · données · risque", "quickstart.spotlight.stage2.label": "Recevez un YAML valide", "quickstart.spotlight.stage3.label": "Exécutez-le", "quickstart.spotlight.manual_intro": "Ou parcourez les cinq étapes manuellement ↓" @@ -2219,8 +3083,43 @@ "wizard.dataset.pdf.desc": "ForgeLM los ingerirá a JSONL antes del entrenamiento (PII + secretos enmascarados).", "wizard.dataset.name.label": "Ruta o nombre del dataset", "wizard.dataset.name.hint": "Nombre HuggingFace (org/dataset), ruta de archivo local (./data/train.jsonl) o directorio (./documents/).", - "wizard.dataset.autodetect.title": "Detección automática de la forma del dataset", - "wizard.dataset.autodetect.body": "ForgeLM detecta automáticamente la estructura de las filas de su dataset. Las filas con { \"messages\": [...] } se tratan como chat multi-turno. Las filas con { \"prompt\": ..., \"response\": ... } se tratan como pares instrucción → respuesta. No tiene que declarar nada — el trainer lo descubre.", + "wizard.dataset.shape.sft.title": "Detección automática de la forma del dataset", + "wizard.dataset.shape.sft.body": "ForgeLM detecta automáticamente la estructura de las filas de su dataset. Las filas con { \"messages\": [...] } se tratan como chat multi-turno. Las filas con { \"prompt\": ..., \"response\": ... } se tratan como pares instrucción → respuesta. No tiene que declarar nada — el trainer lo descubre.", + "wizard.dataset.shape.preference.title": "Filas de preferencia requeridas.", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO necesitan triples de preferencia en cada fila: { \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }. El trainer aprende a preferir chosen sobre rejected. Convierta sus datos primero si no están ya en esta forma — el runtime fallará si no.", + "wizard.dataset.shape.kto.title": "Filas de feedback binario requeridas.", + "wizard.dataset.shape.kto.body": "KTO necesita completions individuales con etiqueta pulgar-arriba / pulgar-abajo: { \"prompt\": ..., \"completion\": ..., \"label\": true|false }. No se necesitan preferencias emparejadas — KTO funciona con el tipo de datos que un widget de feedback produce realmente.", + "wizard.dataset.shape.grpo.title": "Filas de rollout GRPO.", + "wizard.dataset.shape.grpo.body": "GRPO genera múltiples respuestas por prompt y las clasifica con una señal de reward. Filas: { \"prompt\": ... }. Para problemas tipo matemáticas, añada un campo gold_answer opcional — el reward regex-correctness integrado lo usa. Sin modelo de reward, el trainer recurre al shaping de formato/longitud.", + "wizard.step.trainer.title": "Elija su paradigma de alineación", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "Seis trainers, una selección. El paso de caso de uso eligió un default sensato — cámbielo aquí si necesita otro método de alineación.", + "wizard.step.trainer.tutorial": "Los trainers difieren en QUÉ optimizan. SFT aprende mapeos input → output desde ejemplos etiquetados. DPO / ORPO / SimPO comparan una respuesta elegida con una rechazada y refuerzan la diferencia. KTO funciona con feedback pulgar-arriba / pulgar-abajo individual en lugar de preferencias emparejadas. GRPO muestrea muchas respuestas por prompt y las clasifica con un reward — mejor para tareas de razonamiento. El trainer que elija determina qué forma debe tener su dataset.", + "wizard.trainer.recommended": "buen default", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "Supervisado", + "wizard.trainer.sft.desc": "Fine-tune en ejemplos etiquetados input → output. El default para chat, instrucciones, resumen y experticia de dominio BYOD.", + "wizard.trainer.sft.shape": "filas: messages[] O prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "Preferencia", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation. Aprende de pares chosen/rejected sin modelo de reward explícito. Común para alineación pulgar-arriba después de SFT.", + "wizard.trainer.dpo.shape": "filas: prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation. Como DPO pero sin modelo de referencia — combina SFT + alineación en una sola pasada.", + "wizard.trainer.orpo.shape": "filas: prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "Normalizado por longitud", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation. Reference-free, reward normalizado por longitud — evita el sesgo de verbosidad que DPO a veces muestra.", + "wizard.trainer.simpo.shape": "filas: prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "Feedback binario", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation. Funciona con etiquetas pulgar-arriba / pulgar-abajo individuales — perfecto cuando tiene widgets de feedback pero no preferencias emparejadas.", + "wizard.trainer.kto.shape": "filas: prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "Razonamiento", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation. Muestrea múltiples respuestas por prompt y las clasifica con una señal de reward. Mejor para matemáticas, código y razonamiento paso a paso.", + "wizard.trainer.grpo.shape": "filas: prompt (+ gold_answer opcional)", "wizard.step.training.title": "Parámetros de entrenamiento", "wizard.step.training.name": "Entrenamiento", "wizard.step.training.desc": "Los valores por defecto funcionan en el 90 % de los casos. Ajuste solo si sabe por qué.", @@ -2243,10 +3142,64 @@ "wizard.training.batch.label": "Tamaño de batch (por dispositivo)", "wizard.training.batch.hint": "Batches más grandes entrenan más rápido pero necesitan más VRAM. Empiece con 2 si no está seguro.", "wizard.training.batch.detail": "Tamaño de batch = cuántos ejemplos procesa la GPU a la vez. Más grande = entrenamiento más rápido, mejor uso de la GPU, pero VRAM linealmente más alta. Empiece con 2 en una GPU de 12 GB con QLoRA; suba a 4-8 si tiene 24 GB+. Si choca con OOM, baje el batch antes de bajar el rango LoRA.", + "wizard.training.lora_method.label": "Variante de LoRA", + "wizard.training.lora_method.hint": "PiSSA necesita más memoria pero converge más rápido; RSLoRA estabiliza rangos altos (r > 64); DoRA descompone pesos para calidad ligeramente mejor.", + "wizard.training.lora_method.detail": "LoRA estándar es el default seguro y encaja en todos los escenarios 8B-en-12-GB. DoRA divide la actualización LoRA en magnitud + dirección — pequeño aumento de calidad a costo modesto. PiSSA inicializa adapters desde las componentes principales de los pesos base, converge más rápido en datasets pequeños. RSLoRA reescala el factor alpha para rangos altos (r=64+); elección correcta cuando el entrenamiento LoRA estándar se estanca o produce NaN.", + "wizard.training.lora_method.standard.title": "Estándar", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA. Default seguro para todo.", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "División magnitud + dirección. ~5% más calidad.", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "Adapters iniciados con SVD. Convergencia rápida en datos pequeños.", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rango-estabilizado. Use cuando r > 64 o entrenamiento inestable.", + "wizard.training.galore.label": "Usar GaLore (entrenamiento full-parámetro, sin adapters)", + "wizard.training.galore.hint": "Reemplaza LoRA con entrenamiento full-parámetro proyectado por gradiente. VRAM pico más bajo pero más lento por paso.", + "wizard.training.galore.detail": "GaLore entrena cada parámetro vía proyección de gradiente low-rank — sin adapter alguno. VRAM pico cae por debajo de QLoRA en modelos grandes porque el estado del optimizer se proyecta a un subespacio low-rank. Compute por paso es mayor (la proyección se recalcula cada pocos pasos), pero el resultado es un modelo completamente fine-tuned sin necesidad de merge LoRA. Use con 24 GB+ cuando quiera un solo modelo desplegable en lugar de base + adapter.", + "wizard.training.galore_optim.label": "Variante de optimizer GaLore", + "wizard.training.galore_optim.hint": "_8bit reduce a la mitad la VRAM optimizer-state; _layerwise reduce el pico por recomputación por capa.", + "wizard.training.galore_optim.detail": "GaLore reemplaza el optimizer estándar. Las variantes _8bit cuantizan el estado del optimizer a 8-bit (gran ahorro de VRAM, sin pérdida real de calidad). Las variantes _layerwise calculan la proyección GaLore una capa a la vez en lugar de todas a la vez — más lento pero permite entrenar modelos que de otro modo darían OOM. Default galore_adamw_8bit es la elección correcta para la mayoría de configuraciones.", + "wizard.training.galore_optim.adamw": "AdamW (precisión completa)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-bit (recomendado)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW por capa", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-bit por capa", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor por capa", + "wizard.training.trainer_section.title": "Hiperparámetros {trainer}", + "wizard.training.trainer_section.body": "Estos ajustes son específicos al trainer seleccionado. Los defaults son sensatos — ajuste solo si entiende qué hacen y tiene datos para justificar el cambio.", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "Temperatura que controla la desviación KL del modelo de referencia. 0.1 es el default canónico.", + "wizard.training.dpo_beta.detail": "DPO penaliza la policy por desviarse de un modelo de referencia. Beta escala esta penalización — beta bajo permite que el modelo cambie más rápido (y arriesga sobreajuste a preferencias); beta alto lo mantiene cerca de la referencia. El paper DPO recomienda 0.1 como punto de partida. Ajuste 0.05–0.2 solo después de medir el efecto en un set de preferencias held-out.", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Peso odds-ratio. 0.1 default schema; valores más altos penalizan outputs rechazados más agresivamente.", + "wizard.training.orpo_beta.detail": "ORPO reemplaza el modelo de referencia DPO con un término odds-ratio en la loss SFT. Beta controla qué tan fuerte se penaliza la likelihood de la respuesta rechazada relativa a la elegida. 0.1 es el default publicado; suba a 0.2–0.3 solo si observa que el modelo aún prefiere respuestas rejected-style.", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Término de margen que impone diferencia mínima de reward entre chosen y rejected. 0.5 default publicado.", + "wizard.training.simpo_gamma.detail": "Gamma establece un margen mínimo entre las log-probabilidades de chosen y rejected. Gamma más grande = separación más agresiva, pero la loss puede estancarse si ningún par de preferencias supera el margen. Quédese en 0.5 a menos que haya medido distribuciones de gap chosen/rejected.", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "Factor de escala de reward. Multiplica log-probs antes de la comparación de margen. 2.0 default publicado.", + "wizard.training.simpo_beta.detail": "Beta escala la señal de reward por token antes de aplicar el margen gamma. Beta alto amplifica la diferencia entre secuencias chosen y rejected (aprendizaje de preferencias más nítido) pero también puede amplificar ruido. El paper SimPO fija beta = 2.0 con gamma = 0.5; ajuste como par, no independientemente.", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "Peso de divergencia KL contra el modelo de referencia. 0.1 default refleja DPO.", + "wizard.training.kto_beta.detail": "KTO está más cerca en espíritu de RLHF que DPO — moldea la loss con la asimetría de aversión a pérdida de prospect theory, escalada por beta. Default 0.1 está calibrado para ratios típicos de feedback pulgar-arriba / pulgar-abajo. Baje si su dataset está fuertemente sesgado hacia una etiqueta.", + "wizard.training.grpo_num_generations.label": "Generaciones por prompt", + "wizard.training.grpo_num_generations.hint": "Número de respuestas que GRPO muestrea por prompt antes de clasificar. 4 balanceado; 8 más calidad pero más lento.", + "wizard.training.grpo_num_generations.detail": "GRPO clasifica N respuestas muestreadas entre sí para construir el gradient policy. Más muestras = gradient mejor condicionado y convergencia más rápida por paso, pero cada paso cuesta N forward passes. 4 es el sweet spot para modelo 8B en GPU única; suba a 8 para matemáticas/razonamiento donde la calidad del ranking importa.", + "wizard.training.grpo_max_completion_length.label": "Longitud máxima de completion", + "wizard.training.grpo_max_completion_length.hint": "Tope de tokens por respuesta generada. 512 cubre la mayoría de cadenas de razonamiento; suba para matemáticas largas.", + "wizard.training.grpo_max_completion_length.detail": "GRPO trunca los rollouts generados en este conteo de tokens. Topes más cortos = pasos más rápidos pero pueden truncar el razonamiento a mitad; topes más largos exploran más pero desperdician compute en padding. 512 default balanceado — para matemáticas estilo GSM8K puede bajar a 384, para problemas nivel olimpiada empuje a 1024+.", + "wizard.training.grpo_reward_model.label": "Modelo de reward (opcional)", + "wizard.training.grpo_reward_model.hint": "ID HuggingFace Hub de un modelo de reward para puntuar generaciones. Deje en blanco para usar el reward formato/longitud-shaping integrado.", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "Cuando proporciona un ID de modelo de reward, GRPO lo usa para puntuar cada rollout — esa es la receta estándar para alineación RLHF-style. Déjelo en blanco y ForgeLM recurre al combined_format_length_reward integrado: una señal formato/longitud gradient-rica que no necesita modelo extra. Para problemas matemáticos, añadir también un campo gold_answer al dataset dispara un reward regex-correctness encima.", "wizard.step.compliance.title": "Evaluación & EU AI Act", "wizard.step.compliance.name": "Evaluación & cumplimiento", "wizard.step.compliance.desc": "Puertas de calidad y artefactos regulatorios.", "wizard.step.compliance.tutorial": "ForgeLM verifica el modelo fine-tuned ANTES de que lo entregue. Auto-revert deshace el fine-tune si es peor que el modelo base. Llama Guard escanea las salidas en busca de contenido dañino. El nivel de riesgo EU AI Act que elija determina qué artefactos de cumplimiento se autogeneran y si la aprobación humana es REQUERIDA antes del despliegue.", + "wizard.step.operations.title": "Operaciones y ajustes avanzados", + "wizard.step.operations.name": "Operaciones", + "wizard.step.operations.desc": "Notificaciones, monitoreo post-mercado, generación de datos sintéticos y horizontes de retención. Todos opcionales — salte los que no necesite.", + "wizard.step.operations.tutorial": "Estos cuatro bloques cubren todo lo que ForgeLM ofrece más allá del flujo central de entrenamiento + compliance. Webhook envía alerta Slack/Teams/Discord en inicio / éxito / fallo. El monitoreo post-mercado (Artículos 12+17) conecta detección de drift a Prometheus / Datadog tras despliegue. La generación sintética permite a un modelo teacher más fuerte producir ejemplos adicionales. Los horizontes de retención fijan TTLs conformes a RGPD para audit log y artefactos efímeros. Ninguno es obligatorio — déjelos cerrados si su run no los necesita.", "wizard.risk.label": "Clasificación de riesgo EU AI Act", "wizard.risk.hint": "Elija el nivel en el que cae su despliegue. High-risk y unacceptable activan obligaciones obligatorias de auditoría + aprobación humana.", "wizard.risk.unknown": "Unknown — placeholder de pre-clasificación", @@ -2281,6 +3234,114 @@ "wizard.compliance.intended_purpose.label": "Propósito previsto", "wizard.compliance.intended_purpose.hint": "Annex IV §1: objetivo declarado del sistema en lenguaje claro.", "wizard.compliance.intended_purpose.placeholder":"Triar tickets de soporte al cliente vía chat", + "wizard.risk_assessment.title": "Evaluación de riesgos (EU AI Act Artículo 9)", + "wizard.risk_assessment.hint": "Obligatorio para sistemas high-risk. Opcional pero recomendado en otros casos.", + "wizard.risk_assessment.intended_use.label": "Uso previsto", + "wizard.risk_assessment.intended_use.hint": "Artículo 9(2)(a): describa el propósito previsto del sistema en lenguaje claro.", + "wizard.risk_assessment.intended_use.placeholder": "Un asistente de soporte al cliente que tría tickets y borra respuestas.", + "wizard.risk_assessment.intended_use.detail": "Esta es la declaración canónica de para qué se construyó su sistema — ancla el resto de la evaluación de riesgos. Sea específico: \"triar tickets de soporte\" es mejor que \"ayudar a usuarios\". Revisores y reguladores lo leen primero para decidir si las mitigaciones aguas abajo son apropiadas.", + "wizard.risk_assessment.foreseeable_misuse.label": "Mal uso previsible (uno por línea)", + "wizard.risk_assessment.foreseeable_misuse.hint": "Artículo 9(2)(b): escenarios donde el sistema podría ser mal usado.", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "Generar emails de phishing\nAtaques de ingeniería social\nEvadir guardarriles de seguridad vía prompt injection", + "wizard.risk_assessment.foreseeable_misuse.detail": "Liste los escenarios de mal uso que pueda anticipar razonablemente — no cada ataque teórico, sino los relevantes para su contexto de despliegue. Ejercicio estándar: imagine un atacante hostil pero perezoso. ¿Cuál es la forma más fácil de abusar de esto? Cada línea se convierte en un elemento de lista YAML.", + "wizard.risk_assessment.mitigation_measures.label": "Medidas de mitigación (una por línea)", + "wizard.risk_assessment.mitigation_measures.hint": "Artículo 9(2)(c): controles que pone en marcha contra los riesgos de mal uso arriba.", + "wizard.risk_assessment.mitigation_measures.placeholder": "Filtro de seguridad Llama Guard en todas las salidas\nRate limiting por usuario\nAprobación humana para respuestas de riesgo alto", + "wizard.risk_assessment.mitigation_measures.detail": "Para cada mal uso previsible, nombre una mitigación concreta. Los revisores comparan esta lista con la de mal uso — las brechas son una bandera roja. Cada línea se convierte en un elemento de lista YAML. Los portales de safety eval y aprobación humana integrados de ForgeLM son buenas entradas de primera línea aquí.", + "wizard.risk_assessment.vulnerable_groups.label": "Impacto en grupos vulnerables considerado", + "wizard.risk_assessment.vulnerable_groups.hint": "Artículo 9(2)(b): confirme si ha considerado efectos en niños, minorías, etc.", + "wizard.risk_assessment.vulnerable_groups.detail": "El Artículo 9 menciona explícitamente grupos vulnerables (niños, ancianos, minorías, personas con discapacidades). Marque solo después de pensar realmente cómo su sistema podría afectarlos — una falsa certificación es peor que ninguna.", + "wizard.eval_gates.title": "Portales de calidad (avanzado)", + "wizard.eval_gates.hint": "Benchmark + LLM-as-Judge opcionales sobre los portales loss/safety básicos.", + "wizard.benchmark.enabled.label": "Ejecutar benchmark lm-evaluation-harness", + "wizard.benchmark.enabled.hint": "Puntúa el modelo fine-tuned en tareas académicas tras entrenamiento.", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness es el estándar de facto para benchmarking LLM. Activar ejecuta las tareas listadas contra su modelo fine-tuned.", + "wizard.benchmark.tasks.label": "Tareas (una por línea)", + "wizard.benchmark.tasks.hint": "Comunes: arc_easy, arc_challenge, hellaswag, mmlu, truthfulqa, winogrande, gsm8k.", + "wizard.benchmark.min_score.label": "Precisión media mínima", + "wizard.benchmark.min_score.hint": "0.0–1.0. Por debajo, auto_revert deshace el fine-tune. Vacío = sin portal umbral.", + "wizard.benchmark.min_score.placeholder": "ej. 0.55", + "wizard.judge.enabled.label": "Puntuación LLM-as-Judge", + "wizard.judge.enabled.hint": "Puntúe generaciones con un modelo más fuerte (ej. GPT-4o).", + "wizard.judge.enabled.detail": "LLM-as-Judge pide a modelo más fuerte calificar salidas en escala 1–10. Mejor que benchmarks para calidad chat/instrucciones.", + "wizard.judge.model.label": "Modelo juez", + "wizard.judge.model.hint": "Nombre modelo API (gpt-4o, claude-opus-4-7) o ruta local. Juez debe ser sustancialmente más fuerte.", + "wizard.judge.api_key_env.label": "Variable env clave API", + "wizard.judge.api_key_env.hint": "Nombre variable env con la clave API. Vacío = modelo juez local.", + "wizard.judge.min_score.label": "Puntuación juez media mínima", + "wizard.judge.min_score.hint": "Escala 1–10. Por debajo dispara auto_revert. 5.0 \"promedio\"; 7.0 \"bueno\".", + "wizard.governance.title": "Gobernanza de datos (Artículo 10)", + "wizard.governance.hint": "Documente procedencia + sesgos de datos. Obligatorio para despliegues high-risk.", + "wizard.governance.collection_method.label": "Método de recolección", + "wizard.governance.collection_method.hint": "Artículo 10(2)(b): cómo se obtuvieron datos de entrenamiento.", + "wizard.governance.collection_method.placeholder": "Archivos web públicos 2023–2025 raspados, deduplicados por hash URL.", + "wizard.governance.collection_method.detail": "Diga a revisores CÓMO sus datos terminaron en trainer. Respuestas vagas (\"internet\") fallan auditorías Art. 10.", + "wizard.governance.annotation_process.label": "Proceso de anotación", + "wizard.governance.annotation_process.hint": "Artículo 10(2)(b): metodología de etiquetado — quién con qué directrices.", + "wizard.governance.annotation_process.placeholder": "Dos anotadores entrenados etiquetaron cada ejemplo; conflictos resueltos por revisión senior.", + "wizard.governance.annotation_process.detail": "Si datos etiquetados, describa cómo. Acuerdo inter-anotadores, versión directrices, resolución conflictos.", + "wizard.governance.known_biases.label": "Sesgos conocidos", + "wizard.governance.known_biases.hint": "Artículo 10(2)(f): sesgos que el operador conoce en los datos.", + "wizard.governance.known_biases.placeholder": "Predominantemente inglés; grupos edad <18 y >65 subrepresentados.", + "wizard.governance.known_biases.detail": "Ningún dataset es perfectamente balanceado. Nombrar desequilibrios por adelantado es la respuesta honesta.", + "wizard.governance.personal_data.label": "Datos de entrenamiento contienen datos personales", + "wizard.governance.personal_data.hint": "Artículo 10(5) + RGPD: marque si ejemplos incluyen individuos identificables.", + "wizard.governance.dpia_completed.label": "EIPD completada (RGPD Art. 35)", + "wizard.governance.dpia_completed.hint": "Evaluación de Impacto sobre Protección de Datos terminada para este dataset.", + "wizard.webhook.title": "Notificaciones (webhook)", + "wizard.webhook.hint": "Envíe alertas Slack/Teams/Discord en eventos de entrenamiento.", + "wizard.webhook.url.label": "URL webhook o variable env", + "wizard.webhook.url.hint": "URL HTTPS directa o variable env que la lleva (recomendado para secretos).", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... O env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "Puede pegar URL directamente, o usar prefijo env: para apuntar a una variable de entorno.", + "wizard.webhook.enabled.label": "Enviar notificaciones webhook", + "wizard.webhook.enabled.hint": "Cuando está activo, ForgeLM publica un payload webhook en los eventos seleccionados. Apagado = la YAML no contiene ningún bloque webhook.", + "wizard.webhook.enabled.detail": "Las notificaciones conectan eventos de entrenamiento a su chat de equipo (Slack / Teams / Discord) o cualquier endpoint HTTPS que acepte JSON. Las entradas del audit log registran cada envío — incluyendo fallos — como un rastro independiente. Déjelo apagado si no quiere ninguna config de webhook en la YAML.", + "wizard.webhook.notify_start.label": "Notificar al inicio", + "wizard.webhook.notify_success.label": "Notificar en éxito", + "wizard.webhook.notify_failure.label": "Notificar en fallo", + "wizard.monitoring.title": "Monitoreo post-mercado (Art. 12+17)", + "wizard.monitoring.hint": "Conecte métricas entrenamiento a Prometheus/Datadog/webhook custom para observabilidad continua.", + "wizard.monitoring.enabled.label": "Habilitar hooks de monitoreo", + "wizard.monitoring.enabled.hint": "Obligatorio para sistemas high-risk bajo EU AI Act Artículos 12 + 17.", + "wizard.monitoring.enabled.detail": "Una vez desplegado un sistema high-risk, EU AI Act requiere observación continua — detección de drift, reporte de errores.", + "wizard.monitoring.endpoint.label": "Endpoint monitoreo", + "wizard.monitoring.endpoint.hint": "URL de su Prometheus push gateway, Datadog o receptor webhook custom.", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics O env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "Formato métricas", + "wizard.monitoring.metrics_export.none": "Ninguno", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "Webhook custom", + "wizard.monitoring.alert_on_drift.label": "Alertar en drift", + "wizard.monitoring.alert_on_drift.hint": "Emite alerta webhook cuando detector drift marca regresión.", + "wizard.synthetic.title": "Generación datos sintéticos (avanzado)", + "wizard.synthetic.hint": "Genere datos entrenamiento teacher → student usando modelo más fuerte.", + "wizard.synthetic.enabled.label": "Generar datos sintéticos", + "wizard.synthetic.enabled.hint": "Ejecute modelo teacher sobre seed prompts para producir ejemplos.", + "wizard.synthetic.enabled.detail": "Generación sintética permite a modelo más fuerte (ej. GPT-4) producir ejemplos para target fine-tune más pequeño.", + "wizard.synthetic.teacher_model.label": "Modelo teacher", + "wizard.synthetic.teacher_model.hint": "Nombre modelo API (gpt-4o, claude-opus-4-7) o ruta local.", + "wizard.synthetic.seed_file.label": "Archivo seed prompts", + "wizard.synthetic.seed_file.hint": "JSONL o texto plano seed prompts. Teacher genera respuesta para cada uno.", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Variable env clave API teacher", + "wizard.synthetic.api_key_env.hint": "Nombre variable env con clave API teacher. Vacío = modelo teacher local.", + "wizard.retention.title": "Horizontes de retención (RGPD Art. 5+17)", + "wizard.retention.hint": "Cuánto tiempo ForgeLM guarda audit logs, modelos staging, artefactos efímeros.", + "wizard.retention.audit_log.label": "Retención audit log (días)", + "wizard.retention.audit_log.hint": "Cuánto guardar audit_log.jsonl. Default 1825 (5 años) coincide con EU AI Act.", + "wizard.retention.staging.label": "Horizonte staging (días)", + "wizard.retention.staging.hint": "Cuánto guardar modelos rechazados en final_model.staging/. Default 7.", + "wizard.retention.ephemeral.label": "Artefactos efímeros (días)", + "wizard.retention.ephemeral.hint": "Cuánto guardar checkpoints intermedios, samples eval. Default 90.", + "wizard.retention.raw_documents.label": "Documentos crudos (días)", + "wizard.retention.raw_documents.hint": "Cuánto guardar PDFs/DOCX crudos tras ingestion. Default 90.", + "wizard.retention.enforce.label": "Forzar eliminación automática", + "wizard.retention.enforce.hint": "Si activo, ForgeLM auto-elimina artefactos tras su horizonte. Default off.", + "wizard.retention.enforce.log_only": "log_only — registrar violaciones en audit (default)", + "wizard.retention.enforce.warn": "warn_on_excess — log + advertencia stderr", + "wizard.retention.enforce.block": "block_on_excess — abortar trainer (CI regulada)", "wizard.step.review.title": "Revise su configuración", "wizard.step.review.name": "Revisar", "wizard.step.review.desc": "Aquí está el YAML. Cópielo, descárguelo y ejecútelo.", @@ -2290,6 +3351,25 @@ "wizard.review.reset": "Reiniciar asistente", "wizard.review.reset.confirm":"¿Reiniciar todas las respuestas del asistente y empezar de nuevo? Su YAML se perderá.", "wizard.review.run_label": "Luego ejecute:", + "wizard.error.model.required": "Eligió “Modelo HuggingFace personalizado” en el paso anterior, por lo que este campo es obligatorio. Ingrese un ID org/repo (ej. meta-llama/Llama-3.1-8B-Instruct) o regrese y elija un preset.", + "wizard.error.model.format": "Los ID de HuggingFace Hub usan el formato org/repo (ej. meta-llama/Llama-3.1-8B-Instruct). Un nombre sin barra no se resolverá al entrenar.", + "wizard.error.dataset.required": "Elija un dataset antes de continuar — sin una ruta o nombre de HuggingFace, el trainer no tiene de qué aprender.", + "wizard.error.webhook.required": "Activó las notificaciones, así que necesitamos una URL de webhook o variable de entorno. Desactive las notificaciones arriba o pegue su URL de Slack / Teams / Discord (para producción use env:SLACK_WEBHOOK_URL).", + "wizard.error.monitoring.endpoint.required": "Activó el monitoreo post-mercado, así que necesitamos un endpoint para enviar métricas. Desactive el monitoreo arriba o pegue su URL de Prometheus / Datadog / webhook custom (para secretos use env:NOMBRE_VAR).", + "wizard.error.synthetic.teacher_model.required": "Activó la generación de datos sintéticos, así que necesitamos un nombre de modelo teacher (ej. gpt-4o, claude-opus-4-7 o ruta local).", + "wizard.error.synthetic.seed_file.required": "Activó la generación de datos sintéticos, así que necesitamos un archivo de seed prompts (ej. ./data/seed_prompts.jsonl); el teacher producirá una respuesta por cada prompt.", + "wizard.error.synthetic.api_key_env.required": "Activó la generación de datos sintéticos, así que necesitamos la variable de entorno con la clave API del teacher (típicamente OPENAI_API_KEY o ANTHROPIC_API_KEY).", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1 requiere el nombre de la entidad jurídica proveedora para sistemas high-risk. Complete el campo o reduzca la clasificación de riesgo.", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1 requiere un nombre de sistema legible para despliegues high-risk. Complete el campo o reduzca la clasificación de riesgo.", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1 requiere un propósito previsto declarado para sistemas high-risk. Descríbalo en lenguaje simple o reduzca la clasificación de riesgo.", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act Artículo 9 requiere que el uso previsto se documente para sistemas high-risk. Describa qué debe hacer el sistema o reduzca la clasificación de riesgo.", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act Artículo 9 requiere escenarios de mal uso previsible para sistemas high-risk. Listelos uno por línea o reduzca la clasificación de riesgo.", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act Artículo 9 requiere medidas de mitigación para sistemas high-risk. Liste los controles implementados (uno por línea) o reduzca la clasificación de riesgo.", + "wizard.error.governance.collection_method.required": "EU AI Act Artículo 10 requiere documentación sobre la procedencia de los datos para sistemas high-risk. Describa cómo se obtuvieron los datos o reduzca la clasificación de riesgo.", + "wizard.error.governance.annotation_process.required": "EU AI Act Artículo 10 requiere documentación sobre la metodología de anotación. Describa su proceso de etiquetado (“sin anotación” es válido para SFT en texto crudo) o reduzca la clasificación de riesgo.", + "wizard.error.governance.known_biases.required": "EU AI Act Artículo 10 requiere que los sesgos conocidos del dataset se documenten para sistemas high-risk. Liste los desequilibrios que conoce o reduzca la clasificación de riesgo.", + "wizard.error.benchmark.tasks.required": "Activó el gate de benchmark, así que necesitamos al menos una tarea. Agregue nombres de lm-evaluation-harness como arc_easy, hellaswag, mmlu (uno por línea) o desactive el benchmark arriba.", + "wizard.error.judge.api_key_env.required": "Activó LLM-as-Judge, así que necesitamos la variable de entorno con la clave API del juez (típicamente OPENAI_API_KEY o ANTHROPIC_API_KEY).", "quickstart.spotlight.eyebrow": "Configurador guiado", "quickstart.spotlight.title": "¿Prefiere no escribir el YAML a mano?", "quickstart.spotlight.lead": "El asistente integrado en el navegador hace siete preguntas, escribe un YAML válido y le deja ejecutarlo con un solo comando. Sin instalación, su progreso queda guardado localmente.", @@ -2298,8 +3378,8 @@ "quickstart.spotlight.meta1": "≈ 3 minutos", "quickstart.spotlight.meta2": "6 idiomas", "quickstart.spotlight.meta3": "Sin instalación", - "quickstart.spotlight.stage1.label": "Responda 7 preguntas", - "quickstart.spotlight.stage1.snippet": "caso de uso · modelo · datos · LoRA · riesgo", + "quickstart.spotlight.stage1.label": "Responda 9 preguntas", + "quickstart.spotlight.stage1.snippet": "caso de uso · trainer · modelo · datos · riesgo", "quickstart.spotlight.stage2.label": "Obtenga un YAML válido", "quickstart.spotlight.stage3.label": "Ejecútelo", "quickstart.spotlight.manual_intro": "O recorra los cinco pasos manualmente ↓" @@ -2397,8 +3477,43 @@ "wizard.dataset.pdf.desc": "ForgeLM 将在训练前 ingest 它们到 JSONL(PII + secrets 已屏蔽)。", "wizard.dataset.name.label": "数据集路径或名称", "wizard.dataset.name.hint": "HuggingFace 名称(org/dataset)、本地文件路径(./data/train.jsonl)或目录(./documents/)。", - "wizard.dataset.autodetect.title": "自动检测数据集形态", - "wizard.dataset.autodetect.body": "ForgeLM 自动检测数据集行的结构。包含 { \"messages\": [...] } 的行被视为多轮聊天。包含 { \"prompt\": ..., \"response\": ... } 的行被视为指令 → 响应对。你不需要声明任何东西 — trainer 会自己识别。", + "wizard.dataset.shape.sft.title": "自动检测数据集形态", + "wizard.dataset.shape.sft.body": "ForgeLM 自动检测数据集行的结构。包含 { \"messages\": [...] } 的行被视为多轮聊天。包含 { \"prompt\": ..., \"response\": ... } 的行被视为指令 → 响应对。你不需要声明任何东西 — trainer 会自己识别。", + "wizard.dataset.shape.preference.title": "需要偏好行。", + "wizard.dataset.shape.preference.body": "DPO / ORPO / SimPO 每行需要偏好三元组:{ \"prompt\": ..., \"chosen\": ..., \"rejected\": ... }。Trainer 学习偏好 chosen 而非 rejected。如果数据还不是这种形状,请先转换 — 否则运行时会报错。", + "wizard.dataset.shape.kto.title": "需要二元反馈行。", + "wizard.dataset.shape.kto.body": "KTO 需要带点赞 / 点踩标签的单个 completion:{ \"prompt\": ..., \"completion\": ..., \"label\": true|false }。无需配对偏好 — KTO 适用于反馈控件实际产生的数据。", + "wizard.dataset.shape.grpo.title": "GRPO rollout 行。", + "wizard.dataset.shape.grpo.body": "GRPO 为每个 prompt 生成多个回应,并用奖励信号对它们排序。行:{ \"prompt\": ... }。对数学类问题,添加可选的 gold_answer 字段 — 内置的正则正确性奖励会使用它。没有奖励模型时,trainer 回退到格式/长度 shaping。", + "wizard.step.trainer.title": "选择对齐范式", + "wizard.step.trainer.name": "Trainer", + "wizard.step.trainer.desc": "六个 trainer,一个选择。用例步骤选择了合理的默认值 — 如果需要其他对齐方法,在这里更改。", + "wizard.step.trainer.tutorial": "Trainer 的差异在于优化什么。SFT 从带标签的样本中学习 input → output 映射。DPO / ORPO / SimPO 比较选中的回应和被拒绝的回应,强化差异。KTO 使用单个点赞 / 点踩反馈而非配对偏好。GRPO 为每个 prompt 采样多个回应并用奖励对它们排序 — 最适合推理任务。选择的 trainer 决定数据集必须采用的形状。", + "wizard.trainer.recommended": "良好默认", + "wizard.trainer.sft.title": "SFT", + "wizard.trainer.sft.badge": "监督", + "wizard.trainer.sft.desc": "在带标签的 input → output 样本上微调。聊天、指令、摘要和 BYOD 领域专长的默认选项。", + "wizard.trainer.sft.shape": "行:messages[] 或 prompt + response", + "wizard.trainer.dpo.title": "DPO", + "wizard.trainer.dpo.badge": "偏好", + "wizard.trainer.dpo.desc": "Direct Preference Optimisation。从 chosen/rejected 对中学习,无需显式奖励模型。SFT 后点赞对齐的常用选择。", + "wizard.trainer.dpo.shape": "行:prompt + chosen + rejected", + "wizard.trainer.orpo.title": "ORPO", + "wizard.trainer.orpo.badge": "Reference-free", + "wizard.trainer.orpo.desc": "Odds-Ratio Preference Optimisation。类似 DPO 但无参考模型 — 一次过程中合并 SFT + alignment。", + "wizard.trainer.orpo.shape": "行:prompt + chosen + rejected", + "wizard.trainer.simpo.title": "SimPO", + "wizard.trainer.simpo.badge": "长度归一化", + "wizard.trainer.simpo.desc": "Simple Preference Optimisation。Reference-free,长度归一化奖励 — 避免 DPO 有时表现出的冗长偏差。", + "wizard.trainer.simpo.shape": "行:prompt + chosen + rejected", + "wizard.trainer.kto.title": "KTO", + "wizard.trainer.kto.badge": "二元反馈", + "wizard.trainer.kto.desc": "Kahneman-Tversky Optimisation。适用于单个点赞 / 点踩标签 — 当你有反馈控件但没有配对偏好时完美。", + "wizard.trainer.kto.shape": "行:prompt + completion + label", + "wizard.trainer.grpo.title": "GRPO", + "wizard.trainer.grpo.badge": "推理", + "wizard.trainer.grpo.desc": "Group Relative Policy Optimisation。为每个 prompt 采样多个回应并用奖励信号对它们排序。最适合数学、代码和分步推理。", + "wizard.trainer.grpo.shape": "行:prompt (+ 可选 gold_answer)", "wizard.step.training.title": "训练参数", "wizard.step.training.name": "训练", "wizard.step.training.desc": "默认值在 90% 的情况下能工作。仅在你知道为什么时调整。", @@ -2421,10 +3536,64 @@ "wizard.training.batch.label": "批量大小(每设备)", "wizard.training.batch.hint": "更大的批量训练更快但需要更多 VRAM。如果不确定,从 2 开始。", "wizard.training.batch.detail": "批量大小 = GPU 一次处理的样本数。越大 = 训练越快、GPU 利用率越好,但 VRAM 线性增加。在 12 GB GPU 上用 QLoRA 从 2 开始;有 24 GB+ 提到 4-8。如果遇到 OOM,先降批量再降 LoRA rank。", + "wizard.training.lora_method.label": "LoRA 变体", + "wizard.training.lora_method.hint": "PiSSA 需要更多内存但收敛更快;RSLoRA 稳定高 rank(r > 64);DoRA 分解权重以获得稍好的质量。", + "wizard.training.lora_method.detail": "标准 LoRA 是安全默认值,适合所有 8B-在-12-GB 场景。DoRA 将 LoRA 更新拆分为大小 + 方向 — 以适度成本带来小幅质量提升。PiSSA 从基础权重的主成分初始化 adapter,在小数据集上收敛更快。RSLoRA 为高 rank(r=64+)重新缩放 alpha 因子;标准 LoRA 训练停滞或产生 NaN 时是正确选择。", + "wizard.training.lora_method.standard.title": "标准", + "wizard.training.lora_method.standard.desc": "Vanilla LoRA。一切的安全默认。", + "wizard.training.lora_method.dora.title": "DoRA", + "wizard.training.lora_method.dora.desc": "大小 + 方向分离。约 5% 质量提升。", + "wizard.training.lora_method.pissa.title": "PiSSA", + "wizard.training.lora_method.pissa.desc": "SVD 初始化的 adapter。小数据收敛更快。", + "wizard.training.lora_method.rslora.title": "RSLoRA", + "wizard.training.lora_method.rslora.desc": "Rank 稳定。当 r > 64 或训练不稳时使用。", + "wizard.training.galore.label": "使用 GaLore(全参数训练,无 adapter)", + "wizard.training.galore.hint": "用梯度投影全参数训练替换 LoRA。峰值 VRAM 更低但每步更慢。", + "wizard.training.galore.detail": "GaLore 通过 low-rank 梯度投影训练每个参数 — 完全没有 adapter。在大模型上峰值 VRAM 低于 QLoRA,因为 optimizer state 被投影到 low-rank 子空间。每步 compute 更高(投影每隔几步重新计算),但结果是完全 fine-tune 的模型,无需 LoRA merge。当你有 24 GB+ 并希望单个可部署模型而非 base + adapter 时使用。", + "wizard.training.galore_optim.label": "GaLore optimizer 变体", + "wizard.training.galore_optim.hint": "_8bit 将 optimizer-state VRAM 减半;_layerwise 通过逐层重算降低峰值。", + "wizard.training.galore_optim.detail": "GaLore 替换标准 optimizer。_8bit 变体将 optimizer state 量化为 8-bit(VRAM 大量节省,无实际质量损失)。_layerwise 变体一次计算一层而非所有层的 GaLore 投影 — 更慢但能训练否则会 OOM 的模型。默认 galore_adamw_8bit 适合大多数配置。", + "wizard.training.galore_optim.adamw": "AdamW(全精度)", + "wizard.training.galore_optim.adamw_8bit": "AdamW 8-bit(推荐)", + "wizard.training.galore_optim.adafactor": "Adafactor", + "wizard.training.galore_optim.adamw_layerwise": "AdamW 逐层", + "wizard.training.galore_optim.adamw_8bit_layerwise": "AdamW 8-bit 逐层", + "wizard.training.galore_optim.adafactor_layerwise": "Adafactor 逐层", + "wizard.training.trainer_section.title": "{trainer} 超参数", + "wizard.training.trainer_section.body": "这些设置特定于所选 trainer。默认值合理 — 仅在你了解它们的作用并有数据支持改变时再调整。", + "wizard.training.dpo_beta.label": "DPO beta (β)", + "wizard.training.dpo_beta.hint": "控制相对参考模型的 KL 偏差的温度。0.1 是规范默认值。", + "wizard.training.dpo_beta.detail": "DPO 惩罚 policy 偏离参考模型。Beta 缩放这个惩罚 — beta 低让模型变化更快(过拟合偏好的风险增加);beta 高让模型贴近参考。DPO 论文推荐 0.1 作为起点。仅在 held-out 偏好集上测量效果后再调整 0.05–0.2。", + "wizard.training.orpo_beta.label": "ORPO beta (β)", + "wizard.training.orpo_beta.hint": "Odds-ratio 权重。0.1 schema 默认值;更高值更激进地惩罚被拒绝的输出。", + "wizard.training.orpo_beta.detail": "ORPO 用 SFT 损失中的 odds-ratio 项替换 DPO 的参考模型。Beta 控制相对于选中回应,被拒绝回应的 likelihood 被惩罚的强度。0.1 是发布的默认值;仅当观察到模型仍偏好 rejected-style 回应时再升至 0.2–0.3。", + "wizard.training.simpo_gamma.label": "SimPO gamma (γ)", + "wizard.training.simpo_gamma.hint": "Margin 项,在 chosen 和 rejected 间强制最小奖励差。0.5 发布默认值。", + "wizard.training.simpo_gamma.detail": "Gamma 设置 chosen 和 rejected 对数概率之间的最小 margin。更大的 gamma = 更激进的分离,但如果没有偏好对超过 margin,损失可能停滞。除非测量了 chosen/rejected gap 分布,否则保持 0.5。", + "wizard.training.simpo_beta.label": "SimPO beta (β)", + "wizard.training.simpo_beta.hint": "奖励缩放因子。在 margin 比较前乘 log-probs。2.0 发布默认值。", + "wizard.training.simpo_beta.detail": "Beta 在应用 gamma margin 之前缩放每 token 奖励信号。Beta 高放大 chosen 和 rejected 序列之间的差异(更锐利的偏好学习)但也可能放大噪声。SimPO 论文设 beta = 2.0,gamma = 0.5;成对调整,不要独立调。", + "wizard.training.kto_beta.label": "KTO beta (β)", + "wizard.training.kto_beta.hint": "相对参考模型的 KL 散度权重。0.1 默认值反映 DPO。", + "wizard.training.kto_beta.detail": "KTO 在精神上更接近 RLHF 而非 DPO — 用 prospect theory 的损失厌恶不对称性塑造损失,由 beta 缩放。默认 0.1 针对典型的点赞 / 点踩反馈比例校准。如果数据集严重偏向某个标签,降低它。", + "wizard.training.grpo_num_generations.label": "每 prompt 生成数", + "wizard.training.grpo_num_generations.hint": "GRPO 在排序前每个 prompt 采样的回应数量。4 平衡;8 质量更高但更慢。", + "wizard.training.grpo_num_generations.detail": "GRPO 将 N 个采样回应相互排序以构建 policy gradient。更多样本 = 更好条件化的 gradient 和每步更快收敛,但每步花费 N 次 forward pass。8B 模型单 GPU 上 4 是 sweet spot;数学/推理任务排序质量重要时升至 8。", + "wizard.training.grpo_max_completion_length.label": "最大 completion 长度", + "wizard.training.grpo_max_completion_length.hint": "每个生成回应的 token 上限。512 覆盖大多数推理链;长数学问题增加。", + "wizard.training.grpo_max_completion_length.detail": "GRPO 在此 token 数处截断生成的 rollout。更短的上限 = 更快的步骤,但可能在推理中途截断;更长的上限探索更多但浪费 compute 在 padding 上。512 平衡默认 — GSM8K 风格数学可降至 384,olympiad 级问题推到 1024+。", + "wizard.training.grpo_reward_model.label": "奖励模型(可选)", + "wizard.training.grpo_reward_model.hint": "用于评分生成的奖励模型 HuggingFace Hub ID。留空使用内置格式/长度 shaping 奖励。", + "wizard.training.grpo_reward_model.placeholder": "OpenAssistant/reward-model-deberta-v3-large-v2", + "wizard.training.grpo_reward_model.detail": "当你提供奖励模型 ID 时,GRPO 使用它评分每个 rollout — 这是 RLHF-style 对齐的标准做法。留空,ForgeLM 回退到内置 combined_format_length_reward:一个无需额外模型的、gradient 丰富的格式/长度信号。对于数学问题,在数据集中再添加一个 gold_answer 字段会额外触发一个正则正确性奖励。", "wizard.step.compliance.title": "评估 & EU AI Act", "wizard.step.compliance.name": "评估 & 合规", "wizard.step.compliance.desc": "质量 gate 和监管 artefact。", "wizard.step.compliance.tutorial": "ForgeLM 在你交付之前检查微调后的模型。Auto-revert 在微调比基础模型差时撤销微调。Llama Guard 扫描输出中的有害内容。你选择的 EU AI Act 风险等级决定哪些合规 artefact 自动生成,以及在部署之前是否需要人工批准。", + "wizard.step.operations.title": "运维与高级设置", + "wizard.step.operations.name": "运维", + "wizard.step.operations.desc": "通知、上市后监控、合成数据生成和保留期限。全部可选 — 不需要的可以跳过。", + "wizard.step.operations.tutorial": "这四个区块涵盖了 ForgeLM 在核心训练 + 合规流程之外提供的一切。Webhook 在训练开始 / 成功 / 失败时发送 Slack/Teams/Discord 警报。上市后监控(第 12+17 条)在部署后将漂移检测连接到 Prometheus / Datadog。合成数据生成让更强的 teacher 模型产生额外训练样本。保留期限为审计日志和临时 artefact 设置符合 GDPR 的 TTL。没有任何项是强制的 — 你的训练不需要的话保持折叠状态即可。", "wizard.risk.label": "EU AI Act 风险分类", "wizard.risk.hint": "选择你的部署所属的等级。High-risk 和 unacceptable 触发强制审计 + 人工批准义务。", "wizard.risk.unknown": "Unknown — 预分类占位符", @@ -2459,6 +3628,114 @@ "wizard.compliance.intended_purpose.label": "预期用途", "wizard.compliance.intended_purpose.hint": "Annex IV §1:用平实语言陈述的系统目标。", "wizard.compliance.intended_purpose.placeholder":"通过聊天分类客户支持工单", + "wizard.risk_assessment.title": "风险评估(EU AI Act 第 9 条)", + "wizard.risk_assessment.hint": "High-risk 系统强制要求。其他情况下可选但推荐。", + "wizard.risk_assessment.intended_use.label": "预期用途", + "wizard.risk_assessment.intended_use.hint": "第 9(2)(a) 条:用平实语言描述系统的预期目的。", + "wizard.risk_assessment.intended_use.placeholder": "一个客户支持助手,分类工单并起草回复。", + "wizard.risk_assessment.intended_use.detail": "这是关于你的系统为何而建的规范声明 — 它锚定风险评估的其余部分。要具体:\"分类支持工单\"比\"帮助用户\"更好。审查者和监管者先读这个,然后决定下游缓解措施是否合适。", + "wizard.risk_assessment.foreseeable_misuse.label": "可预见的滥用(每行一个)", + "wizard.risk_assessment.foreseeable_misuse.hint": "第 9(2)(b) 条:系统可能被滥用的场景。", + "wizard.risk_assessment.foreseeable_misuse.placeholder": "生成钓鱼邮件\n社会工程攻击\n通过 prompt injection 绕过安全护栏", + "wizard.risk_assessment.foreseeable_misuse.detail": "列出你能合理预期的滥用场景 — 不是每个理论攻击,而是与你的部署场景相关的。标准练习:想象一个敌对但懒惰的攻击者。滥用这个的最容易方式是什么?textarea 的每一行成为 YAML 列表中的一项。", + "wizard.risk_assessment.mitigation_measures.label": "缓解措施(每行一个)", + "wizard.risk_assessment.mitigation_measures.hint": "第 9(2)(c) 条:你针对上述滥用风险设置的控制。", + "wizard.risk_assessment.mitigation_measures.placeholder": "所有输出上的 Llama Guard 安全过滤\n按用户限速\n高风险回应的人工审批", + "wizard.risk_assessment.mitigation_measures.detail": "对每个可预见的滥用,命名一个具体的缓解。审查者将此列表与滥用列表对比 — 缺口是红旗。每行成为一个 YAML 列表项。ForgeLM 内置的 safety eval 和人工审批 gate 是这里良好的第一道防线。", + "wizard.risk_assessment.vulnerable_groups.label": "考虑了对脆弱群体的影响", + "wizard.risk_assessment.vulnerable_groups.hint": "第 9(2)(b) 条:确认你是否考虑了对儿童、少数群体等的影响。", + "wizard.risk_assessment.vulnerable_groups.detail": "第 9 条明确指出脆弱群体(儿童、老人、少数群体、残疾人)。仅在你真正考虑过系统如何影响他们之后再勾选 — 错误认证比无认证更糟。", + "wizard.eval_gates.title": "质量门(高级)", + "wizard.eval_gates.hint": "在基本 loss/safety gate 之上的可选 benchmark + LLM-as-Judge 评分。", + "wizard.benchmark.enabled.label": "运行 lm-evaluation-harness 基准", + "wizard.benchmark.enabled.hint": "训练后在学术任务上(ARC、HellaSwag、MMLU 等)给微调模型评分。", + "wizard.benchmark.enabled.detail": "lm-evaluation-harness 是 LLM 基准测试的事实标准。启用后对你的微调模型运行列出的任务,并将 JSON 结果存储在 checkpoint 旁边。", + "wizard.benchmark.tasks.label": "任务(每行一个)", + "wizard.benchmark.tasks.hint": "常见:arc_easy、arc_challenge、hellaswag、mmlu、truthfulqa、winogrande、gsm8k。", + "wizard.benchmark.min_score.label": "最小平均准确率", + "wizard.benchmark.min_score.hint": "0.0–1.0。低于此 auto_revert 撤销微调。留空跳过阈值门。", + "wizard.benchmark.min_score.placeholder": "例如 0.55", + "wizard.judge.enabled.label": "LLM-as-Judge 评分", + "wizard.judge.enabled.hint": "用更强的模型(例如 GPT-4o)对生成评分。", + "wizard.judge.enabled.detail": "LLM-as-Judge 让更强的模型在 1–10 量表上为微调模型的输出打分。在没有单一正确答案的聊天/指令质量上比基准更好。", + "wizard.judge.model.label": "Judge 模型", + "wizard.judge.model.hint": "API 模型名(gpt-4o、claude-opus-4-7)或本地路径。Judge 必须明显比测试模型更强。", + "wizard.judge.api_key_env.label": "API 密钥环境变量", + "wizard.judge.api_key_env.hint": "携带 API 密钥的环境变量名。空 = 本地 judge 模型。", + "wizard.judge.min_score.label": "最小平均 judge 分数", + "wizard.judge.min_score.hint": "1–10 量表。低于此触发 auto_revert。5.0 是\"平均\";7.0 是\"好\"。", + "wizard.governance.title": "数据治理(第 10 条)", + "wizard.governance.hint": "记录数据来源 + 偏差。high-risk 部署强制要求。", + "wizard.governance.collection_method.label": "收集方法", + "wizard.governance.collection_method.hint": "第 10(2)(b) 条:训练数据如何获取。", + "wizard.governance.collection_method.placeholder": "2023–2025 公共网络存档抓取,按 URL 哈希去重。", + "wizard.governance.collection_method.detail": "告诉审查者你的数据如何到达 trainer。模糊回答(\"互联网\")无法通过第 10 条审计。", + "wizard.governance.annotation_process.label": "标注流程", + "wizard.governance.annotation_process.hint": "第 10(2)(b) 条:标注方法 — 谁用什么准则标注。", + "wizard.governance.annotation_process.placeholder": "两名训练有素的标注员标注每个样本;冲突由资深复核解决。", + "wizard.governance.annotation_process.detail": "如果数据已标注,描述如何。标注员一致性、准则版本、冲突解决。", + "wizard.governance.known_biases.label": "已知偏差", + "wizard.governance.known_biases.hint": "第 10(2)(f) 条:操作者了解数据中的偏差。", + "wizard.governance.known_biases.placeholder": "主要为英语;<18 和 >65 年龄段代表不足。", + "wizard.governance.known_biases.detail": "没有数据集是完美平衡的。提前命名失衡是诚实的回答。", + "wizard.governance.personal_data.label": "训练数据包含个人数据", + "wizard.governance.personal_data.hint": "第 10(5) 条 + GDPR:如果任何样本包含可识别个人则勾选。", + "wizard.governance.dpia_completed.label": "DPIA 已完成(GDPR 第 35 条)", + "wizard.governance.dpia_completed.hint": "本数据集的数据保护影响评估已完成并记录。", + "wizard.webhook.title": "通知(webhook)", + "wizard.webhook.hint": "在训练事件发送 Slack/Teams/Discord 警报。", + "wizard.webhook.url.label": "Webhook URL 或环境变量", + "wizard.webhook.url.hint": "直接 https URL 或承载它的环境变量名(secret 推荐)。", + "wizard.webhook.url.placeholder": "https://hooks.slack.com/services/... 或 env:SLACK_WEBHOOK_URL", + "wizard.webhook.url.detail": "你可以直接粘贴 URL,或使用 env: 前缀指向环境变量。", + "wizard.webhook.enabled.label": "发送 webhook 通知", + "wizard.webhook.enabled.hint": "启用后,ForgeLM 在你选择的事件发送 webhook payload。关闭 = YAML 不包含任何 webhook 块。", + "wizard.webhook.enabled.detail": "通知将训练事件连接到你的团队聊天(Slack / Teams / Discord)或任何接受 JSON 的 HTTPS endpoint。审计日志条目记录每次发送 — 包括失败 — 提供独立追踪。如果不想 YAML 中包含任何 webhook 配置,保持关闭。", + "wizard.webhook.notify_start.label": "训练开始时通知", + "wizard.webhook.notify_success.label": "成功时通知", + "wizard.webhook.notify_failure.label": "失败时通知", + "wizard.monitoring.title": "上市后监控(第 12+17 条)", + "wizard.monitoring.hint": "将训练指标连接到 Prometheus/Datadog/自定义 webhook 以持续观测。", + "wizard.monitoring.enabled.label": "启用监控 hook", + "wizard.monitoring.enabled.hint": "EU AI Act 第 12 + 17 条要求 high-risk 系统强制启用。", + "wizard.monitoring.enabled.detail": "high-risk 系统部署后,EU AI Act 要求持续观察 — 漂移检测、错误报告、定期准确性复查。", + "wizard.monitoring.endpoint.label": "监控 endpoint", + "wizard.monitoring.endpoint.hint": "你的 Prometheus push gateway、Datadog 或自定义 webhook 接收器的 URL。", + "wizard.monitoring.endpoint.placeholder": "https://prometheus.example.com/metrics 或 env:DATADOG_URL", + "wizard.monitoring.metrics_export.label": "指标格式", + "wizard.monitoring.metrics_export.none": "无", + "wizard.monitoring.metrics_export.prometheus": "Prometheus", + "wizard.monitoring.metrics_export.datadog": "Datadog", + "wizard.monitoring.metrics_export.custom": "自定义 webhook", + "wizard.monitoring.alert_on_drift.label": "漂移时报警", + "wizard.monitoring.alert_on_drift.hint": "当漂移检测器标记回归时发出 webhook 警报。", + "wizard.synthetic.title": "合成数据生成(高级)", + "wizard.synthetic.hint": "使用更强的模型生成 teacher → student 训练数据。", + "wizard.synthetic.enabled.label": "生成合成数据", + "wizard.synthetic.enabled.hint": "在 seed prompts 上运行 teacher 模型以生成训练样本。", + "wizard.synthetic.enabled.detail": "合成数据生成让更强的模型(例如 GPT-4)为更小的微调目标生成训练样本。", + "wizard.synthetic.teacher_model.label": "Teacher 模型", + "wizard.synthetic.teacher_model.hint": "API 模型名(gpt-4o、claude-opus-4-7)或本地路径。", + "wizard.synthetic.seed_file.label": "Seed prompts 文件", + "wizard.synthetic.seed_file.hint": "JSONL 或纯文本 seed prompts 文件。Teacher 为每个生成响应。", + "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", + "wizard.synthetic.api_key_env.label": "Teacher API 密钥环境变量", + "wizard.synthetic.api_key_env.hint": "承载 teacher API 密钥的环境变量名。空 = 本地 teacher 模型。", + "wizard.retention.title": "保留期限(GDPR 第 5+17 条)", + "wizard.retention.hint": "ForgeLM 保留 audit log、staging 模型、临时 artefact 多长时间。", + "wizard.retention.audit_log.label": "Audit log 保留(天)", + "wizard.retention.audit_log.hint": "audit_log.jsonl 保留多久。默认 1825(5 年)符合 EU AI Act。", + "wizard.retention.staging.label": "Staging 期限(天)", + "wizard.retention.staging.hint": "final_model.staging/ 中被拒绝的模型保留多久。默认 7。", + "wizard.retention.ephemeral.label": "临时 artefact(天)", + "wizard.retention.ephemeral.hint": "中间 checkpoint、eval 样本保留多久。默认 90。", + "wizard.retention.raw_documents.label": "原始文档(天)", + "wizard.retention.raw_documents.hint": "ingestion 后原始 PDF/DOCX 保留多久。默认 90。", + "wizard.retention.enforce.label": "强制自动删除", + "wizard.retention.enforce.hint": "启用后,ForgeLM 在期限后自动删除 artefact。默认关闭。", + "wizard.retention.enforce.log_only": "log_only — 将违规记录到 audit(默认)", + "wizard.retention.enforce.warn": "warn_on_excess — log + stderr 警告", + "wizard.retention.enforce.block": "block_on_excess — 中止 trainer(受监管 CI)", "wizard.step.review.title": "审查你的配置", "wizard.step.review.name": "审查", "wizard.step.review.desc": "这是 YAML。复制、下载并运行。", @@ -2468,6 +3745,25 @@ "wizard.review.reset": "重置向导", "wizard.review.reset.confirm":"重置所有向导答案并重新开始?你的 YAML 将丢失。", "wizard.review.run_label": "然后运行:", + "wizard.error.model.required": "你在上一步选择了\"自定义 HuggingFace 模型\",所以此字段为必填。输入 org/repo 格式的 ID(如 meta-llama/Llama-3.1-8B-Instruct)或返回上一步选择一个预设。", + "wizard.error.model.format": "HuggingFace Hub ID 使用 org/repo 格式(如 meta-llama/Llama-3.1-8B-Instruct)。不带斜杠的纯名称在训练时无法解析。", + "wizard.error.dataset.required": "继续之前请选择一个数据集 — 没有路径或 HuggingFace 名称,trainer 没有可学习的对象。", + "wizard.error.webhook.required": "你启用了通知,所以需要一个 webhook URL 或环境变量。要么关闭上方的通知,要么粘贴你的 Slack / Teams / Discord URL(生产环境密钥请使用 env:SLACK_WEBHOOK_URL)。", + "wizard.error.monitoring.endpoint.required": "你启用了上市后监控,所以需要一个推送指标的 endpoint。要么关闭上方的监控,要么粘贴你的 Prometheus / Datadog / 自定义 webhook URL(密钥请使用 env:变量名)。", + "wizard.error.synthetic.teacher_model.required": "你启用了合成数据生成,所以需要一个 teacher 模型名(如 gpt-4o、claude-opus-4-7 或本地模型路径)。", + "wizard.error.synthetic.seed_file.required": "你启用了合成数据生成,所以需要一个 seed prompts 文件(如 ./data/seed_prompts.jsonl);teacher 会为每个 prompt 生成一个回应。", + "wizard.error.synthetic.api_key_env.required": "你启用了合成数据生成,所以需要承载 teacher API 密钥的环境变量(通常是 OPENAI_API_KEY 或 ANTHROPIC_API_KEY)。", + "wizard.error.compliance.provider_name.required": "EU AI Act Annex IV §1 要求 high-risk 系统提供供应商的法律实体名称。请填写或降低风险分类。", + "wizard.error.compliance.system_name.required": "EU AI Act Annex IV §1 要求 high-risk 部署提供人类可读的系统名称。请填写或降低风险分类。", + "wizard.error.compliance.intended_purpose.required": "EU AI Act Annex IV §1 要求 high-risk 系统声明预期用途。请用平实语言描述用途或降低风险分类。", + "wizard.error.risk_assessment.intended_use.required": "EU AI Act 第 9 条要求 high-risk 系统记录预期使用方式。请描述系统应做什么,或降低风险分类。", + "wizard.error.risk_assessment.foreseeable_misuse.required": "EU AI Act 第 9 条要求 high-risk 系统说明可预见的滥用场景。请每行列出一个,或降低风险分类。", + "wizard.error.risk_assessment.mitigation_measures.required": "EU AI Act 第 9 条要求 high-risk 系统说明缓解措施。请列出已实施的控制(每行一个),或降低风险分类。", + "wizard.error.governance.collection_method.required": "EU AI Act 第 10 条要求 high-risk 系统提供数据来源文档。请描述训练数据是如何获取的,或降低风险分类。", + "wizard.error.governance.annotation_process.required": "EU AI Act 第 10 条要求标注方法文档。请描述你的标注流程(\"无标注\"对原始文本 SFT 有效),或降低风险分类。", + "wizard.error.governance.known_biases.required": "EU AI Act 第 10 条要求 high-risk 系统记录已知的数据集偏差。请列出你了解的失衡,或降低风险分类。", + "wizard.error.benchmark.tasks.required": "你启用了 benchmark gate,所以至少需要一个任务。添加 lm-evaluation-harness 名称(如 arc_easy、hellaswag、mmlu,每行一个),或关闭上方的 benchmark。", + "wizard.error.judge.api_key_env.required": "你启用了 LLM-as-Judge,所以需要承载 judge API 密钥的环境变量(通常是 OPENAI_API_KEY 或 ANTHROPIC_API_KEY)。", "quickstart.spotlight.eyebrow": "引导式配置器", "quickstart.spotlight.title": "不想手写 YAML?", "quickstart.spotlight.lead": "浏览器内向导问七个问题,写出有效的 YAML,让你用一条命令运行。无需安装,进度本地保存。", @@ -2476,8 +3772,8 @@ "quickstart.spotlight.meta1": "约 3 分钟", "quickstart.spotlight.meta2": "6 种语言", "quickstart.spotlight.meta3": "无需安装", - "quickstart.spotlight.stage1.label": "回答 7 个问题", - "quickstart.spotlight.stage1.snippet": "用例 · 模型 · 数据 · LoRA · 风险", + "quickstart.spotlight.stage1.label": "回答 9 个问题", + "quickstart.spotlight.stage1.snippet": "用例 · trainer · 模型 · 数据 · 风险", "quickstart.spotlight.stage2.label": "得到一份有效 YAML", "quickstart.spotlight.stage3.label": "运行它", "quickstart.spotlight.manual_intro": "或者手动浏览五个步骤 ↓" diff --git a/site/js/wizard.js b/site/js/wizard.js index 591078f4..039da093 100644 --- a/site/js/wizard.js +++ b/site/js/wizard.js @@ -87,11 +87,40 @@ /* ── State + persistence ──────────────────────────────── */ var STORAGE_KEY = 'forgelm.wizard.state'; - // Schema version 2 drops the data.format / prompt_field / response_field - // and the compliance audit_log / annex_iv_export toggles — those fields - // do not exist in forgelm/config.py (DataConfig + ComplianceMetadataConfig - // both use ``extra="forbid"``). Any v1 snapshot is discarded on load. - var STATE_VERSION = 2; + // Schema version 3 adds the explicit trainer-selection step + the + // trainer-specific hyperparameter knobs (dpo_beta, simpo_*, kto_beta, + // grpo_*) that the schema supports but the wizard previously hid. + // v2 → v3: new fields don't break old saves (loadState merges over + // defaults), but bumping the version reserves the right to drop + // incompatible state shapes if needed in the future. + var STATE_VERSION = 3; + + // Trainer registry — single source of truth for the 6 trainer_type + // values the schema accepts (TrainingConfig.trainer_type Literal in + // forgelm/config.py). Keep ordered; ``isPreference`` controls which + // dataset shape rendering applies, ``hasParam`` lists which extra + // hyperparameters must surface in Step 5 + emitted to YAML. + var TRAINERS = [ + { id: 'sft', recommended: true, + isPreference: false, isKto: false, isGrpo: false, + params: [] }, + { id: 'dpo', isPreference: true, isKto: false, isGrpo: false, + params: ['dpoBeta'] }, + { id: 'orpo', isPreference: true, isKto: false, isGrpo: false, + params: ['orpoBeta'] }, + { id: 'simpo', isPreference: true, isKto: false, isGrpo: false, + params: ['simpoGamma', 'simpoBeta'] }, + { id: 'kto', isPreference: false, isKto: true, isGrpo: false, + params: ['ktoBeta'] }, + { id: 'grpo', isPreference: false, isKto: false, isGrpo: true, + params: ['grpoNumGenerations', 'grpoMaxCompletionLength', 'grpoRewardModel'] } + ]; + function trainerDef(id) { + for (var i = 0; i < TRAINERS.length; i++) { + if (TRAINERS[i].id === id) return TRAINERS[i]; + } + return TRAINERS[0]; // fallback to SFT + } function defaultState() { return { @@ -100,6 +129,10 @@ experience: 'beginner', // 'beginner' | 'expert' detailsVisible: true, // toggle for inline tutorial paragraphs useCase: null, // 'customer-support' | 'code-copilot' | 'domain-expert' | 'grpo-math' | 'medical-tr' | 'custom' + // Trainer selection. Use-case picks a sensible preset, but the + // operator can override on Step 3 (trainer step). All 6 schema + // values reachable: sft / dpo / orpo / simpo / kto / grpo. + trainerType: 'sft', model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'huggingface', // 'huggingface' | 'local-jsonl' | 'local-pdf' @@ -110,12 +143,38 @@ // → instruction). Forcing them in the YAML would trigger // ``extra="forbid"`` on DataConfig. qlora: true, + // PEFT variant. 'lora' (default standard LoRA), 'dora' (weight- + // decomposed), 'pissa' (singular value initialised), 'rslora' + // (rank-stabilised). Mutually exclusive with GaLore (next field). + loraMethod: 'lora', + // GaLore: full-parameter training via gradient projection — no + // adapters. When true, lora.method emission becomes inert (the + // schema requires the lora block but GaLore bypasses it at + // runtime) and training.galore_enabled fires. + galoreEnabled: false, + // GaLore optimizer variant. Only consulted when galoreEnabled. + // _8bit halves optimiser-state VRAM; _layerwise cuts peak VRAM + // further by recomputing per-layer. + galoreOptim: 'galore_adamw_8bit', loraR: 8, loraAlpha: 16, epochs: 3, learningRate: '1e-4', batchSize: 2, gradientAccumulation: 2, + // Trainer-specific hyperparameters. Defaults mirror the schema + // defaults in forgelm/config.py (TrainingConfig fields), so a + // wizard-emitted YAML matches the runtime defaults when the + // operator doesn't deviate. Each value is conditionally + // surfaced + emitted based on state.trainerType. + dpoBeta: 0.1, // schema default + orpoBeta: 0.1, // schema default + simpoGamma: 0.5, // schema default + simpoBeta: 2.0, // schema default + ktoBeta: 0.1, // schema default + grpoNumGenerations: 4, // schema default + grpoMaxCompletionLength: 512, // schema default + grpoRewardModel: '', // schema default is None; '' = omit from YAML // EU AI Act risk + evaluation toggles. Audit log + Annex IV export // are NOT YAML toggles — the audit log is always emitted, and the // Annex IV bundle is produced whenever ``compliance.*`` metadata is @@ -131,7 +190,77 @@ // tiers where the EU AI Act requires named provider info. providerName: '', systemName: '', - intendedPurpose: '' + intendedPurpose: '', + // Evaluation: benchmark (lm-evaluation-harness) + LLM Judge. + // Both are nested under evaluation.* and gated by their own + // ``enabled`` toggles. ``benchmarkTasks`` is newline-separated + // (e.g. arc_easy / hellaswag / mmlu) and emitted as a YAML list. + benchmarkEnabled: false, + benchmarkTasks: 'arc_easy\nhellaswag\nmmlu', + benchmarkMinScore: '', // empty → schema default (None) + judgeEnabled: false, + judgeModel: 'gpt-4o', + judgeApiKeyEnv: 'OPENAI_API_KEY', + judgeMinScore: 5.0, + // Article 9 risk assessment fields. ``risk_category`` mirrors + // ``compliance.risk_classification`` so the two sibling tier + // fields stay in lockstep — see ForgeConfig._risk_tiers in + // forgelm/config.py for why both exist. ``foreseeableMisuse`` + // and ``mitigationMeasures`` accept newline-separated lists in + // the textarea and are emitted as YAML arrays. + intendedUse: '', + foreseeableMisuse: '', + mitigationMeasures: '', + vulnerableGroupsConsidered: false, + // Article 10 data governance — surfaces inside the dataset + // step accordion. All fields are free-text apart from the two + // booleans (personal data + DPIA). + collectionMethod: '', + annotationProcess: '', + knownBiases: '', + personalDataIncluded: false, + dpiaCompleted: false, + // Webhook (Slack/Teams/Discord) notifications. ``webhookEnabled`` + // gates the whole block — when off, the YAML omits the webhook + // section entirely. When on, ``webhookUrl`` is required (validated + // in validateStep). The URL accepts either a literal https value + // OR ``env:VAR_NAME`` to source from the environment — buildYaml + // branches on the prefix. + webhookEnabled: false, + webhookUrl: '', + webhookNotifyStart: false, + webhookNotifySuccess: true, + webhookNotifyFailure: true, + // Article 12+17 post-market monitoring. Auto-coerced to true + // for high-risk tiers. + monitoringEnabled: false, + monitoringEndpoint: '', + metricsExport: 'none', // 'none' | 'prometheus' | 'datadog' | 'custom_webhook' + alertOnDrift: true, + // Synthetic data generation (teacher → student). Optional + // pre-training stage; ``syntheticTeacherModel`` is the API + // name or local path for the teacher. + syntheticEnabled: false, + syntheticTeacherModel: 'gpt-4o', + syntheticSeedFile: '', + syntheticApiKeyEnv: 'OPENAI_API_KEY', + // GDPR Article 5+17 retention horizons. Defaults below match + // forgelm/config.py RetentionConfig. + retentionAuditLog: 1825, // 5 years + retentionStaging: 7, + retentionEphemeral: 90, + retentionRawDocuments: 90, + // Policy enforcement: 'log_only' (default; logs to audit only), + // 'warn_on_excess' (stderr warning), 'block_on_excess' (aborts + // trainer with EXIT_EVAL_FAILURE). Schema Literal — must use + // one of these three values. + retentionEnforce: 'log_only', + // Track which steps the operator tried to advance past while + // failing required-field validation. The renderer reads this to + // decide whether to paint inline error messages — so the FIRST + // visit to a step is clean (no errors shown), and only after + // hitting Next on an invalid step do the errors appear. + attemptedSteps: [] }; } @@ -146,6 +275,11 @@ Object.keys(parsed).forEach(function (k) { if (Object.hasOwn(merged, k)) merged[k] = parsed[k]; }); + // ``attemptedSteps`` is per-session — wiping it on every load + // ensures the operator opens the wizard to a clean form, no + // pre-painted validation errors from a prior session. The error + // surface only fires once they hit Next on an invalid step. + merged.attemptedSteps = []; return merged; } catch (e) { return null; @@ -184,34 +318,160 @@ } } + /* ── Required-field validation ────────────────────────── */ + // Some steps require operator input that has no sensible default — + // e.g. a custom HuggingFace model ID, or a dataset path / Hub name. + // ``validateStep(state, stepId)`` returns an array of {field, key} + // entries, one per validation failure. Empty array = step is valid. + // The renderer for each step calls back into this function and, if + // ``state.attemptedSteps`` includes the current step ID, paints + // inline errors next to the offending input. ``goNext()`` blocks + // advancement when validateStep returns a non-empty array. + function validateStep(state, stepId) { + var errors = []; + var blank = function (v) { return !((v || '').trim()); }; + var strict = isStrictRisk(state); + + if (stepId === 'model') { + if (state.modelPreset === 'custom') { + var v = (state.model || '').trim(); + if (!v) { + errors.push({ field: 'model', key: 'wizard.error.model.required' }); + } else if (v.indexOf('/') < 0) { + // HF Hub IDs are /; bare names won't resolve. + errors.push({ field: 'model', key: 'wizard.error.model.format' }); + } + } + } else if (stepId === 'dataset') { + if (blank(state.datasetName)) { + errors.push({ field: 'datasetName', key: 'wizard.error.dataset.required' }); + } + // Article 10 (data governance) is mandatory documentation for + // high-risk + unacceptable systems. The wizard treats the three + // free-text fields as required when the operator picked a + // strict tier on the compliance step. + if (strict) { + if (blank(state.collectionMethod)) { + errors.push({ field: 'collectionMethod', key: 'wizard.error.governance.collection_method.required' }); + } + if (blank(state.annotationProcess)) { + errors.push({ field: 'annotationProcess', key: 'wizard.error.governance.annotation_process.required' }); + } + if (blank(state.knownBiases)) { + errors.push({ field: 'knownBiases', key: 'wizard.error.governance.known_biases.required' }); + } + } + } else if (stepId === 'compliance') { + // High-risk + unacceptable tiers carry mandatory Annex IV §1 + // metadata + Article 9 risk-management evidence. Without these + // the runtime accepts empty strings (schema defaults), but the + // resulting compliance bundle would fail an external audit, so + // we enforce the obligation here. + if (strict) { + if (blank(state.providerName)) { + errors.push({ field: 'providerName', key: 'wizard.error.compliance.provider_name.required' }); + } + if (blank(state.systemName)) { + errors.push({ field: 'systemName', key: 'wizard.error.compliance.system_name.required' }); + } + if (blank(state.intendedPurpose)) { + errors.push({ field: 'intendedPurpose', key: 'wizard.error.compliance.intended_purpose.required' }); + } + if (blank(state.intendedUse)) { + errors.push({ field: 'intendedUse', key: 'wizard.error.risk_assessment.intended_use.required' }); + } + if (blank(state.foreseeableMisuse)) { + errors.push({ field: 'foreseeableMisuse', key: 'wizard.error.risk_assessment.foreseeable_misuse.required' }); + } + if (blank(state.mitigationMeasures)) { + errors.push({ field: 'mitigationMeasures', key: 'wizard.error.risk_assessment.mitigation_measures.required' }); + } + } + // Eval-gate accordion: each gate's primary input is required + // when the gate is enabled. ``benchmarkTasks`` is a newline list + // — empty after trim means "no tasks", which makes the gate a + // no-op. Same for the judge API key env var. + if (state.benchmarkEnabled && blank(state.benchmarkTasks)) { + errors.push({ field: 'benchmarkTasks', key: 'wizard.error.benchmark.tasks.required' }); + } + if (state.judgeEnabled && blank(state.judgeApiKeyEnv)) { + errors.push({ field: 'judgeApiKeyEnv', key: 'wizard.error.judge.api_key_env.required' }); + } + } else if (stepId === 'operations') { + // Each accordion that exposes a primary input gates that input + // as required ONLY when the accordion's own enable toggle is on. + // Off → skip; the operator opted out of the whole feature. + if (state.webhookEnabled && blank(state.webhookUrl)) { + errors.push({ field: 'webhookUrl', key: 'wizard.error.webhook.required' }); + } + if (state.monitoringEnabled && blank(state.monitoringEndpoint)) { + errors.push({ field: 'monitoringEndpoint', key: 'wizard.error.monitoring.endpoint.required' }); + } + if (state.syntheticEnabled) { + if (blank(state.syntheticTeacherModel)) { + errors.push({ field: 'syntheticTeacherModel', key: 'wizard.error.synthetic.teacher_model.required' }); + } + if (blank(state.syntheticSeedFile)) { + errors.push({ field: 'syntheticSeedFile', key: 'wizard.error.synthetic.seed_file.required' }); + } + if (blank(state.syntheticApiKeyEnv)) { + errors.push({ field: 'syntheticApiKeyEnv', key: 'wizard.error.synthetic.api_key_env.required' }); + } + } + } + return errors; + } + + function fieldHasError(state, stepId, fieldName) { + if (!state.attemptedSteps || state.attemptedSteps.indexOf(stepId) < 0) return null; + var errs = validateStep(state, stepId); + for (var i = 0; i < errs.length; i++) { + if (errs[i].field === fieldName) return errs[i].key; + } + return null; + } + /* ── Use-case templates (Step 2 → preselect Step 3-5) ─── */ + // Use-case → state preset. ``trainerType`` is included so the new + // trainer step (Step 3) lands on the right card by default; the + // operator can still override it. The mapping is aligned with the + // shipped templates under ``forgelm/templates/`` — every shipped + // template uses ``trainer_type: sft`` except ``grpo-math`` which + // uses ``grpo``. The wizard previously emitted DPO/ORPO for + // customer-support / code-copilot which contradicted the templates; + // this file now matches the runtime defaults. var USE_CASE_PRESETS = { 'customer-support': { + trainerType: 'sft', model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'huggingface', datasetName: 'argilla/Capybara-Preferences' }, 'code-copilot': { + trainerType: 'sft', model: 'Qwen/Qwen2.5-Coder-7B-Instruct', modelPreset: 'custom', datasetKind: 'huggingface', datasetName: 'bigcode/the-stack-smol-xs' }, 'domain-expert': { + trainerType: 'sft', model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'local-pdf', datasetName: './policies/' }, 'grpo-math': { + trainerType: 'grpo', model: 'Qwen/Qwen2.5-7B-Instruct', modelPreset: 'qwen-7b', datasetKind: 'huggingface', datasetName: 'openai/gsm8k' }, 'medical-tr': { + trainerType: 'sft', model: 'meta-llama/Llama-3.1-8B-Instruct', modelPreset: 'llama3-8b', datasetKind: 'huggingface', @@ -222,18 +482,6 @@ } }; - // Map use-case → trainer (reflected in the YAML's ``training.trainer_type``). - function trainerForUseCase(useCase) { - switch (useCase) { - case 'customer-support': return 'dpo'; - case 'code-copilot': return 'orpo'; - case 'domain-expert': return 'sft'; - case 'grpo-math': return 'grpo'; - case 'medical-tr': return 'sft'; - default: return 'sft'; - } - } - function applyUseCasePreset(state, useCase) { var preset = USE_CASE_PRESETS[useCase] || {}; Object.keys(preset).forEach(function (k) { state[k] = preset[k]; }); @@ -294,6 +542,13 @@ k(1, 'alpha', String(state.loraAlpha)); k(1, 'dropout', '0.1'); // schema default k(1, 'bias', '"none"'); + // ``method`` is emitted whenever the operator picked a non-default + // PEFT variant. GaLore mode also emits the lora block (schema + // requires it) but with the standard LoRA method as a no-op default + // — GaLore bypasses LoRA at runtime regardless of what's here. + if (!state.galoreEnabled && state.loraMethod && state.loraMethod !== 'lora') { + k(1, 'method', JSON.stringify(state.loraMethod)); + } k(1, 'target_modules', '["q_proj", "v_proj"]'); blank(); @@ -308,12 +563,38 @@ : './data/train.jsonl'; k(1, 'dataset_name_or_path', JSON.stringify(state.datasetName || defaultPath)); } - comment(' DataConfig auto-detects row shape: { "messages": [...] } → chat;'); - comment(' { "prompt": ..., "response": ... } → instruction-following.'); + // Trainer-aware autodetect note — different alignment paradigms + // expect different row shapes. SFT auto-detects messages or + // prompt+response; preference trainers (DPO/ORPO/SimPO) need + // chosen+rejected pairs; KTO needs binary feedback; GRPO needs + // a prompt and (optionally) a gold answer. + var trDef = trainerDef(state.trainerType); + if (trDef.isPreference) { + comment(' ' + state.trainerType.toUpperCase() + ' expects preference rows: { "prompt": ..., "chosen": ..., "rejected": ... }'); + } else if (trDef.isKto) { + comment(' KTO expects binary-feedback rows: { "prompt": ..., "completion": ..., "label": true|false }'); + } else if (trDef.isGrpo) { + comment(' GRPO expects rollout prompts: { "prompt": ..., "gold_answer": "..." }'); + comment(' (gold_answer is optional; required only when using the regex correctness reward.)'); + } else { + comment(' DataConfig auto-detects SFT row shape: { "messages": [...] } → chat;'); + comment(' { "prompt": ..., "response": ... } → instruction-following.'); + } + // Article 10 data governance — emitted as a nested ``governance`` + // block under data when the operator filled in any field. + var hasGovernance = state.collectionMethod || state.annotationProcess || state.knownBiases || state.personalDataIncluded || state.dpiaCompleted; + if (hasGovernance) { + header(1, 'governance'); + if (state.collectionMethod) k(2, 'collection_method', JSON.stringify(state.collectionMethod)); + if (state.annotationProcess) k(2, 'annotation_process', JSON.stringify(state.annotationProcess)); + if (state.knownBiases) k(2, 'known_biases', JSON.stringify(state.knownBiases)); + if (state.personalDataIncluded) k(2, 'personal_data_included', 'true'); + if (state.dpiaCompleted) k(2, 'dpia_completed', 'true'); + } blank(); header(0, 'training'); - k(1, 'trainer_type', JSON.stringify(trainerForUseCase(state.useCase))); + k(1, 'trainer_type', JSON.stringify(state.trainerType)); k(1, 'output_dir', '"./checkpoints"'); k(1, 'final_model_dir', '"final_model"'); k(1, 'num_train_epochs', String(state.epochs)); @@ -321,6 +602,36 @@ k(1, 'per_device_train_batch_size', String(state.batchSize)); k(1, 'gradient_accumulation_steps', String(state.gradientAccumulation)); k(1, 'merge_adapters', 'false'); + // GaLore: full-parameter training via gradient projection, + // alternative to LoRA. When enabled the lora.* block above is + // ignored at runtime; we still emit it because the schema requires + // the block to be present. + if (state.galoreEnabled) { + k(1, 'galore_enabled', 'true'); + k(1, 'galore_optim', JSON.stringify(state.galoreOptim)); + } + // Trainer-specific hyperparameters — only emit fields whose state + // value differs from the schema default OR when the operator is + // explicitly tuning that trainer (so the YAML carries an audit + // trail of what the wizard chose). For GRPO, ``grpo_reward_model`` + // stays out of the YAML when empty (schema default is None — TRL + // then wires the built-in format/length reward). + if (state.trainerType === 'dpo') { + k(1, 'dpo_beta', String(state.dpoBeta)); + } else if (state.trainerType === 'orpo') { + k(1, 'orpo_beta', String(state.orpoBeta)); + } else if (state.trainerType === 'simpo') { + k(1, 'simpo_gamma', String(state.simpoGamma)); + k(1, 'simpo_beta', String(state.simpoBeta)); + } else if (state.trainerType === 'kto') { + k(1, 'kto_beta', String(state.ktoBeta)); + } else if (state.trainerType === 'grpo') { + k(1, 'grpo_num_generations', String(state.grpoNumGenerations)); + k(1, 'grpo_max_completion_length', String(state.grpoMaxCompletionLength)); + if (state.grpoRewardModel) { + k(1, 'grpo_reward_model', JSON.stringify(state.grpoRewardModel)); + } + } blank(); header(0, 'evaluation'); @@ -342,6 +653,37 @@ k(2, 'min_classifier_confidence', '0.7'); k(2, 'max_safety_regression', '0.05'); } + // BenchmarkConfig (lm-evaluation-harness). Optional quality gate + // that scores the fine-tuned model on academic tasks. Tasks are + // emitted as a YAML list; min_score is omitted when blank so the + // schema default (None) applies. + if (state.benchmarkEnabled) { + header(1, 'benchmark'); + k(2, 'enabled', 'true'); + var benchTasks = (state.benchmarkTasks || '').split('\n') + .map(function (s) { return s.trim(); }) + .filter(function (s) { return s.length > 0; }); + if (benchTasks.length) { + lines.push(' tasks:'); + benchTasks.forEach(function (t) { lines.push(' - ' + JSON.stringify(t)); }); + } + var ms = parseFloat(state.benchmarkMinScore); + if (!isNaN(ms) && ms > 0) { + k(2, 'min_score', String(ms)); + } + } + // JudgeConfig (LLM-as-Judge). Optional gate that scores outputs + // with a stronger model (e.g. GPT-4o) and blocks promotion if + // average score falls below min_score. + if (state.judgeEnabled) { + header(1, 'llm_judge'); + k(2, 'enabled', 'true'); + k(2, 'judge_model', JSON.stringify(state.judgeModel || 'gpt-4o')); + if (state.judgeApiKeyEnv) { + k(2, 'judge_api_key_env', JSON.stringify(state.judgeApiKeyEnv)); + } + k(2, 'min_score', String(state.judgeMinScore)); + } blank(); // ComplianceMetadataConfig — Annex IV §1 provider + system info. @@ -355,6 +697,113 @@ if (state.intendedPurpose) k(1, 'intended_purpose', JSON.stringify(state.intendedPurpose)); blank(); + // Article 9 risk assessment block. Only emit when the operator + // filled in at least one field (or when the tier is strict, in + // which case Article 9 documentation is mandatory). The + // risk_category mirrors compliance.risk_classification so the + // two sibling fields don't drift. + function listFromTextarea(text) { + if (!text) return []; + return text.split('\n').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; }); + } + var raMisuse = listFromTextarea(state.foreseeableMisuse); + var raMitigation = listFromTextarea(state.mitigationMeasures); + var hasRiskAssessment = state.intendedUse || raMisuse.length || raMitigation.length || state.vulnerableGroupsConsidered || isStrictRisk(state); + if (hasRiskAssessment) { + header(0, 'risk_assessment'); + k(1, 'risk_category', JSON.stringify(state.riskClassification)); + if (state.intendedUse) k(1, 'intended_use', JSON.stringify(state.intendedUse)); + if (raMisuse.length) { + lines.push(' foreseeable_misuse:'); + raMisuse.forEach(function (m) { lines.push(' - ' + JSON.stringify(m)); }); + } + if (raMitigation.length) { + lines.push(' mitigation_measures:'); + raMitigation.forEach(function (m) { lines.push(' - ' + JSON.stringify(m)); }); + } + if (state.vulnerableGroupsConsidered) k(1, 'vulnerable_groups_considered', 'true'); + blank(); + } + + // Webhook (Slack/Teams/Discord notifications). The URL field + // accepts either a literal https URL or ``env:VAR_NAME`` to + // source from the environment — the latter is the production + // path because YAML files are version-controlled and the URL + // is a secret. + if (state.webhookEnabled && state.webhookUrl) { + header(0, 'webhook'); + var raw = state.webhookUrl.trim(); + if (raw.indexOf('env:') === 0) { + k(1, 'url_env', JSON.stringify(raw.slice(4))); + } else { + k(1, 'url', JSON.stringify(raw)); + } + if (state.webhookNotifyStart) k(1, 'notify_on_start', 'true'); + if (!state.webhookNotifySuccess) k(1, 'notify_on_success', 'false'); + if (!state.webhookNotifyFailure) k(1, 'notify_on_failure', 'false'); + blank(); + } + + // Article 12+17 post-market monitoring. Auto-coerced enabled for + // strict tiers (operator can't disable it under high-risk). + var monitoringActive = state.monitoringEnabled || isStrictRisk(state); + if (monitoringActive || state.monitoringEndpoint) { + header(0, 'monitoring'); + k(1, 'enabled', monitoringActive ? 'true' : 'false'); + if (state.monitoringEndpoint) { + var rawM = state.monitoringEndpoint.trim(); + if (rawM.indexOf('env:') === 0) { + k(1, 'endpoint_env', JSON.stringify(rawM.slice(4))); + } else { + k(1, 'endpoint', JSON.stringify(rawM)); + } + } + if (state.metricsExport && state.metricsExport !== 'none') { + k(1, 'metrics_export', JSON.stringify(state.metricsExport)); + } + if (!state.alertOnDrift) k(1, 'alert_on_drift', 'false'); + blank(); + } + + // Synthetic data generation (teacher → student distillation). + // Only emitted when the operator opted in. + if (state.syntheticEnabled) { + header(0, 'synthetic'); + k(1, 'enabled', 'true'); + k(1, 'teacher_model', JSON.stringify(state.syntheticTeacherModel || 'gpt-4o')); + if (state.syntheticSeedFile) k(1, 'seed_file', JSON.stringify(state.syntheticSeedFile)); + if (state.syntheticApiKeyEnv) k(1, 'api_key_env', JSON.stringify(state.syntheticApiKeyEnv)); + blank(); + } + + // GDPR Article 5(1)(e) + 17 retention horizons. Only emit fields + // that deviate from schema defaults (1825 / 7 / 90 / 90) so the + // YAML stays terse for the typical case. + var retainDefaults = { audit: 1825, staging: 7, ephemeral: 90, raw: 90 }; + var nonDefaultRetention = + state.retentionAuditLog !== retainDefaults.audit || + state.retentionStaging !== retainDefaults.staging || + state.retentionEphemeral !== retainDefaults.ephemeral || + state.retentionRawDocuments !== retainDefaults.raw || + (state.retentionEnforce && state.retentionEnforce !== 'log_only'); + if (nonDefaultRetention) { + header(0, 'retention'); + if (state.retentionAuditLog !== retainDefaults.audit) + k(1, 'audit_log_retention_days', String(state.retentionAuditLog)); + if (state.retentionStaging !== retainDefaults.staging) + k(1, 'staging_ttl_days', String(state.retentionStaging)); + if (state.retentionEphemeral !== retainDefaults.ephemeral) + k(1, 'ephemeral_artefact_retention_days', String(state.retentionEphemeral)); + if (state.retentionRawDocuments !== retainDefaults.raw) + k(1, 'raw_documents_retention_days', String(state.retentionRawDocuments)); + // Schema default for enforce is 'log_only'; only emit when the + // operator picked a stricter mode. + if (state.retentionEnforce && state.retentionEnforce !== 'log_only') { + k(1, 'enforce', JSON.stringify(state.retentionEnforce)); + } + blank(); + } + // Friendly closing comment so an operator reading the YAML cold // knows the audit log + Annex IV bundle land on disk regardless // of whether they show up as toggles here. @@ -415,10 +864,12 @@ var STEPS = [ { id: 'welcome', titleKey: 'wizard.step.welcome.title', nameKey: 'wizard.step.welcome.name' }, { id: 'use-case', titleKey: 'wizard.step.usecase.title', nameKey: 'wizard.step.usecase.name' }, + { id: 'trainer', titleKey: 'wizard.step.trainer.title', nameKey: 'wizard.step.trainer.name' }, { id: 'model', titleKey: 'wizard.step.model.title', nameKey: 'wizard.step.model.name' }, { id: 'dataset', titleKey: 'wizard.step.dataset.title', nameKey: 'wizard.step.dataset.name' }, { id: 'training', titleKey: 'wizard.step.training.title', nameKey: 'wizard.step.training.name' }, { id: 'compliance', titleKey: 'wizard.step.compliance.title', nameKey: 'wizard.step.compliance.name' }, + { id: 'operations', titleKey: 'wizard.step.operations.title', nameKey: 'wizard.step.operations.name' }, { id: 'review', titleKey: 'wizard.step.review.title', nameKey: 'wizard.step.review.name' } ]; @@ -577,7 +1028,65 @@ persist(); } + // Map an invalid field name back to the accordion that hosts it. + // When the operator hits Next on an invalid step, we auto-open the + // accordion containing the first error so they don't have to hunt + // for it — particularly important for collapsed sections like + // governance / risk_assessment / eval_gates / webhook / monitoring. + var FIELD_TO_ACCORDION = { + collectionMethod: 'governance', + annotationProcess: 'governance', + knownBiases: 'governance', + intendedUse: 'risk_assessment', + foreseeableMisuse: 'risk_assessment', + mitigationMeasures: 'risk_assessment', + benchmarkTasks: 'eval_gates', + judgeApiKeyEnv: 'eval_gates', + webhookUrl: 'webhook', + monitoringEndpoint: 'monitoring', + syntheticTeacherModel: 'synthetic', + syntheticSeedFile: 'synthetic', + syntheticApiKeyEnv: 'synthetic' + }; + function goNext() { + // Validate the CURRENT step before advancing. On failure, mark + // the step as "attempted" so the renderer shows inline errors + // next to the offending fields, and rerender in place — the + // operator stays on the same step until they fix the input. + var currentStepId = STEPS[state.step].id; + var errors = validateStep(state, currentStepId); + if (errors.length > 0) { + if (!state.attemptedSteps) state.attemptedSteps = []; + if (state.attemptedSteps.indexOf(currentStepId) < 0) { + state.attemptedSteps.push(currentStepId); + } + // Auto-open every accordion that hosts an invalid field, so + // the offending input is actually visible after rerender. + if (!state.openAccordions) state.openAccordions = {}; + for (var i = 0; i < errors.length; i++) { + var accId = FIELD_TO_ACCORDION[errors[i].field]; + if (accId) state.openAccordions[accId] = true; + } + persist(); + render(); + // Move the keyboard focus + viewport to the FIRST invalid input. + // We focus the input itself rather than the .wizard-error sibling + // so screen readers announce the field label and the operator + // can start typing immediately. ``preventScroll: true`` on focus + // lets us drive the scroll separately with smooth behaviour. + var firstInvalid = pane.querySelector('.wizard-input.is-invalid'); + if (firstInvalid) { + if (typeof firstInvalid.focus === 'function') { + try { firstInvalid.focus({ preventScroll: true }); } + catch (_) { firstInvalid.focus(); } + } + if (typeof firstInvalid.scrollIntoView === 'function') { + firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + return; + } if (state.step < STEPS.length - 1) { state.step += 1; render(); @@ -674,6 +1183,106 @@ // event handlers (no innerHTML interpolation of state values). var STEP_RENDERERS = {}; + // Collapsible accordion section for grouping advanced / optional + // fields inside an existing step. Title is a button that toggles + // the open/closed state (persisted on the state object so closing + + // re-opening the wizard keeps the operator's preference). The body + // builder runs every render so the children stay current with state. + // SVG icon set used by wizard accordions. Each accordionId maps to + // a single Lucide-style line icon — the icon visually anchors the + // section's purpose so the operator can scan a stack of accordions + // and find the one they want without reading every label. Using + // ``currentColor`` lets the CSS theme-aware open/closed state flip + // tint without re-rendering the SVG. + var ACCORDION_ICONS = { + governance: '', + risk_assessment: '', + eval_gates: '', + webhook: '', + monitoring: '', + synthetic: '', + retention: '' + }; + + // Render an inline SVG with the icon paths for the given accordionId. + // Returns null when the id has no registered icon — the caller then + // omits the icon slot entirely so older accordions don't grow a + // broken-icon hole. + function accordionIconNode(accordionId) { + var paths = ACCORDION_ICONS[accordionId]; + if (!paths) return null; + return el('span', { + class: 'wizard-accordion-icon', + 'aria-hidden': 'true', + html: '' + paths + '' + }); + } + + // Chevron rendered as SVG instead of the previous unicode arrow. + // Rotation is driven by the parent ``.is-open`` class via CSS, not + // an inline style attribute, so the transition keyframes match the + // body-expand animation timing. + function accordionChevronNode() { + return el('span', { + class: 'wizard-accordion-chevron', + 'aria-hidden': 'true', + html: '' + }); + } + + function makeAccordion(state, persist, accordionId, titleKey, hintKey, buildBody) { + if (!state.openAccordions) state.openAccordions = {}; + var isOpen = !!state.openAccordions[accordionId]; + var section = el('section', { class: 'wizard-accordion' + (isOpen ? ' is-open' : '') }); + + var header = el('button', { + type: 'button', + class: 'wizard-accordion-header', + 'aria-expanded': isOpen ? 'true' : 'false', + on: { + click: function () { + state.openAccordions[accordionId] = !isOpen; + persist(); + // Toggle in place; child build is already attached. Just + // flip the open/expanded class + aria attribute. CSS drives + // the chevron rotation + body grid-rows animation off the + // ``.is-open`` class — no inline style mutation needed. + var newOpen = !!state.openAccordions[accordionId]; + isOpen = newOpen; + section.classList.toggle('is-open', newOpen); + header.setAttribute('aria-expanded', newOpen ? 'true' : 'false'); + } + } + }); + + var iconNode = accordionIconNode(accordionId); + if (iconNode) header.appendChild(iconNode); + + // Title + hint stack vertically in a single column so the row + // layout (icon | titles | chevron) stays consistent across short + // and long hints. + var titlesCol = el('div', { class: 'wizard-accordion-titles' }); + titlesCol.appendChild(el('span', { class: 'wizard-accordion-title', text: tr(titleKey) })); + if (hintKey) { + titlesCol.appendChild(el('span', { class: 'wizard-accordion-hint', text: tr(hintKey) })); + } + header.appendChild(titlesCol); + header.appendChild(accordionChevronNode()); + section.appendChild(header); + + // Body wrapper enables the smooth grid-rows expand/collapse + // animation. ``.wizard-accordion-body-wrap`` is a 0fr/1fr grid + // controlled by CSS; ``.wizard-accordion-body`` is its only child + // and stays fully rendered so the buildBody callback's listeners + // survive open/close cycles. + var bodyWrap = el('div', { class: 'wizard-accordion-body-wrap' }); + var body = el('div', { class: 'wizard-accordion-body' }); + buildBody(body); + bodyWrap.appendChild(body); + section.appendChild(bodyWrap); + return section; + } + function stepHeader(state, titleKey, descKey, tutorialKey) { var nodes = []; nodes.push(el('h2', { class: 'wizard-step-title', text: tr(titleKey) })); @@ -752,7 +1361,57 @@ pane.appendChild(grid); }; - /* Step 3: Base model */ + /* Step 3: Trainer (alignment paradigm). Surfaces all 6 schema-supported + trainer_type values as cards. The use-case step preselects a sensible + default but the operator can override here. Each card shows its data + shape requirement so the operator knows what JSONL rows the trainer + will accept (SFT: messages/prompt+response, preference trainers: + chosen+rejected, KTO: binary feedback, GRPO: prompt+gold_answer). */ + STEP_RENDERERS['trainer'] = function (pane, state, rerender, persist) { + stepHeader(state, 'wizard.step.trainer.title', 'wizard.step.trainer.desc', 'wizard.step.trainer.tutorial') + .forEach(function (n) { pane.appendChild(n); }); + + var grid = el('div', { class: 'wizard-cards' }); + TRAINERS.forEach(function (def) { + var titleNode = el('span', { class: 'wizard-card-title' }, [ + tr('wizard.trainer.' + def.id + '.title') + ]); + titleNode.appendChild(el('span', { + class: 'badge', + text: tr('wizard.trainer.' + def.id + '.badge') + })); + if (def.recommended) { + titleNode.appendChild(el('span', { + class: 'badge', + text: tr('wizard.trainer.recommended'), + style: 'background: rgba(249,115,22,0.15); color: var(--ember-bright); border-color: var(--ember);' + })); + } + var card = el('button', { + type: 'button', + class: 'wizard-card' + (state.trainerType === def.id ? ' is-selected' : ''), + on: { + click: function () { + state.trainerType = def.id; + persist(); + rerender(); + } + } + }, [ + titleNode, + el('span', { class: 'wizard-card-desc', text: tr('wizard.trainer.' + def.id + '.desc') }), + el('span', { + class: 'wizard-card-desc', + style: 'font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted); margin-top: 0.4rem;', + text: tr('wizard.trainer.' + def.id + '.shape') + }) + ]); + grid.appendChild(card); + }); + pane.appendChild(grid); + }; + + /* Step 4: Base model */ STEP_RENDERERS['model'] = function (pane, state, rerender, persist) { stepHeader(state, 'wizard.step.model.title', 'wizard.step.model.desc', 'wizard.step.model.tutorial') .forEach(function (n) { pane.appendChild(n); }); @@ -789,26 +1448,42 @@ pane.appendChild(grid); if (state.modelPreset === 'custom') { + var modelErrKey = fieldHasError(state, 'model', 'model'); var input = el('input', { type: 'text', - class: 'wizard-input', + class: 'wizard-input' + (modelErrKey ? ' is-invalid' : ''), placeholder: 'huggingface-org/your-model', - value: state.model || '' + value: state.model || '', + 'aria-invalid': modelErrKey ? 'true' : 'false' }); input.addEventListener('input', function () { state.model = input.value.trim(); persist(); - // Re-run YAML preview without re-rendering the pane (so the - // input keeps focus + caret position). + // If the operator was previously blocked on this field but + // their new value now validates, drop the error visual on + // next render (handled by re-running validation in render()). + var prevErr = fieldHasError(state, 'model', 'model'); + var stillErr = validateStep(state, 'model').some(function (e) { return e.field === 'model'; }); + if (prevErr && !stillErr) { + // Re-render to clear the error state cleanly. Cheap because + // the only thing that changes is the .is-invalid class. + rerender(); + return; + } + // Otherwise just refresh the YAML preview — keeps focus + + // caret position on the input. var yamlOutput = document.querySelector('[data-wizard-yaml-output]'); if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); }); - var row = el('div', { class: 'wizard-row' }, [ + var rowChildren = [ el('label', { class: 'wizard-row-label', text: tr('wizard.model.custom.label') }), input, el('span', { class: 'wizard-row-hint', text: tr('wizard.model.custom.hint') }) - ]); - pane.appendChild(row); + ]; + if (modelErrKey) { + rowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(modelErrKey) })); + } + pane.appendChild(el('div', { class: 'wizard-row' }, rowChildren)); } }; @@ -841,34 +1516,148 @@ }); pane.appendChild(grid); + var datasetErrKey = fieldHasError(state, 'dataset', 'datasetName'); var nameInput = el('input', { type: 'text', - class: 'wizard-input', + class: 'wizard-input' + (datasetErrKey ? ' is-invalid' : ''), placeholder: state.datasetKind === 'huggingface' ? 'org/dataset-name' : (state.datasetKind === 'local-pdf' ? './documents/' : './data/train.jsonl'), - value: state.datasetName + value: state.datasetName, + 'aria-invalid': datasetErrKey ? 'true' : 'false' }); nameInput.addEventListener('input', function () { state.datasetName = nameInput.value.trim(); persist(); + var prevErr = fieldHasError(state, 'dataset', 'datasetName'); + var stillErr = validateStep(state, 'dataset').some(function (e) { return e.field === 'datasetName'; }); + if (prevErr && !stillErr) { + rerender(); + return; + } var yamlOutput = document.querySelector('[data-wizard-yaml-output]'); if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); }); - pane.appendChild(el('div', { class: 'wizard-row' }, [ + var dsRowChildren = [ el('label', { class: 'wizard-row-label', text: tr('wizard.dataset.name.label') }), nameInput, el('span', { class: 'wizard-row-hint', text: tr('wizard.dataset.name.hint') }) - ])); - - // Format auto-detection note (replaces the prior format / prompt_field - // / response_field manual UI). DataConfig in forgelm/config.py infers - // the row shape from the JSONL fields, so no schema field is exposed - // for it — the wizard previously emitted these as YAML keys, which - // ``extra="forbid"`` rejected at dry-run. + ]; + if (datasetErrKey) { + dsRowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(datasetErrKey) })); + } + pane.appendChild(el('div', { class: 'wizard-row' }, dsRowChildren)); + + // Trainer-aware shape guidance. SFT auto-detects messages or + // prompt+response, but preference / KTO / GRPO trainers expect + // specific row shapes that DataConfig does NOT auto-detect — the + // operator must format the JSONL accordingly. We surface that + // expectation here instead of leaving them to find it in the + // runtime error. + var dsTrDef = trainerDef(state.trainerType); + var shapeKey; + if (dsTrDef.isPreference) shapeKey = 'preference'; + else if (dsTrDef.isKto) shapeKey = 'kto'; + else if (dsTrDef.isGrpo) shapeKey = 'grpo'; + else shapeKey = 'sft'; pane.appendChild(el('div', { class: 'wizard-step-tutorial', - html: '' + tr('wizard.dataset.autodetect.title') + ' ' + tr('wizard.dataset.autodetect.body') + html: '' + tr('wizard.dataset.shape.' + shapeKey + '.title') + ' ' + tr('wizard.dataset.shape.' + shapeKey + '.body') + })); + + function liveYaml() { + var yamlOutput = document.querySelector('[data-wizard-yaml-output]'); + if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); + } + function appendDetail(rowEl, detailKey) { + if (state.detailsVisible && detailKey) { + rowEl.appendChild(el('p', { class: 'wizard-row-detail', text: tr(detailKey) })); + } + } + + // Data Governance accordion (EU AI Act Article 10). + pane.appendChild(makeAccordion(state, persist, 'governance', + 'wizard.governance.title', 'wizard.governance.hint', function (body) { + + // High-risk + unacceptable tiers carry mandatory Article 10 + // documentation. We pass the error i18n key in only when those + // tiers are active; everywhere else the textarea stays optional. + var requireGov = isStrictRisk(state); + function makeTextarea(field, labelKey, hintKey, placeholderKey, detailKey, rows, errorKey) { + var errKey = errorKey ? fieldHasError(state, 'dataset', field) : null; + var ta = el('textarea', { + class: 'wizard-input' + (errKey ? ' is-invalid' : ''), + rows: String(rows || 2), + placeholder: tr(placeholderKey) + }); + ta.value = state[field] || ''; + var rowChildren = [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + ta, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]; + if (errKey) { + rowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(errKey) })); + } + var row = el('div', { class: 'wizard-row' }, rowChildren); + ta.addEventListener('input', function () { + state[field] = ta.value; + persist(); + liveYaml(); + if (errorKey) { + var stillErr = validateStep(state, 'dataset').some(function (e) { return e.field === field; }); + if (!stillErr) { + ta.classList.remove('is-invalid'); + var errEl = row.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + } + }); + appendDetail(row, detailKey); + return row; + } + + body.appendChild(makeTextarea('collectionMethod', + 'wizard.governance.collection_method.label', + 'wizard.governance.collection_method.hint', + 'wizard.governance.collection_method.placeholder', + 'wizard.governance.collection_method.detail', 2, + requireGov ? 'wizard.error.governance.collection_method.required' : null)); + body.appendChild(makeTextarea('annotationProcess', + 'wizard.governance.annotation_process.label', + 'wizard.governance.annotation_process.hint', + 'wizard.governance.annotation_process.placeholder', + 'wizard.governance.annotation_process.detail', 2, + requireGov ? 'wizard.error.governance.annotation_process.required' : null)); + body.appendChild(makeTextarea('knownBiases', + 'wizard.governance.known_biases.label', + 'wizard.governance.known_biases.hint', + 'wizard.governance.known_biases.placeholder', + 'wizard.governance.known_biases.detail', 2, + requireGov ? 'wizard.error.governance.known_biases.required' : null)); + + function makeCheckbox(field, labelKey, hintKey) { + var cb = el('input', { type: 'checkbox' }); + cb.checked = !!state[field]; + cb.addEventListener('change', function () { + state[field] = cb.checked; + persist(); + liveYaml(); + }); + return el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + cb, + el('span', { class: 'wizard-toggle-label', text: tr(labelKey) }) + ]), + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]); + } + body.appendChild(makeCheckbox('personalDataIncluded', + 'wizard.governance.personal_data.label', + 'wizard.governance.personal_data.hint')); + body.appendChild(makeCheckbox('dpiaCompleted', + 'wizard.governance.dpia_completed.label', + 'wizard.governance.dpia_completed.hint')); })); }; @@ -892,7 +1681,7 @@ } } - // QLoRA toggle + // QLoRA toggle (4-bit base model quantization) var qloraInput = el('input', { type: 'checkbox' }); qloraInput.checked = state.qlora; qloraInput.addEventListener('change', function () { @@ -910,6 +1699,94 @@ appendDetail(qloraRow, 'wizard.training.qlora.detail'); pane.appendChild(qloraRow); + // PEFT method radio (Standard LoRA / DoRA / PiSSA / RSLoRA). + // Hidden when GaLore is enabled — GaLore replaces LoRA so the + // method choice is irrelevant. Emits to lora.method in YAML. + if (!state.galoreEnabled) { + var methodRow = el('div', { class: 'wizard-row' }); + methodRow.appendChild(el('span', { class: 'wizard-row-label', text: tr('wizard.training.lora_method.label') })); + var methods = [ + { id: 'lora', titleKey: 'wizard.training.lora_method.standard.title', descKey: 'wizard.training.lora_method.standard.desc' }, + { id: 'dora', titleKey: 'wizard.training.lora_method.dora.title', descKey: 'wizard.training.lora_method.dora.desc' }, + { id: 'pissa', titleKey: 'wizard.training.lora_method.pissa.title', descKey: 'wizard.training.lora_method.pissa.desc' }, + { id: 'rslora', titleKey: 'wizard.training.lora_method.rslora.title', descKey: 'wizard.training.lora_method.rslora.desc' } + ]; + var methodGrid = el('div', { class: 'wizard-cards', style: 'grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.6rem;' }); + methods.forEach(function (m) { + var card = el('button', { + type: 'button', + class: 'wizard-card' + (state.loraMethod === m.id ? ' is-selected' : ''), + on: { + click: function () { + state.loraMethod = m.id; + persist(); + liveYaml(); + // Re-render to update the selected-card visual state. + rerender(); + } + } + }, [ + el('span', { class: 'wizard-card-title', text: tr(m.titleKey) }), + el('span', { class: 'wizard-card-desc', text: tr(m.descKey) }) + ]); + methodGrid.appendChild(card); + }); + methodRow.appendChild(methodGrid); + methodRow.appendChild(el('span', { class: 'wizard-row-hint', text: tr('wizard.training.lora_method.hint') })); + appendDetail(methodRow, 'wizard.training.lora_method.detail'); + pane.appendChild(methodRow); + } + + // GaLore toggle (full-parameter training via gradient projection). + // Mutually exclusive with LoRA-style adapters at runtime. + var galoreInput = el('input', { type: 'checkbox' }); + galoreInput.checked = state.galoreEnabled; + galoreInput.addEventListener('change', function () { + state.galoreEnabled = galoreInput.checked; + persist(); + // Re-render so the LoRA method radio + GaLore variant select + // appear/disappear in line with the toggle. + rerender(); + }); + var galoreRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + galoreInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.training.galore.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.training.galore.hint') }) + ]); + appendDetail(galoreRow, 'wizard.training.galore.detail'); + pane.appendChild(galoreRow); + + // GaLore variant select — only when GaLore is enabled. + if (state.galoreEnabled) { + var galoreSelect = el('select', { class: 'wizard-select' }); + [ + { id: 'galore_adamw', labelKey: 'wizard.training.galore_optim.adamw' }, + { id: 'galore_adamw_8bit', labelKey: 'wizard.training.galore_optim.adamw_8bit' }, + { id: 'galore_adafactor', labelKey: 'wizard.training.galore_optim.adafactor' }, + { id: 'galore_adamw_layerwise', labelKey: 'wizard.training.galore_optim.adamw_layerwise' }, + { id: 'galore_adamw_8bit_layerwise', labelKey: 'wizard.training.galore_optim.adamw_8bit_layerwise' }, + { id: 'galore_adafactor_layerwise', labelKey: 'wizard.training.galore_optim.adafactor_layerwise' } + ].forEach(function (opt) { + var option = el('option', { value: opt.id, text: tr(opt.labelKey) }); + if (state.galoreOptim === opt.id) option.selected = true; + galoreSelect.appendChild(option); + }); + galoreSelect.addEventListener('change', function () { + state.galoreOptim = galoreSelect.value; + persist(); + liveYaml(); + }); + var galoreOptimRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.training.galore_optim.label') }), + galoreSelect, + el('span', { class: 'wizard-row-hint', text: tr('wizard.training.galore_optim.hint') }) + ]); + appendDetail(galoreOptimRow, 'wizard.training.galore_optim.detail'); + pane.appendChild(galoreOptimRow); + } + // LoRA r / alpha sliders function makeSlider(field, min, max, hintKey, labelKey, detailKey) { var slider = el('input', { @@ -935,8 +1812,14 @@ appendDetail(row, detailKey); return row; } - pane.appendChild(makeSlider('loraR', 4, 64, 'wizard.training.lora_r.hint', 'wizard.training.lora_r.label', 'wizard.training.lora_r.detail')); - pane.appendChild(makeSlider('loraAlpha', 8, 128, 'wizard.training.lora_alpha.hint', 'wizard.training.lora_alpha.label', 'wizard.training.lora_alpha.detail')); + // LoRA rank / alpha — only meaningful when LoRA-style adapters + // are in use. Hidden when GaLore is enabled because GaLore + // bypasses LoRA entirely; the schema-required lora block still + // emits with default r=8 / alpha=16 in the YAML. + if (!state.galoreEnabled) { + pane.appendChild(makeSlider('loraR', 4, 64, 'wizard.training.lora_r.hint', 'wizard.training.lora_r.label', 'wizard.training.lora_r.detail')); + pane.appendChild(makeSlider('loraAlpha', 8, 128, 'wizard.training.lora_alpha.hint', 'wizard.training.lora_alpha.label', 'wizard.training.lora_alpha.detail')); + } // Epochs var epochsInput = el('input', { @@ -1001,9 +1884,124 @@ ]); appendDetail(batchRow, 'wizard.training.batch.detail'); pane.appendChild(batchRow); + + /* Trainer-specific hyperparameters. Conditionally rendered based + on state.trainerType. The schema in forgelm/config.py defines + these on TrainingConfig with sensible defaults; the wizard + lets the operator override them and emits the chosen value to + YAML so the run is reproducible. SFT has no trainer-specific + params, so the section is hidden entirely for SFT runs. */ + var trDef2 = trainerDef(state.trainerType); + if (trDef2.params.length > 0) { + var sectionHeader = el('div', { + class: 'wizard-step-tutorial', + style: 'margin-top: 1.5rem;', + html: '' + tr('wizard.training.trainer_section.title').replace('{trainer}', state.trainerType.toUpperCase()) + ' ' + tr('wizard.training.trainer_section.body') + }); + pane.appendChild(sectionHeader); + + // Helper for a numeric param row with a slider + live value + function makeParamSlider(field, min, max, step, labelKey, hintKey, detailKey) { + var slider = el('input', { + type: 'range', + class: 'wizard-slider', + min: String(min), + max: String(max), + step: String(step), + value: String(state[field]) + }); + var valueOut = el('span', { + class: 'wizard-slider-value', + text: String(state[field]) + }); + slider.addEventListener('input', function () { + var v = parseFloat(slider.value); + state[field] = v; + valueOut.textContent = String(v); + persist(); + liveYaml(); + }); + var row = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + el('div', { class: 'wizard-slider-row' }, [slider, valueOut]), + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]); + appendDetail(row, detailKey); + return row; + } + + // Helper for an integer param input + function makeParamNumber(field, min, max, step, labelKey, hintKey, detailKey) { + var input = el('input', { + type: 'number', + class: 'wizard-input', + min: String(min), + max: String(max), + step: String(step), + value: String(state[field]) + }); + input.addEventListener('input', function () { + var v = parseInt(input.value, 10); + if (!isNaN(v) && v >= min && v <= max) { + state[field] = v; + persist(); + liveYaml(); + } + }); + var row = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + input, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]); + appendDetail(row, detailKey); + return row; + } + + if (state.trainerType === 'dpo') { + pane.appendChild(makeParamSlider('dpoBeta', 0.05, 0.5, 0.05, + 'wizard.training.dpo_beta.label', 'wizard.training.dpo_beta.hint', 'wizard.training.dpo_beta.detail')); + } else if (state.trainerType === 'orpo') { + pane.appendChild(makeParamSlider('orpoBeta', 0.05, 0.5, 0.05, + 'wizard.training.orpo_beta.label', 'wizard.training.orpo_beta.hint', 'wizard.training.orpo_beta.detail')); + } else if (state.trainerType === 'simpo') { + pane.appendChild(makeParamSlider('simpoGamma', 0.1, 2.0, 0.1, + 'wizard.training.simpo_gamma.label', 'wizard.training.simpo_gamma.hint', 'wizard.training.simpo_gamma.detail')); + pane.appendChild(makeParamSlider('simpoBeta', 0.5, 4.0, 0.5, + 'wizard.training.simpo_beta.label', 'wizard.training.simpo_beta.hint', 'wizard.training.simpo_beta.detail')); + } else if (state.trainerType === 'kto') { + pane.appendChild(makeParamSlider('ktoBeta', 0.05, 0.5, 0.05, + 'wizard.training.kto_beta.label', 'wizard.training.kto_beta.hint', 'wizard.training.kto_beta.detail')); + } else if (state.trainerType === 'grpo') { + pane.appendChild(makeParamNumber('grpoNumGenerations', 1, 16, 1, + 'wizard.training.grpo_num_generations.label', 'wizard.training.grpo_num_generations.hint', 'wizard.training.grpo_num_generations.detail')); + pane.appendChild(makeParamNumber('grpoMaxCompletionLength', 64, 2048, 32, + 'wizard.training.grpo_max_completion_length.label', 'wizard.training.grpo_max_completion_length.hint', 'wizard.training.grpo_max_completion_length.detail')); + // GRPO reward model is optional; an empty value omits it from + // the YAML and TRL falls back to the built-in + // format/length-shaping reward. + var rewardInput = el('input', { + type: 'text', + class: 'wizard-input', + value: state.grpoRewardModel || '', + placeholder: tr('wizard.training.grpo_reward_model.placeholder') + }); + rewardInput.addEventListener('input', function () { + state.grpoRewardModel = rewardInput.value.trim(); + persist(); + liveYaml(); + }); + var rewardRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.training.grpo_reward_model.label') }), + rewardInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.training.grpo_reward_model.hint') }) + ]); + appendDetail(rewardRow, 'wizard.training.grpo_reward_model.detail'); + pane.appendChild(rewardRow); + } + } }; - /* Step 6: Evaluation + EU AI Act compliance */ + /* Step 7: Evaluation + EU AI Act compliance */ STEP_RENDERERS['compliance'] = function (pane, state, rerender, persist) { stepHeader(state, 'wizard.step.compliance.title', 'wizard.step.compliance.desc', 'wizard.step.compliance.tutorial') .forEach(function (n) { pane.appendChild(n); }); @@ -1127,32 +2125,655 @@ // high-risk" so the operator knows the obligation rises with the // tier. Empty values are valid; ComplianceMetadataConfig has // string defaults of "". - function makeMetaInput(field, labelKey, hintKey, placeholderKey) { + // ``errorKey`` is the i18n key for the inline validation message + // shown when the operator hits Next without filling this field. + // For high-risk tiers all three Annex IV §1 fields are mandatory; + // ``fieldHasError`` returns the key only after a failed Next so + // the first visit to the step stays uncluttered. + function makeMetaInput(field, labelKey, hintKey, placeholderKey, errorKey) { + var errKey = errorKey ? fieldHasError(state, 'compliance', field) : null; var input = el('input', { type: 'text', - class: 'wizard-input', + class: 'wizard-input' + (errKey ? ' is-invalid' : ''), value: state[field] || '', placeholder: tr(placeholderKey) }); - input.addEventListener('input', function () { - state[field] = input.value; - persist(); - liveYaml(); - }); - return el('div', { class: 'wizard-row' }, [ + var rowChildren = [ el('label', { class: 'wizard-row-label' }, [ tr(labelKey), highRisk ? el('span', { class: 'badge', text: tr('wizard.locked.badge'), style: 'margin-left: 0.4rem;' }) : null ]), input, el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) - ]); + ]; + if (errKey) { + rowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(errKey) })); + } + var row = el('div', { class: 'wizard-row' }, rowChildren); + input.addEventListener('input', function () { + state[field] = input.value; + persist(); + liveYaml(); + if (errorKey) { + var stillErr = validateStep(state, 'compliance').some(function (e) { return e.field === field; }); + if (!stillErr) { + input.classList.remove('is-invalid'); + var errEl = row.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + } + }); + return row; } if (highRisk) { - pane.appendChild(makeMetaInput('providerName', 'wizard.compliance.provider_name.label', 'wizard.compliance.provider_name.hint', 'wizard.compliance.provider_name.placeholder')); - pane.appendChild(makeMetaInput('systemName', 'wizard.compliance.system_name.label', 'wizard.compliance.system_name.hint', 'wizard.compliance.system_name.placeholder')); - pane.appendChild(makeMetaInput('intendedPurpose', 'wizard.compliance.intended_purpose.label', 'wizard.compliance.intended_purpose.hint', 'wizard.compliance.intended_purpose.placeholder')); + pane.appendChild(makeMetaInput('providerName', 'wizard.compliance.provider_name.label', 'wizard.compliance.provider_name.hint', 'wizard.compliance.provider_name.placeholder', 'wizard.error.compliance.provider_name.required')); + pane.appendChild(makeMetaInput('systemName', 'wizard.compliance.system_name.label', 'wizard.compliance.system_name.hint', 'wizard.compliance.system_name.placeholder', 'wizard.error.compliance.system_name.required')); + pane.appendChild(makeMetaInput('intendedPurpose', 'wizard.compliance.intended_purpose.label', 'wizard.compliance.intended_purpose.hint', 'wizard.compliance.intended_purpose.placeholder', 'wizard.error.compliance.intended_purpose.required')); + } + + // Article 9 Risk Assessment accordion. Open by default for strict + // tiers (high-risk / unacceptable) where Article 9 documentation + // is mandatory; collapsed otherwise as a reminder it exists. + if (highRisk && state.openAccordions && state.openAccordions['risk_assessment'] === undefined) { + state.openAccordions = state.openAccordions || {}; + state.openAccordions['risk_assessment'] = true; } + pane.appendChild(makeAccordion(state, persist, 'risk_assessment', + 'wizard.risk_assessment.title', 'wizard.risk_assessment.hint', function (body) { + + // Shared factory for the three required textareas. Wires the + // inline validation pattern: errKey appears only after a failed + // Next; the error visual + alert message are removed live as + // soon as the operator types something non-blank. + function makeRequiredTextarea(field, rows, labelKey, hintKey, placeholderKey, detailKey, errorKey) { + var errKey = errorKey ? fieldHasError(state, 'compliance', field) : null; + var ta = el('textarea', { + class: 'wizard-input' + (errKey ? ' is-invalid' : ''), + rows: String(rows), + placeholder: tr(placeholderKey) + }); + ta.value = state[field] || ''; + var rowChildren = [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + ta, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]; + if (errKey) { + rowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(errKey) })); + } + var row = el('div', { class: 'wizard-row' }, rowChildren); + ta.addEventListener('input', function () { + state[field] = ta.value; + persist(); + liveYaml(); + if (errorKey) { + var stillErr = validateStep(state, 'compliance').some(function (e) { return e.field === field; }); + if (!stillErr) { + ta.classList.remove('is-invalid'); + var errEl = row.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + } + }); + appendDetail(row, detailKey); + return row; + } + + // All three risk-assessment fields are required ONLY for strict + // tiers (high-risk + unacceptable). For everyone else they stay + // optional — the schema accepts empty strings. + var requireRA = isStrictRisk(state); + body.appendChild(makeRequiredTextarea('intendedUse', 2, + 'wizard.risk_assessment.intended_use.label', + 'wizard.risk_assessment.intended_use.hint', + 'wizard.risk_assessment.intended_use.placeholder', + 'wizard.risk_assessment.intended_use.detail', + requireRA ? 'wizard.error.risk_assessment.intended_use.required' : null)); + body.appendChild(makeRequiredTextarea('foreseeableMisuse', 3, + 'wizard.risk_assessment.foreseeable_misuse.label', + 'wizard.risk_assessment.foreseeable_misuse.hint', + 'wizard.risk_assessment.foreseeable_misuse.placeholder', + 'wizard.risk_assessment.foreseeable_misuse.detail', + requireRA ? 'wizard.error.risk_assessment.foreseeable_misuse.required' : null)); + body.appendChild(makeRequiredTextarea('mitigationMeasures', 3, + 'wizard.risk_assessment.mitigation_measures.label', + 'wizard.risk_assessment.mitigation_measures.hint', + 'wizard.risk_assessment.mitigation_measures.placeholder', + 'wizard.risk_assessment.mitigation_measures.detail', + requireRA ? 'wizard.error.risk_assessment.mitigation_measures.required' : null)); + + // vulnerable_groups_considered: checkbox + var vgInput = el('input', { type: 'checkbox' }); + vgInput.checked = !!state.vulnerableGroupsConsidered; + vgInput.addEventListener('change', function () { + state.vulnerableGroupsConsidered = vgInput.checked; + persist(); + liveYaml(); + }); + var vgRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + vgInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.risk_assessment.vulnerable_groups.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.risk_assessment.vulnerable_groups.hint') }) + ]); + appendDetail(vgRow, 'wizard.risk_assessment.vulnerable_groups.detail'); + body.appendChild(vgRow); + })); + + // Evaluation gates accordion — Benchmark + LLM Judge. Both are + // optional quality gates layered on top of the Auto-revert / Loss + // / Safety triplet that the main step exposes. Surfacing them in + // an accordion keeps the main step uncluttered for first-time + // users while making the full schema reachable for power users. + pane.appendChild(makeAccordion(state, persist, 'eval_gates', + 'wizard.eval_gates.title', 'wizard.eval_gates.hint', function (body) { + + // ── Benchmark sub-section ── + var benchInput = el('input', { type: 'checkbox' }); + benchInput.checked = !!state.benchmarkEnabled; + benchInput.addEventListener('change', function () { + state.benchmarkEnabled = benchInput.checked; + persist(); + rerender(); + }); + var benchToggleRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + benchInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.benchmark.enabled.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.benchmark.enabled.hint') }) + ]); + appendDetail(benchToggleRow, 'wizard.benchmark.enabled.detail'); + body.appendChild(benchToggleRow); + + if (state.benchmarkEnabled) { + // benchmarkTasks is required when the benchmark gate is on — + // an empty list makes the gate a no-op. Validation message + // explains this so the operator knows why they can't advance. + var benchTasksErrKey = fieldHasError(state, 'compliance', 'benchmarkTasks'); + var tasksTextarea = el('textarea', { + class: 'wizard-input' + (benchTasksErrKey ? ' is-invalid' : ''), + rows: '3', + placeholder: 'arc_easy\nhellaswag\nmmlu' + }); + tasksTextarea.value = state.benchmarkTasks || ''; + var tasksRowChildren = [ + el('label', { class: 'wizard-row-label', text: tr('wizard.benchmark.tasks.label') }), + tasksTextarea, + el('span', { class: 'wizard-row-hint', text: tr('wizard.benchmark.tasks.hint') }) + ]; + if (benchTasksErrKey) { + tasksRowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(benchTasksErrKey) })); + } + var tasksRow = el('div', { class: 'wizard-row' }, tasksRowChildren); + tasksTextarea.addEventListener('input', function () { + state.benchmarkTasks = tasksTextarea.value; + persist(); + liveYaml(); + var stillErr = validateStep(state, 'compliance').some(function (e) { return e.field === 'benchmarkTasks'; }); + if (!stillErr) { + tasksTextarea.classList.remove('is-invalid'); + var errEl = tasksRow.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + }); + body.appendChild(tasksRow); + + var minScoreInput = el('input', { + type: 'number', + class: 'wizard-input', + min: '0', + max: '1', + step: '0.01', + placeholder: tr('wizard.benchmark.min_score.placeholder'), + value: state.benchmarkMinScore || '' + }); + minScoreInput.addEventListener('input', function () { + state.benchmarkMinScore = minScoreInput.value; + persist(); + liveYaml(); + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.benchmark.min_score.label') }), + minScoreInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.benchmark.min_score.hint') }) + ])); + } + + // ── LLM Judge sub-section ── + var judgeInput = el('input', { type: 'checkbox' }); + judgeInput.checked = !!state.judgeEnabled; + judgeInput.addEventListener('change', function () { + state.judgeEnabled = judgeInput.checked; + persist(); + rerender(); + }); + var judgeToggleRow = el('div', { class: 'wizard-row', style: 'margin-top: 1.4rem;' }, [ + el('label', { class: 'wizard-toggle' }, [ + judgeInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.judge.enabled.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.judge.enabled.hint') }) + ]); + appendDetail(judgeToggleRow, 'wizard.judge.enabled.detail'); + body.appendChild(judgeToggleRow); + + if (state.judgeEnabled) { + var judgeModelInput = el('input', { + type: 'text', + class: 'wizard-input', + placeholder: 'gpt-4o', + value: state.judgeModel || '' + }); + judgeModelInput.addEventListener('input', function () { + state.judgeModel = judgeModelInput.value.trim(); + persist(); + liveYaml(); + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.judge.model.label') }), + judgeModelInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.judge.model.hint') }) + ])); + + // Judge API key env var is required when the judge gate is on — + // without it the runtime can't authenticate to the judge model. + var judgeApiErrKey = fieldHasError(state, 'compliance', 'judgeApiKeyEnv'); + var judgeApiInput = el('input', { + type: 'text', + class: 'wizard-input' + (judgeApiErrKey ? ' is-invalid' : ''), + placeholder: 'OPENAI_API_KEY', + value: state.judgeApiKeyEnv || '' + }); + var judgeApiRowChildren = [ + el('label', { class: 'wizard-row-label', text: tr('wizard.judge.api_key_env.label') }), + judgeApiInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.judge.api_key_env.hint') }) + ]; + if (judgeApiErrKey) { + judgeApiRowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(judgeApiErrKey) })); + } + var judgeApiRow = el('div', { class: 'wizard-row' }, judgeApiRowChildren); + judgeApiInput.addEventListener('input', function () { + state.judgeApiKeyEnv = judgeApiInput.value.trim(); + persist(); + liveYaml(); + var stillErr = validateStep(state, 'compliance').some(function (e) { return e.field === 'judgeApiKeyEnv'; }); + if (!stillErr) { + judgeApiInput.classList.remove('is-invalid'); + var errEl = judgeApiRow.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + }); + body.appendChild(judgeApiRow); + + var judgeScoreInput = el('input', { + type: 'number', + class: 'wizard-input', + min: '1', + max: '10', + step: '0.5', + value: String(state.judgeMinScore || 5.0) + }); + judgeScoreInput.addEventListener('input', function () { + var v = parseFloat(judgeScoreInput.value); + if (!isNaN(v) && v >= 1 && v <= 10) { + state.judgeMinScore = v; + persist(); + liveYaml(); + } + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.judge.min_score.label') }), + judgeScoreInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.judge.min_score.hint') }) + ])); + } + })); + + }; + + /* Step 8: Advanced operational settings — notifications, post-market + monitoring, synthetic data generation, retention horizons. All + optional; the operator can simply Next through this step if none of + these features apply. Lifted out of the compliance step in v3 so + each block stays digestible (compliance was getting too tall to + scan in one viewport). */ + STEP_RENDERERS['operations'] = function (pane, state, rerender, persist) { + stepHeader(state, 'wizard.step.operations.title', 'wizard.step.operations.desc', 'wizard.step.operations.tutorial') + .forEach(function (n) { pane.appendChild(n); }); + + function liveYaml() { + var yamlOutput = document.querySelector('[data-wizard-yaml-output]'); + if (yamlOutput) yamlOutput.innerHTML = tintYaml(buildYaml(state)); + } + + // Same per-row tutorial-paragraph helper as the other steps. + // Adds a wizard-row-detail block beneath the row when the operator + // is in beginner mode (state.detailsVisible). The detail block + // gives 2-3 sentences of plain-language context the hint glosses. + function appendDetail(rowEl, detailKey) { + if (state.detailsVisible && detailKey) { + rowEl.appendChild(el('p', { class: 'wizard-row-detail', text: tr(detailKey) })); + } + } + + // Webhook (Slack/Teams/Discord) accordion. + pane.appendChild(makeAccordion(state, persist, 'webhook', + 'wizard.webhook.title', 'wizard.webhook.hint', function (body) { + + // Enable toggle. When off, the rest of the accordion's inputs + // stay collapsed and the YAML omits the webhook block entirely. + // When on, the URL is required (validateStep paints an inline + // error if blank when the operator hits Next). + var enabledInput = el('input', { type: 'checkbox' }); + enabledInput.checked = !!state.webhookEnabled; + enabledInput.addEventListener('change', function () { + state.webhookEnabled = enabledInput.checked; + persist(); + rerender(); + }); + var enabledRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + enabledInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.webhook.enabled.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.webhook.enabled.hint') }) + ]); + appendDetail(enabledRow, 'wizard.webhook.enabled.detail'); + body.appendChild(enabledRow); + + if (!state.webhookEnabled) return; + + // URL field — required when webhookEnabled is true. Validation + // is wired through fieldHasError so the inline error appears + // only after the operator tried to advance past this step. + var webhookUrlErrKey = fieldHasError(state, 'operations', 'webhookUrl'); + var urlInput = el('input', { + type: 'text', + class: 'wizard-input' + (webhookUrlErrKey ? ' is-invalid' : ''), + placeholder: tr('wizard.webhook.url.placeholder'), + value: state.webhookUrl || '' + }); + urlInput.addEventListener('input', function () { + state.webhookUrl = urlInput.value.trim(); + persist(); + liveYaml(); + // Drop the error visual once the field becomes non-empty. + var stillErr = validateStep(state, 'operations').some(function (e) { return e.field === 'webhookUrl'; }); + if (!stillErr) { + urlInput.classList.remove('is-invalid'); + var errEl = urlRow.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + }); + var urlRowChildren = [ + el('label', { class: 'wizard-row-label', text: tr('wizard.webhook.url.label') }), + urlInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.webhook.url.hint') }) + ]; + if (webhookUrlErrKey) { + urlRowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(webhookUrlErrKey) })); + } + var urlRow = el('div', { class: 'wizard-row' }, urlRowChildren); + appendDetail(urlRow, 'wizard.webhook.url.detail'); + body.appendChild(urlRow); + + function makeWebhookToggle(field, labelKey) { + var cb = el('input', { type: 'checkbox' }); + cb.checked = !!state[field]; + cb.addEventListener('change', function () { + state[field] = cb.checked; + persist(); + liveYaml(); + }); + return el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + cb, + el('span', { class: 'wizard-toggle-label', text: tr(labelKey) }) + ]) + ]); + } + body.appendChild(makeWebhookToggle('webhookNotifyStart', 'wizard.webhook.notify_start.label')); + body.appendChild(makeWebhookToggle('webhookNotifySuccess', 'wizard.webhook.notify_success.label')); + body.appendChild(makeWebhookToggle('webhookNotifyFailure', 'wizard.webhook.notify_failure.label')); + })); + + // Post-market monitoring (Article 12+17) accordion. + pane.appendChild(makeAccordion(state, persist, 'monitoring', + 'wizard.monitoring.title', 'wizard.monitoring.hint', function (body) { + var monLocked = isStrictRisk(state); + var monInput = el('input', { type: 'checkbox' }); + monInput.checked = !!state.monitoringEnabled || monLocked; + if (monLocked) monInput.disabled = true; + monInput.addEventListener('change', function () { + state.monitoringEnabled = monInput.checked; + persist(); + rerender(); + }); + var monLabelEl = el('span', { class: 'wizard-toggle-label', text: tr('wizard.monitoring.enabled.label') }); + if (monLocked) { + monLabelEl.appendChild(el('span', { + class: 'badge', + text: tr('wizard.locked.badge'), + style: 'margin-left: 0.4rem;' + })); + } + var monRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [monInput, monLabelEl]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.monitoring.enabled.hint') }) + ]); + appendDetail(monRow, 'wizard.monitoring.enabled.detail'); + body.appendChild(monRow); + + var endpointShown = !!state.monitoringEnabled || monLocked; + if (endpointShown) { + // Endpoint is required when monitoring is enabled — same + // pattern as webhook.url. fieldHasError gates the visual so + // the error appears only after a failed Next attempt. + var monEndpointErrKey = fieldHasError(state, 'operations', 'monitoringEndpoint'); + var epInput = el('input', { + type: 'text', + class: 'wizard-input' + (monEndpointErrKey ? ' is-invalid' : ''), + placeholder: tr('wizard.monitoring.endpoint.placeholder'), + value: state.monitoringEndpoint || '' + }); + var epRowChildren = [ + el('label', { class: 'wizard-row-label', text: tr('wizard.monitoring.endpoint.label') }), + epInput, + el('span', { class: 'wizard-row-hint', text: tr('wizard.monitoring.endpoint.hint') }) + ]; + if (monEndpointErrKey) { + epRowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(monEndpointErrKey) })); + } + var epRow = el('div', { class: 'wizard-row' }, epRowChildren); + epInput.addEventListener('input', function () { + state.monitoringEndpoint = epInput.value.trim(); + persist(); + liveYaml(); + var stillErr = validateStep(state, 'operations').some(function (e) { return e.field === 'monitoringEndpoint'; }); + if (!stillErr) { + epInput.classList.remove('is-invalid'); + var errEl = epRow.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + }); + body.appendChild(epRow); + + var meSelect = el('select', { class: 'wizard-select' }); + [ + { id: 'none', labelKey: 'wizard.monitoring.metrics_export.none' }, + { id: 'prometheus', labelKey: 'wizard.monitoring.metrics_export.prometheus' }, + { id: 'datadog', labelKey: 'wizard.monitoring.metrics_export.datadog' }, + { id: 'custom_webhook', labelKey: 'wizard.monitoring.metrics_export.custom' } + ].forEach(function (opt) { + var option = el('option', { value: opt.id, text: tr(opt.labelKey) }); + if (state.metricsExport === opt.id) option.selected = true; + meSelect.appendChild(option); + }); + meSelect.addEventListener('change', function () { + state.metricsExport = meSelect.value; + persist(); + liveYaml(); + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.monitoring.metrics_export.label') }), + meSelect + ])); + + var driftCb = el('input', { type: 'checkbox' }); + driftCb.checked = !!state.alertOnDrift; + driftCb.addEventListener('change', function () { + state.alertOnDrift = driftCb.checked; + persist(); + liveYaml(); + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + driftCb, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.monitoring.alert_on_drift.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.monitoring.alert_on_drift.hint') }) + ])); + } + })); + + // Synthetic data generation accordion. + pane.appendChild(makeAccordion(state, persist, 'synthetic', + 'wizard.synthetic.title', 'wizard.synthetic.hint', function (body) { + var synInput = el('input', { type: 'checkbox' }); + synInput.checked = !!state.syntheticEnabled; + synInput.addEventListener('change', function () { + state.syntheticEnabled = synInput.checked; + persist(); + rerender(); + }); + var synRow = el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-toggle' }, [ + synInput, + el('span', { class: 'wizard-toggle-label', text: tr('wizard.synthetic.enabled.label') }) + ]), + el('span', { class: 'wizard-row-hint', text: tr('wizard.synthetic.enabled.hint') }) + ]); + appendDetail(synRow, 'wizard.synthetic.enabled.detail'); + body.appendChild(synRow); + + if (state.syntheticEnabled) { + // Synthetic data generation has THREE required inputs once + // the feature is enabled: teacher model, seed prompts file, + // and the API key env var. None have a sensible default that + // ForgeLM can guess, so all three are validated here. The + // factory below mirrors the validateStep ↔ inline-error + // wiring used by webhook.url and monitoring.endpoint. + function makeRequiredText(field, labelKey, hintKey, placeholderKey, errorKey) { + var errKey = fieldHasError(state, 'operations', field); + var inp = el('input', { + type: 'text', + class: 'wizard-input' + (errKey ? ' is-invalid' : ''), + placeholder: placeholderKey ? tr(placeholderKey) : '', + value: state[field] || '' + }); + var rowChildren = [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + inp, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]; + if (errKey) { + rowChildren.push(el('div', { class: 'wizard-error', role: 'alert', text: tr(errKey) })); + } + var row = el('div', { class: 'wizard-row' }, rowChildren); + inp.addEventListener('input', function () { + state[field] = inp.value.trim(); + persist(); + liveYaml(); + var stillErr = validateStep(state, 'operations').some(function (e) { return e.field === field; }); + if (!stillErr) { + inp.classList.remove('is-invalid'); + var errEl = row.querySelector('.wizard-error'); + if (errEl) errEl.remove(); + } + }); + return row; + } + body.appendChild(makeRequiredText('syntheticTeacherModel', + 'wizard.synthetic.teacher_model.label', + 'wizard.synthetic.teacher_model.hint', + null, + 'wizard.error.synthetic.teacher_model.required')); + body.appendChild(makeRequiredText('syntheticSeedFile', + 'wizard.synthetic.seed_file.label', + 'wizard.synthetic.seed_file.hint', + 'wizard.synthetic.seed_file.placeholder', + 'wizard.error.synthetic.seed_file.required')); + body.appendChild(makeRequiredText('syntheticApiKeyEnv', + 'wizard.synthetic.api_key_env.label', + 'wizard.synthetic.api_key_env.hint', + null, + 'wizard.error.synthetic.api_key_env.required')); + } + })); + + // Retention horizons (GDPR Art. 5+17) accordion. + pane.appendChild(makeAccordion(state, persist, 'retention', + 'wizard.retention.title', 'wizard.retention.hint', function (body) { + function makeNumberRow(field, labelKey, hintKey) { + var inp = el('input', { + type: 'number', + class: 'wizard-input', + min: '0', + step: '1', + value: String(state[field]) + }); + inp.addEventListener('input', function () { + var v = parseInt(inp.value, 10); + if (!isNaN(v) && v >= 0) { + state[field] = v; + persist(); + liveYaml(); + } + }); + return el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr(labelKey) }), + inp, + el('span', { class: 'wizard-row-hint', text: tr(hintKey) }) + ]); + } + body.appendChild(makeNumberRow('retentionAuditLog', + 'wizard.retention.audit_log.label', 'wizard.retention.audit_log.hint')); + body.appendChild(makeNumberRow('retentionStaging', + 'wizard.retention.staging.label', 'wizard.retention.staging.hint')); + body.appendChild(makeNumberRow('retentionEphemeral', + 'wizard.retention.ephemeral.label', 'wizard.retention.ephemeral.hint')); + body.appendChild(makeNumberRow('retentionRawDocuments', + 'wizard.retention.raw_documents.label', 'wizard.retention.raw_documents.hint')); + + // ``enforce`` is a 3-way Literal (log_only | warn_on_excess | + // block_on_excess) — surfaced as a select. Default ``log_only`` + // is the safe choice; ``block_on_excess`` is for regulated CI + // gates where silent extension of retention horizons is the + // bug we're trying to prevent. + var enforceSelect = el('select', { class: 'wizard-select' }); + [ + { id: 'log_only', labelKey: 'wizard.retention.enforce.log_only' }, + { id: 'warn_on_excess', labelKey: 'wizard.retention.enforce.warn' }, + { id: 'block_on_excess', labelKey: 'wizard.retention.enforce.block' } + ].forEach(function (opt) { + var option = el('option', { value: opt.id, text: tr(opt.labelKey) }); + if (state.retentionEnforce === opt.id) option.selected = true; + enforceSelect.appendChild(option); + }); + enforceSelect.addEventListener('change', function () { + state.retentionEnforce = enforceSelect.value; + persist(); + liveYaml(); + }); + body.appendChild(el('div', { class: 'wizard-row' }, [ + el('label', { class: 'wizard-row-label', text: tr('wizard.retention.enforce.label') }), + enforceSelect, + el('span', { class: 'wizard-row-hint', text: tr('wizard.retention.enforce.hint') }) + ])); + })); }; /* Step 7: Review + download */ From 6783a5c24313c73140191bae4f75c6b5d6b43aa8 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 7 May 2026 16:27:35 +0300 Subject: [PATCH 09/10] =?UTF-8?q?fix(site):=20full=20review=20pass=20?= =?UTF-8?q?=E2=80=94=2022=20verified=20findings=20(3=20CRITICAL=20+=208=20?= =?UTF-8?q?HIGH=20+=209=20MEDIUM=20+=202=20LOW)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Synthesis of the six-agent site audit (docs/analysis/code_reviews/ 2026-05-07_site_full_review.md). Each finding was verified against the code first; 7 reported issues were false positives and skipped (documented in the commit body), 1 major refactor (translations per-locale split) was deliberately deferred. CRITICAL — 3 confirmed: - Wizard dialog aria-labelledby="wizard-title" pointed at a non-existent id (orphan reference; screen readers announced "dialog" with no name). Fixed by adding id="wizard-title" to the rendered step title in all three render sites in wizard.js. - 6 of 8 pages had no
wrapper around primary content. Wrapped every page (except guide.html which already had it) so assistive tech, search engines, and skip-links can locate the main region. - All 8 pages declared pointing at the same URL as the EN canonical. The site uses runtime JS i18n on a single English-baseline URL, so the alternate misled crawlers. Removed the false hreflang and added an HTML comment explaining the choice. HIGH — 8 fixed, 1 deferred: - index.html had three

tags (one per hero slide). Demoted slides 2 + 3 to

; CSS .hero h1 generalized to .hero h1, .hero h2 so the visual treatment is identical. - All early - + + + + + + + + +

-
+
What you get on disk @@ -217,7 +268,7 @@

A run's compliance footprint

After every successful (or failed) run, this is what lands in your output directory.

-
+
checkpoints/policy-bot/ @@ -252,7 +303,7 @@

A run's compliance footprint

What ForgeLM does not claim

-
+ + diff --git a/site/contact.html b/site/contact.html index cf099b5f..6e566998 100644 --- a/site/contact.html +++ b/site/contact.html @@ -14,27 +14,76 @@ + + + + + - + - - + + + + + + + + +
+ diff --git a/site/css/style.css b/site/css/style.css index 288c7c60..8c864438 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -2,9 +2,13 @@ ForgeLM — Site Stylesheet Forge metaphor: ember orange + steel cyan Dark-first, vanilla CSS, no framework, no build step. - ============================================================ */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); + Note on fonts: Google Fonts CSS is loaded via a tag in + each page's (right after the preconnects). Loading via + HTML link is faster than CSS @import — the browser can fetch + the font CSS in parallel with style.css instead of waiting for + style.css to parse before discovering the @import. + ============================================================ */ /* ── Tokens ──────────────────────────────────────────────── */ :root { @@ -73,7 +77,27 @@ --glow-ember: 0 0 0 1px rgba(249,115,22,.35), 0 8px 32px rgba(249,115,22,.18); --glow-steel: 0 0 0 1px rgba(14,165,233,.35), 0 8px 32px rgba(14,165,233,.18); --transition: .22s cubic-bezier(.4,0,.2,1); + --transition-fast: .15s cubic-bezier(.4,0,.2,1); --transition-slow: .4s cubic-bezier(.4,0,.2,1); + + /* Responsive breakpoints — canonical 4-tier scale. + Existing media queries in this file use a wider mix of breakpoint + values (480 / 560 / 600 / 720 / 880 / 900 / 980 / 1100 / 1180 / + 1280) chosen for individual component layouts. We do NOT migrate + them en masse — that would risk subtle visual regressions. New + rules should reach for these tokens in CSS calc() / clamp() + contexts; @media queries still need literal values per CSS spec. */ + --bp-sm: 480px; /* small mobile */ + --bp-md: 768px; /* tablet portrait */ + --bp-lg: 900px; /* small desktop / wizard mobile cutoff */ + --bp-xl: 1200px; /* wide desktop */ + + /* Terminal/window-control dot colors — macOS-style red/yellow/green + used in the hero terminal mock + wizard YAML preview. Tokens only + so future surfaces can stay visually consistent. */ + --dot-red: #ff5f56; + --dot-yellow: #ffbd2e; + --dot-green: #27c93f; } /* Light mode override */ @@ -103,6 +127,51 @@ --pill-danger-bg: #fee2e2; } +/* ── Wizard storage-unavailable banner ─────────────────────── + Painted only when localStorage save fails (private-mode, quota, + blocked storage). Uses role="status" + aria-live="polite" so a + screen reader announces it without trapping focus. */ +.wizard-storage-warn { + position: absolute; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 110; + max-width: min(560px, calc(100% - 2rem)); + padding: 0.7rem 1rem; + background: var(--pill-warn-bg, #fef9c3); + color: var(--pill-warn-fg, #713f12); + border-radius: var(--radius-sm); + border-left: 3px solid var(--warn, #d97706); + font-size: 0.85rem; + box-shadow: var(--shadow); +} + +/* ── Skip to main content (a11y, WCAG 2.4.1 Bypass Blocks) ─── */ +/* Visually hidden until focused. First Tab on any page reveals + the link as a banner above the navbar so keyboard users can + jump past nav into the page's
. */ +.skip-link { + position: absolute; + top: 0.5rem; + left: 0.5rem; + z-index: 200; + padding: 0.6rem 1rem; + background: var(--ember); + color: #fff; + font-weight: 600; + border-radius: var(--radius-sm); + text-decoration: none; + transform: translateY(-200%); + transition: transform var(--transition); +} +.skip-link:focus, +.skip-link:focus-visible { + transform: translateY(0); + outline: 2px solid #fff; + outline-offset: 2px; +} + /* ── Reset ───────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @@ -197,6 +266,59 @@ section { padding: 5rem 0; } } .text-center { text-align: center; } +.text-center-mt { + text-align: center; + margin-top: 2rem; +} + +/* ── Section + container utilities — extracted from inline ``style=""`` + attributes that previously lived in HTML. The class names describe + the design intent (alternate-band section, full-bleed container, + compact CTA card) so future authors don't reach for inline styles. +*/ +.section-alt { + background: var(--bg-alt); +} +.container-flush { + padding-left: 0; + padding-right: 0; +} +.stack-narrow { + margin-top: 4rem; + padding: 0; +} +.cta-card-compact { + padding: 2rem; +} + +/* Quickstart wizard CTA inside the manual 5-step page — compact h2 + + lead so it sits comfortably below the steps without dwarfing them. */ +.quickstart-wizard-h2 { + font-size: 1.4rem; + margin-bottom: 0.5rem; +} +.quickstart-wizard-p { + font-size: 0.95rem; + margin-bottom: 1.2rem; +} + +/* Compliance page closing remark linking to the audit cookbook. */ +.compliance-cookbook { + margin-top: 2rem; + font-size: 0.95rem; + color: var(--text-muted); + text-align: center; +} + +/* Contact page hero strip + form intro. */ +.contact-section { padding-top: 3rem; } +.contact-form-intro { + font-size: 0.9rem; + margin-bottom: 1.4rem; +} + +/* Compliance page article-grid wrapper that needs a small upper gap. */ +.legal-content-mt { margin-top: 1rem; } .text-gradient { background: linear-gradient(135deg, var(--ember) 0%, var(--steel) 100%); -webkit-background-clip: text; @@ -289,7 +411,7 @@ section { padding: 5rem 0; } color: var(--text-secondary); font-size: 0.92rem; font-weight: 500; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .nav-menu a:hover, .nav-menu a.active { color: var(--text); @@ -321,7 +443,7 @@ section { padding: 5rem 0; } font: 600 0.78rem var(--font-sans); color: var(--text); cursor: pointer; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .lang-toggle-btn:hover { border-color: var(--ember); @@ -361,7 +483,7 @@ section { padding: 5rem 0; } font: 500 0.88rem var(--font-sans); color: var(--text-secondary); cursor: pointer; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .lang-toggle-menu button:hover { background: var(--bg-elev); @@ -382,7 +504,7 @@ section { padding: 5rem 0; } place-items: center; cursor: pointer; color: var(--text-secondary); - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .theme-toggle:hover { color: var(--ember-bright); @@ -443,7 +565,7 @@ section { padding: 5rem 0; } border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); white-space: nowrap; line-height: 1.2; } @@ -502,7 +624,7 @@ section { padding: 5rem 0; } background: radial-gradient(ellipse at 30% 40%, rgba(249,115,22,.18), transparent 55%), radial-gradient(ellipse at 70% 60%, rgba(14,165,233,.14), transparent 55%); - filter: blur(40px); + filter: blur(24px); pointer-events: none; z-index: 0; } @@ -561,9 +683,16 @@ section { padding: 5rem 0; } 50% { opacity: .35; } } -.hero h1 { +/* Hero-slide titles share the same visual treatment regardless of tag. + Slide 1 uses

(the page's primary heading); slides 2 + 3 use

+ so each page has exactly one h1 — required for SEO + WCAG heading + hierarchy. The font-size scale collapses h1 + h2 to identical values + in this slot. */ +.hero h1, +.hero h2 { margin-bottom: 1.2rem; letter-spacing: -0.03em; + font-size: clamp(2rem, 5vw, 3.25rem); } .hero-lead { @@ -825,9 +954,9 @@ section { padding: 5rem 0; } height: 11px; border-radius: 50%; } -.terminal-bar .dot.r { background: #ff5f56; } -.terminal-bar .dot.y { background: #ffbd2e; } -.terminal-bar .dot.g { background: #27c93f; } +.terminal-bar .dot.r { background: var(--dot-red); } +.terminal-bar .dot.y { background: var(--dot-yellow); } +.terminal-bar .dot.g { background: var(--dot-green); } .terminal-body { padding: 1.2rem 1.3rem; @@ -921,7 +1050,7 @@ section { padding: 5rem 0; } padding: 1.2rem 1rem; text-align: center; position: relative; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); cursor: default; } .pipeline-stage:hover { @@ -966,7 +1095,7 @@ section { padding: 5rem 0; } border: 1px solid var(--border); border-radius: var(--radius); padding: 1.6rem; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); position: relative; overflow: hidden; } @@ -1158,7 +1287,7 @@ section { padding: 5rem 0; } border-radius: 4px; cursor: pointer; font: 500 0.72rem var(--font-mono); - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); min-height: 32px; } .copy-btn:hover { color: var(--ember-bright); border-color: var(--ember); } @@ -1179,7 +1308,7 @@ section { padding: 5rem 0; } border: 1px solid var(--border); border-radius: var(--radius); padding: 1.4rem; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); position: relative; } .article-card:hover { @@ -1296,7 +1425,7 @@ section { padding: 5rem 0; } background: radial-gradient(ellipse at 30% 50%, rgba(249,115,22,.2), transparent 60%), radial-gradient(ellipse at 70% 50%, rgba(14,165,233,.18), transparent 60%); - filter: blur(40px); + filter: blur(24px); pointer-events: none; } .cta-card > * { position: relative; z-index: 1; } @@ -2479,7 +2608,7 @@ section { padding: 5rem 0; } border: 1px solid var(--border-strong); border-radius: var(--radius-sm); outline: none; - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .form-group input:focus, .form-group textarea:focus, @@ -2530,7 +2659,7 @@ section { padding: 5rem 0; } padding: 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border); - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .channel-item:hover { background: var(--bg-elev); @@ -2617,6 +2746,15 @@ section { padding: 5rem 0; } color: var(--text); margin-bottom: 0.8rem; } +.footer-brand-mark { + width: 28px; + height: 28px; + border-radius: 6px; +} +.footer-brand-icon { + width: 14px; + height: 14px; +} .footer-desc { font-size: 0.88rem; color: var(--text-muted); @@ -2673,6 +2811,13 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } } /* ── Print ──────────────────────────────────────────────── */ +/* ── Print stylesheet ──────────────────────────────────────── + ``!important`` is industry-standard for print resets: the cascade + has already applied dark-theme tokens, animation surfaces, and + chrome backdrops, all of which need to be force-overridden for + paper output. A scoped-reset alternative would require either + sweeping selector duplication or a body[data-printing] hook — + both worse for maintenance than the explicit !important here. */ @media print { .navbar, .nav-hamburger, .theme-toggle, .lang-toggle, .hero-terminal, .copy-btn, footer { display: none !important; } @@ -2835,7 +2980,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } border-radius: var(--radius-sm); border-left: 2px solid transparent; margin-left: -2px; - transition: all 0.15s ease; + transition: background-color, border-color, color, transform, box-shadow, opacity 0.15s ease; line-height: 1.4; } .guide-nav-section a:hover { @@ -3162,7 +3307,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-card); - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); text-decoration: none !important; min-height: 70px; } @@ -3241,7 +3386,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--radius-sm); - transition: all var(--transition); + transition: background-color, border-color, color, transform, box-shadow, opacity var(--transition); } .guide-edit:hover { color: var(--ember-bright); @@ -3317,7 +3462,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } color: var(--text); text-decoration: none; border-left: 3px solid transparent; - transition: all 0.12s ease; + transition: background-color, border-color, color, transform, box-shadow, opacity 0.12s ease; } .guide-search-results a:hover, .guide-search-results a.focused { diff --git a/site/features.html b/site/features.html index e7205d94..30eb7b35 100644 --- a/site/features.html +++ b/site/features.html @@ -14,27 +14,76 @@ + + + + + - + - - + + + + + + + + +

@@ -143,7 +194,7 @@

Long-context

- +

Evaluation & safety

@@ -152,41 +203,41 @@

Evaluation & safety

-
-
+
+

Benchmark integration

Plug-in lm-evaluation-harness tasks. Per-task floor thresholds.

-
-
-
+
+
+

LLM-as-judge

Quality scoring via OpenAI API or a local judge model.

- -
-
+
+
+

Llama Guard safety

Confidence-weighted scoring across 14 harm categories.

- -
-
+
+
+

Auto-revert

If thresholds fail, ForgeLM rolls back to the last-good checkpoint.

- -
-
+
+
+

Trend tracking

Cross-run safety / quality history.

- -
-
+
+
+

Exit codes

0 / 1 / 2 / 3 / 4 — a public contract for CI/CD.

- -
-
+
+
+

Standalone safety eval

forgelm safety-eval runs Llama Guard against any JSONL/Parquet/HF dataset and emits the same Article 9 risk artefact as a full run.

- +
@@ -196,7 +247,7 @@

Standalone safety eval

- +

Data ingestion & audit

@@ -205,46 +256,46 @@

Data ingestion & audit

-
-
+
+

Multi-format ingest

PDF, DOCX, EPUB, TXT, Markdown → SFT-ready JSONL.

-
-
-
+
+
+

PII masking

Email, phone, credit card, IBAN, national IDs.

- -
-
+
+
+

Secrets scrubbing

AWS keys, GitHub PATs, JWTs — redacted with [REDACTED-SECRET].

- -
-
+
+
+

Near-duplicate detection

LSH-banded simhash or MinHash LSH.

- -
-
+
+
+

Quality filter

Gopher / C4 / RefinedWeb-style heuristics.

- -
-
+
+
+

Language detection

Top-3 language counts per split via langdetect.

- -
-
+
+
+

Cross-split leakage

Catches train rows that also appear in validation/test.

- -
-
+
+
+

Annex IV provenance

Audit report embedded in EU AI Act Article 10 governance artifact.

- +
@@ -254,7 +305,7 @@

Annex IV provenance

- +

Enterprise & MLOps

@@ -263,76 +314,76 @@

Enterprise & MLOps

-
-
+
+

Webhook notifications

Slack and Teams alerts on training events.

-
-
-
+
+
+

Experiment tracking

W&B, MLflow, TensorBoard via report_to.

- -
-
+
+
+

JSON output mode

--output-format json emits a machine-readable envelope.

- -
-
+
+
+

Air-gap operation

No mandatory internet calls.

- -
-
+
+
+

Docker & compose

Official Dockerfile and docker-compose.yml.

- -
-
+
+
+

Model card auto-gen

HuggingFace-compatible README with metrics and config.

- -
-
+
+
+

GPU cost estimation

Auto-detection across 16 GPU profiles with cost tracking.

- -
-
+
+
+

Synthetic data pipeline

Teacher-to-student distillation with --generate-data.

- -
-
+
+
+

Model merging

TIES, DARE, SLERP, linear merge via --merge.

- -
-
+
+
+

Library API

forgelm.api exposes train, audit, safety-eval, and verify-audit as Python functions for embedding inside other pipelines.

- -
-
+
+
+

Environment diagnostics

forgelm doctor probes Python, GPU, drivers, optional extras, disk space, network, and prints a green/yellow/red triage report.

- -
-
+
+
+

Air-gap pre-cache

forgelm cache-models and forgelm cache-tasks stage HuggingFace models and lm-eval-harness datasets for offline runs.

- -
-
+
+
+

Deterministic audit workers

--workers N parallelises the data audit while preserving deterministic, byte-identical reports across runs.

- -
-
+
+
+

Supply-chain security

CycloneDX 1.5 SBOM emitted per release tag, plus pip-audit nightly and bandit in CI.

- +
@@ -342,7 +393,7 @@

Supply-chain security

- +

Compliance & GDPR tooling

@@ -351,36 +402,36 @@

Compliance & GDPR tooling

-
-
+
+

Article 14 staging gate

forgelm approve / forgelm reject + forgelm approvals implement the human-oversight staging directory with audit-stamped sign-off.

-
-
-
+
+
+

GDPR Article 17 erasure

forgelm purge drops a data-subject's rows from training corpora and stamps the deletion in the append-only audit log.

- -
-
+
+
+

GDPR Article 15 access

forgelm reverse-pii walks the PII redaction map back to surface every row in which a data subject appears.

- -
-
+
+
+

Annex IV verifier

forgelm verify-annex-iv validates a generated artefact bundle against the Annex IV schema and the run's audit chain.

- -
-
+
+
+

GGUF integrity verifier

forgelm verify-gguf validates the GGUF magic header, parses the metadata block, and re-checks the SHA-256 sidecar (when present) before you ship a quantized model.

- -
-
+
+
+

ISO 27001 / SOC 2 alignment

Audit-trail, change-management, data-lineage, and supply-chain evidence the deployer's auditor asks for. Software is aligned, not certified.

- +
@@ -398,16 +449,57 @@

Want to see it in YAML?

+ + diff --git a/site/guide.html b/site/guide.html index 38565e8f..2a7a2ed9 100644 --- a/site/guide.html +++ b/site/guide.html @@ -14,25 +14,74 @@ + + + + + - + - - - + + + + + + + + 0 @@ -40,10 +89,10 @@ -
+